diff --git a/logo.mjs b/logo.mjs
index 268d2ea..34f0eaf 100644
--- a/logo.mjs
+++ b/logo.mjs
@@ -12,4 +12,4 @@ 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/package-lock.json b/package-lock.json
index 06f6b5d..23d4e26 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@companion-ai/feynman",
- "version": "0.2.9",
+ "version": "0.2.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@companion-ai/feynman",
- "version": "0.2.9",
+ "version": "0.2.10",
"hasInstallScript": true,
"dependencies": {
"@companion-ai/alpha-hub": "^0.1.2",
diff --git a/package.json b/package.json
index b070820..85a71dc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@companion-ai/feynman",
- "version": "0.2.9",
+ "version": "0.2.10",
"description": "Research-first CLI agent built on Pi and alphaXiv",
"type": "module",
"engines": {
diff --git a/src/cli.ts b/src/cli.ts
index 443b516..6e9b3fb 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -19,6 +19,7 @@ import { launchPiChat } from "./pi/launch.js";
import { CORE_PACKAGE_SOURCES, getOptionalPackagePresetSources, listOptionalPackagePresets } from "./pi/package-presets.js";
import { normalizeFeynmanSettings, normalizeThinkingLevel, parseModelSpec } from "./pi/settings.js";
import {
+ getCurrentModelSpec,
loginModelProvider,
logoutModelProvider,
printModelList,
@@ -424,6 +425,22 @@ export async function main(): Promise {
}
}
+ if (!explicitModelSpec && !getCurrentModelSpec(feynmanSettingsPath) && process.stdin.isTTY && process.stdout.isTTY) {
+ await runSetup({
+ settingsPath: feynmanSettingsPath,
+ bundledSettingsPath,
+ authPath: feynmanAuthPath,
+ workingDir,
+ sessionDir,
+ appRoot,
+ defaultThinkingLevel: thinkingLevel,
+ });
+ if (!getCurrentModelSpec(feynmanSettingsPath)) {
+ return;
+ }
+ normalizeFeynmanSettings(feynmanSettingsPath, bundledSettingsPath, thinkingLevel, feynmanAuthPath);
+ }
+
await launchPiChat({
appRoot,
workingDir,
diff --git a/src/model/commands.ts b/src/model/commands.ts
index 6d8df32..b43b132 100644
--- a/src/model/commands.ts
+++ b/src/model/commands.ts
@@ -191,33 +191,23 @@ export function setDefaultModelSpec(settingsPath: string, authPath: string, spec
}
export async function runModelSetup(settingsPath: string, authPath: string): Promise {
- const status = collectModelStatus(settingsPath, authPath);
+ let status = collectModelStatus(settingsPath, authPath);
if (status.availableModels.length === 0) {
- printWarning("No Pi models are currently authenticated for Feynman.");
- for (const line of status.guidance) {
- printInfo(line);
+ await loginModelProvider(authPath, undefined, settingsPath);
+ status = collectModelStatus(settingsPath, authPath);
+ if (status.availableModels.length === 0) {
+ return;
}
- printInfo("Tip: run `feynman model login ` if your provider supports Pi OAuth login.");
+ }
+
+ if (status.currentValid) {
+ printInfo(`Model: ${status.current}`);
return;
}
- const choices = status.availableModels.map((spec) => {
- const markers = [
- spec === status.recommended ? "recommended" : undefined,
- spec === status.current ? "current" : undefined,
- ].filter(Boolean);
- return `${spec}${markers.length > 0 ? ` (${markers.join(", ")})` : ""}`;
- });
- choices.push(`Keep current (${status.current ?? "unset"})`);
-
- const defaultIndex = status.current ? Math.max(0, status.availableModels.indexOf(status.current)) : 0;
- const selection = await promptChoice("Select your default research model:", choices, defaultIndex >= 0 ? defaultIndex : 0);
-
- if (selection >= status.availableModels.length) {
- printInfo("Skipped (keeping current model)");
- return;
+ const recommended = status.recommended ?? status.availableModels[0];
+ if (recommended) {
+ setDefaultModelSpec(settingsPath, authPath, recommended);
}
-
- setDefaultModelSpec(settingsPath, authPath, status.availableModels[selection]!);
}
diff --git a/src/setup/setup.ts b/src/setup/setup.ts
index e8f3569..aec4d11 100644
--- a/src/setup/setup.ts
+++ b/src/setup/setup.ts
@@ -7,10 +7,9 @@ import type { ThinkingLevel } from "../pi/settings.js";
import { getCurrentModelSpec, runModelSetup } from "../model/commands.js";
import { buildModelStatusSnapshotFromRecords, getAvailableModelRecords, getSupportedModelRecords } from "../model/catalog.js";
import { PANDOC_FALLBACK_PATHS, resolveExecutable } from "../system/executables.js";
-import { promptText } from "./prompts.js";
import { setupPreviewDependencies } from "./preview.js";
import { runDoctor } from "./doctor.js";
-import { printInfo, printPanel, printSection, printSuccess } from "../ui/terminal.js";
+import { printInfo, printSection, printSuccess } from "../ui/terminal.js";
type SetupOptions = {
settingsPath: string;
@@ -22,129 +21,16 @@ type SetupOptions = {
defaultThinkingLevel?: ThinkingLevel;
};
-async function explainWebAccess(): Promise {
- const status = getPiWebAccessStatus();
- printSection("Web Access");
- printInfo("Feynman uses the bundled `pi-web-access` package directly.");
- printInfo("Default v1 path: sign into gemini.google.com in a supported Chromium browser.");
- printInfo(`Current search route: ${status.routeLabel}`);
- printInfo(`Pi config path: ${status.configPath}`);
- printInfo("Advanced users can edit the Pi config directly if they want API keys or a different route.");
-}
-
-function isPreviewConfigured() {
- return Boolean(resolveExecutable("pandoc", PANDOC_FALLBACK_PATHS));
-}
-
function isInteractiveTerminal(): boolean {
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
}
function printNonInteractiveSetupGuidance(): void {
- printPanel("Feynman Setup", [
- "Non-interactive terminal detected.",
- ]);
- printInfo("Use the explicit commands instead of the interactive setup wizard:");
- printInfo(" feynman status");
+ printInfo("Non-interactive terminal. Use explicit commands:");
printInfo(" feynman model login ");
printInfo(" feynman model set ");
- printInfo(" feynman search status");
- printInfo(` edit ${getPiWebSearchConfigPath()} # optional advanced web config`);
printInfo(" feynman alpha login");
printInfo(" feynman doctor");
- printInfo(" feynman # Pi's /login flow still works inside chat if you prefer it");
-}
-
-async function runPreviewSetup(): Promise {
- const result = setupPreviewDependencies();
- printSuccess(result.message);
-}
-
-function printConfigurationLocation(appRoot: string): void {
- printSection("Configuration Location");
- printInfo(`Data folder: ${getFeynmanHome()}`);
- printInfo(`Sessions: ${getDefaultSessionDir()}`);
- printInfo(`Install dir: ${appRoot}`);
-}
-
-function printSetupSummary(settingsPath: string, authPath: string): void {
- const modelStatus = buildModelStatusSnapshotFromRecords(
- getSupportedModelRecords(authPath),
- getAvailableModelRecords(authPath),
- getCurrentModelSpec(settingsPath),
- );
- printSection("Setup Summary");
- printInfo(`Model: ${getCurrentModelSpec(settingsPath) ?? "not set"}`);
- printInfo(`Model valid: ${modelStatus.currentValid ? "yes" : "no"}`);
- printInfo(`Recommended model: ${modelStatus.recommended ?? "not available"}`);
- printInfo(`alphaXiv: ${isAlphaLoggedIn() ? "configured" : "missing"}`);
- printInfo(`Web access: pi-web-access (${getPiWebAccessStatus().routeLabel})`);
- printInfo(`Preview: ${isPreviewConfigured() ? "configured" : "not configured"}`);
- for (const line of modelStatus.guidance) {
- printInfo(line);
- }
-}
-
-async function runFullSetup(options: SetupOptions): Promise {
- printConfigurationLocation(options.appRoot);
- await runModelSetup(options.settingsPath, options.authPath);
- if (!isAlphaLoggedIn()) {
- await loginAlpha();
- printSuccess("alphaXiv login complete");
- } else {
- printInfo("alphaXiv login already configured");
- }
- await explainWebAccess();
- await runPreviewSetup();
- normalizeFeynmanSettings(
- options.settingsPath,
- options.bundledSettingsPath,
- options.defaultThinkingLevel ?? "medium",
- options.authPath,
- );
- runDoctor({
- settingsPath: options.settingsPath,
- authPath: options.authPath,
- sessionDir: options.sessionDir,
- workingDir: options.workingDir,
- appRoot: options.appRoot,
- });
- printSetupSummary(options.settingsPath, options.authPath);
-}
-
-function hasExistingSetup(settingsPath: string, authPath: string): boolean {
- const modelStatus = buildModelStatusSnapshotFromRecords(
- getSupportedModelRecords(authPath),
- getAvailableModelRecords(authPath),
- getCurrentModelSpec(settingsPath),
- );
- return Boolean(
- modelStatus.current ||
- modelStatus.availableModels.length > 0 ||
- isAlphaLoggedIn() ||
- isPreviewConfigured(),
- );
-}
-
-async function runDefaultInteractiveSetup(options: SetupOptions): Promise {
- const existing = hasExistingSetup(options.settingsPath, options.authPath);
- printPanel("Feynman Setup Wizard", [
- "Guided setup for the research-first Pi agent.",
- "Press Ctrl+C at any time to exit.",
- ]);
-
- if (existing) {
- printSection("Full Setup");
- printInfo("Existing configuration detected. Rerunning the full guided setup.");
- } else {
- printInfo("We'll walk you through:");
- printInfo(" 1. Model Selection");
- printInfo(" 2. alphaXiv Login");
- printInfo(" 3. Preview Dependencies");
- }
- printInfo("Press Enter to begin, or Ctrl+C to exit.");
- await promptText("Press Enter to start");
- await runFullSetup(options);
}
export async function runSetup(options: SetupOptions): Promise {
@@ -153,5 +39,31 @@ export async function runSetup(options: SetupOptions): Promise {
return;
}
- await runDefaultInteractiveSetup(options);
+ await runModelSetup(options.settingsPath, options.authPath);
+
+ if (!isAlphaLoggedIn()) {
+ await loginAlpha();
+ printSuccess("alphaXiv login complete");
+ }
+
+ const result = setupPreviewDependencies();
+ printSuccess(result.message);
+
+ normalizeFeynmanSettings(
+ options.settingsPath,
+ options.bundledSettingsPath,
+ options.defaultThinkingLevel ?? "medium",
+ options.authPath,
+ );
+
+ const modelStatus = buildModelStatusSnapshotFromRecords(
+ getSupportedModelRecords(options.authPath),
+ getAvailableModelRecords(options.authPath),
+ getCurrentModelSpec(options.settingsPath),
+ );
+ printSection("Ready");
+ printInfo(`Model: ${getCurrentModelSpec(options.settingsPath) ?? "not set"}`);
+ printInfo(`alphaXiv: ${isAlphaLoggedIn() ? "configured" : "not configured"}`);
+ printInfo(`Preview: ${resolveExecutable("pandoc", PANDOC_FALLBACK_PATHS) ? "configured" : "not configured"}`);
+ printInfo(`Web: ${getPiWebAccessStatus().routeLabel}`);
}