Polish Feynman harness and stabilize Pi web runtime
This commit is contained in:
180
src/setup/doctor.ts
Normal file
180
src/setup/doctor.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
|
||||
import { getUserName as getAlphaUserName, isLoggedIn as isAlphaLoggedIn } from "@companion-ai/alpha-hub/lib";
|
||||
|
||||
import {
|
||||
FEYNMAN_CONFIG_PATH,
|
||||
formatWebSearchDoctorLines,
|
||||
getWebSearchStatus,
|
||||
loadFeynmanConfig,
|
||||
} from "../config/feynman-config.js";
|
||||
import { BROWSER_FALLBACK_PATHS, PANDOC_FALLBACK_PATHS, resolveExecutable } from "../system/executables.js";
|
||||
import { readJson } from "../pi/settings.js";
|
||||
import { validatePiInstallation } from "../pi/runtime.js";
|
||||
import { printInfo, printPanel, printSection } from "../ui/terminal.js";
|
||||
import { getCurrentModelSpec } from "../model/commands.js";
|
||||
import { buildModelStatusSnapshotFromRecords, getAvailableModelRecords, getSupportedModelRecords } from "../model/catalog.js";
|
||||
|
||||
export type DoctorOptions = {
|
||||
settingsPath: string;
|
||||
authPath: string;
|
||||
sessionDir: string;
|
||||
workingDir: string;
|
||||
appRoot: string;
|
||||
};
|
||||
|
||||
export type FeynmanStatusSnapshot = {
|
||||
model?: string;
|
||||
modelValid: boolean;
|
||||
recommendedModel?: string;
|
||||
recommendedModelReason?: string;
|
||||
authenticatedModelCount: number;
|
||||
authenticatedProviderCount: number;
|
||||
modelGuidance: string[];
|
||||
alphaLoggedIn: boolean;
|
||||
alphaUser?: string;
|
||||
webProviderLabel: string;
|
||||
webConfigured: boolean;
|
||||
previewConfigured: boolean;
|
||||
sessionDir: string;
|
||||
configPath: string;
|
||||
pandocReady: boolean;
|
||||
browserReady: boolean;
|
||||
piReady: boolean;
|
||||
missingPiBits: string[];
|
||||
};
|
||||
|
||||
export function collectStatusSnapshot(options: DoctorOptions): FeynmanStatusSnapshot {
|
||||
const config = loadFeynmanConfig();
|
||||
const pandocPath = resolveExecutable("pandoc", PANDOC_FALLBACK_PATHS);
|
||||
const browserPath = process.env.PUPPETEER_EXECUTABLE_PATH ?? resolveExecutable("google-chrome", BROWSER_FALLBACK_PATHS);
|
||||
const missingPiBits = validatePiInstallation(options.appRoot);
|
||||
const webStatus = getWebSearchStatus(config.webSearch ?? {});
|
||||
const modelStatus = buildModelStatusSnapshotFromRecords(
|
||||
getSupportedModelRecords(options.authPath),
|
||||
getAvailableModelRecords(options.authPath),
|
||||
getCurrentModelSpec(options.settingsPath),
|
||||
);
|
||||
|
||||
return {
|
||||
model: modelStatus.current,
|
||||
modelValid: modelStatus.currentValid,
|
||||
recommendedModel: modelStatus.recommended,
|
||||
recommendedModelReason: modelStatus.recommendationReason,
|
||||
authenticatedModelCount: modelStatus.availableModels.length,
|
||||
authenticatedProviderCount: modelStatus.providers.filter((provider) => provider.configured).length,
|
||||
modelGuidance: modelStatus.guidance,
|
||||
alphaLoggedIn: isAlphaLoggedIn(),
|
||||
alphaUser: isAlphaLoggedIn() ? getAlphaUserName() ?? undefined : undefined,
|
||||
webProviderLabel: webStatus.selected.label,
|
||||
webConfigured: webStatus.perplexityConfigured || webStatus.geminiApiConfigured || webStatus.selected.id === "gemini-browser",
|
||||
previewConfigured: Boolean(config.preview?.lastSetupAt),
|
||||
sessionDir: options.sessionDir,
|
||||
configPath: FEYNMAN_CONFIG_PATH,
|
||||
pandocReady: Boolean(pandocPath),
|
||||
browserReady: Boolean(browserPath),
|
||||
piReady: missingPiBits.length === 0,
|
||||
missingPiBits,
|
||||
};
|
||||
}
|
||||
|
||||
export function runStatus(options: DoctorOptions): void {
|
||||
const snapshot = collectStatusSnapshot(options);
|
||||
printPanel("Feynman Status", [
|
||||
"Current setup summary for the research shell.",
|
||||
]);
|
||||
printSection("Core");
|
||||
printInfo(`Model: ${snapshot.model ?? "not configured"}`);
|
||||
printInfo(`Model valid: ${snapshot.modelValid ? "yes" : "no"}`);
|
||||
printInfo(`Authenticated models: ${snapshot.authenticatedModelCount}`);
|
||||
printInfo(`Authenticated providers: ${snapshot.authenticatedProviderCount}`);
|
||||
printInfo(`Recommended model: ${snapshot.recommendedModel ?? "not available"}`);
|
||||
printInfo(`alphaXiv: ${snapshot.alphaLoggedIn ? snapshot.alphaUser ?? "configured" : "not configured"}`);
|
||||
printInfo(`Web research: ${snapshot.webConfigured ? snapshot.webProviderLabel : "not configured"}`);
|
||||
printInfo(`Preview: ${snapshot.previewConfigured ? "configured" : "not configured"}`);
|
||||
|
||||
printSection("Paths");
|
||||
printInfo(`Config: ${snapshot.configPath}`);
|
||||
printInfo(`Sessions: ${snapshot.sessionDir}`);
|
||||
|
||||
printSection("Runtime");
|
||||
printInfo(`Pi runtime: ${snapshot.piReady ? "ready" : "missing files"}`);
|
||||
printInfo(`Pandoc: ${snapshot.pandocReady ? "ready" : "missing"}`);
|
||||
printInfo(`Browser preview: ${snapshot.browserReady ? "ready" : "missing"}`);
|
||||
if (snapshot.missingPiBits.length > 0) {
|
||||
for (const entry of snapshot.missingPiBits) {
|
||||
printInfo(` missing: ${entry}`);
|
||||
}
|
||||
}
|
||||
if (snapshot.modelGuidance.length > 0) {
|
||||
printSection("Next Steps");
|
||||
for (const line of snapshot.modelGuidance) {
|
||||
printInfo(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function runDoctor(options: DoctorOptions): void {
|
||||
const settings = readJson(options.settingsPath);
|
||||
const config = loadFeynmanConfig();
|
||||
const modelRegistry = new ModelRegistry(AuthStorage.create(options.authPath));
|
||||
const availableModels = modelRegistry.getAvailable();
|
||||
const pandocPath = resolveExecutable("pandoc", PANDOC_FALLBACK_PATHS);
|
||||
const browserPath = process.env.PUPPETEER_EXECUTABLE_PATH ?? resolveExecutable("google-chrome", BROWSER_FALLBACK_PATHS);
|
||||
const missingPiBits = validatePiInstallation(options.appRoot);
|
||||
|
||||
printPanel("Feynman Doctor", [
|
||||
"Checks config, auth, runtime wiring, and preview dependencies.",
|
||||
]);
|
||||
console.log(`working dir: ${options.workingDir}`);
|
||||
console.log(`session dir: ${options.sessionDir}`);
|
||||
console.log(`config path: ${FEYNMAN_CONFIG_PATH}`);
|
||||
console.log("");
|
||||
console.log(`alphaXiv auth: ${isAlphaLoggedIn() ? "ok" : "missing"}`);
|
||||
if (isAlphaLoggedIn()) {
|
||||
const name = getAlphaUserName();
|
||||
if (name) {
|
||||
console.log(` user: ${name}`);
|
||||
}
|
||||
}
|
||||
console.log(`models available: ${availableModels.length}`);
|
||||
if (availableModels.length > 0) {
|
||||
const sample = availableModels
|
||||
.slice(0, 6)
|
||||
.map((model) => `${model.provider}/${model.id}`)
|
||||
.join(", ");
|
||||
console.log(` sample: ${sample}`);
|
||||
}
|
||||
console.log(
|
||||
`default model: ${typeof settings.defaultProvider === "string" && typeof settings.defaultModel === "string"
|
||||
? `${settings.defaultProvider}/${settings.defaultModel}`
|
||||
: "not set"}`,
|
||||
);
|
||||
const modelStatus = collectStatusSnapshot(options);
|
||||
console.log(`default model valid: ${modelStatus.modelValid ? "yes" : "no"}`);
|
||||
console.log(`authenticated providers: ${modelStatus.authenticatedProviderCount}`);
|
||||
console.log(`authenticated models: ${modelStatus.authenticatedModelCount}`);
|
||||
console.log(`recommended model: ${modelStatus.recommendedModel ?? "not available"}`);
|
||||
if (modelStatus.recommendedModelReason) {
|
||||
console.log(` why: ${modelStatus.recommendedModelReason}`);
|
||||
}
|
||||
console.log(`pandoc: ${pandocPath ?? "missing"}`);
|
||||
console.log(`browser preview runtime: ${browserPath ?? "missing"}`);
|
||||
console.log(`configured session dir: ${config.sessionDir ?? "not set"}`);
|
||||
for (const line of formatWebSearchDoctorLines(config.webSearch ?? {})) {
|
||||
console.log(line);
|
||||
}
|
||||
console.log(`quiet startup: ${settings.quietStartup === true ? "enabled" : "disabled"}`);
|
||||
console.log(`theme: ${typeof settings.theme === "string" ? settings.theme : "not set"}`);
|
||||
if (missingPiBits.length > 0) {
|
||||
console.log("pi runtime: missing files");
|
||||
for (const entry of missingPiBits) {
|
||||
console.log(` ${entry}`);
|
||||
}
|
||||
} else {
|
||||
console.log("pi runtime: ok");
|
||||
}
|
||||
for (const line of modelStatus.modelGuidance) {
|
||||
console.log(`next step: ${line}`);
|
||||
}
|
||||
console.log("setup hint: feynman setup");
|
||||
}
|
||||
29
src/setup/preview.ts
Normal file
29
src/setup/preview.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
import { BREW_FALLBACK_PATHS, PANDOC_FALLBACK_PATHS, resolveExecutable } from "../system/executables.js";
|
||||
|
||||
export type PreviewSetupResult =
|
||||
| { status: "ready"; message: string }
|
||||
| { status: "installed"; message: string }
|
||||
| { status: "manual"; message: string };
|
||||
|
||||
export function setupPreviewDependencies(): PreviewSetupResult {
|
||||
const pandocPath = resolveExecutable("pandoc", PANDOC_FALLBACK_PATHS);
|
||||
if (pandocPath) {
|
||||
return { status: "ready", message: `pandoc already installed at ${pandocPath}` };
|
||||
}
|
||||
|
||||
const brewPath = resolveExecutable("brew", BREW_FALLBACK_PATHS);
|
||||
if (process.platform === "darwin" && brewPath) {
|
||||
const result = spawnSync(brewPath, ["install", "pandoc"], { stdio: "inherit" });
|
||||
if (result.status !== 0) {
|
||||
throw new Error("Failed to install pandoc via Homebrew.");
|
||||
}
|
||||
return { status: "installed", message: "Preview dependency installed: pandoc" };
|
||||
}
|
||||
|
||||
return {
|
||||
status: "manual",
|
||||
message: "pandoc is required for preview support. Install it manually and rerun `feynman --doctor`.",
|
||||
};
|
||||
}
|
||||
30
src/setup/prompts.ts
Normal file
30
src/setup/prompts.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { stdin as input, stdout as output } from "node:process";
|
||||
import { createInterface } from "node:readline/promises";
|
||||
|
||||
export async function promptText(question: string, defaultValue = ""): Promise<string> {
|
||||
if (!input.isTTY || !output.isTTY) {
|
||||
throw new Error("feynman setup requires an interactive terminal.");
|
||||
}
|
||||
const rl = createInterface({ input, output });
|
||||
try {
|
||||
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
||||
const value = (await rl.question(`${question}${suffix}: `)).trim();
|
||||
return value || defaultValue;
|
||||
} finally {
|
||||
rl.close();
|
||||
}
|
||||
}
|
||||
|
||||
export async function promptChoice(question: string, choices: string[], defaultIndex = 0): Promise<number> {
|
||||
console.log(question);
|
||||
for (const [index, choice] of choices.entries()) {
|
||||
const marker = index === defaultIndex ? "*" : " ";
|
||||
console.log(` ${marker} ${index + 1}. ${choice}`);
|
||||
}
|
||||
const answer = await promptText("Select", String(defaultIndex + 1));
|
||||
const parsed = Number(answer);
|
||||
if (!Number.isFinite(parsed) || parsed < 1 || parsed > choices.length) {
|
||||
return defaultIndex;
|
||||
}
|
||||
return parsed - 1;
|
||||
}
|
||||
316
src/setup/setup.ts
Normal file
316
src/setup/setup.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
import { isLoggedIn as isAlphaLoggedIn, login as loginAlpha } from "@companion-ai/alpha-hub/lib";
|
||||
|
||||
import {
|
||||
DEFAULT_WEB_SEARCH_PROVIDER,
|
||||
FEYNMAN_CONFIG_PATH,
|
||||
WEB_SEARCH_PROVIDERS,
|
||||
configureWebSearchProvider,
|
||||
getConfiguredWebSearchProvider,
|
||||
getWebSearchStatus,
|
||||
hasConfiguredWebProvider,
|
||||
loadFeynmanConfig,
|
||||
saveFeynmanConfig,
|
||||
} from "../config/feynman-config.js";
|
||||
import { getFeynmanHome } from "../config/paths.js";
|
||||
import { normalizeFeynmanSettings } from "../pi/settings.js";
|
||||
import type { ThinkingLevel } from "../pi/settings.js";
|
||||
import { getCurrentModelSpec, runModelSetup } from "../model/commands.js";
|
||||
import { buildModelStatusSnapshotFromRecords, getAvailableModelRecords, getSupportedModelRecords } from "../model/catalog.js";
|
||||
import { promptChoice, promptText } from "./prompts.js";
|
||||
import { setupPreviewDependencies } from "./preview.js";
|
||||
import { runDoctor } from "./doctor.js";
|
||||
import { printInfo, printPanel, printSection, printSuccess } from "../ui/terminal.js";
|
||||
|
||||
type SetupOptions = {
|
||||
section: string | undefined;
|
||||
settingsPath: string;
|
||||
bundledSettingsPath: string;
|
||||
authPath: string;
|
||||
workingDir: string;
|
||||
sessionDir: string;
|
||||
appRoot: string;
|
||||
defaultThinkingLevel?: ThinkingLevel;
|
||||
};
|
||||
|
||||
async function setupWebProvider(): Promise<void> {
|
||||
const config = loadFeynmanConfig();
|
||||
const current = getConfiguredWebSearchProvider(config.webSearch ?? {});
|
||||
const preferredSelectionId = config.webSearch?.feynmanWebProvider ?? DEFAULT_WEB_SEARCH_PROVIDER;
|
||||
const choices = [
|
||||
...WEB_SEARCH_PROVIDERS.map((provider) => `${provider.label} — ${provider.description}`),
|
||||
"Skip",
|
||||
];
|
||||
const defaultIndex = WEB_SEARCH_PROVIDERS.findIndex((provider) => provider.id === preferredSelectionId);
|
||||
const selection = await promptChoice(
|
||||
"Choose a web search provider for Feynman:",
|
||||
choices,
|
||||
defaultIndex >= 0 ? defaultIndex : 0,
|
||||
);
|
||||
|
||||
if (selection === WEB_SEARCH_PROVIDERS.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selected = WEB_SEARCH_PROVIDERS[selection] ?? WEB_SEARCH_PROVIDERS[0];
|
||||
let nextWebConfig = { ...(config.webSearch ?? {}) };
|
||||
|
||||
if (selected.id === "perplexity") {
|
||||
const key = await promptText(
|
||||
"Perplexity API key",
|
||||
typeof nextWebConfig.perplexityApiKey === "string" ? nextWebConfig.perplexityApiKey : "",
|
||||
);
|
||||
nextWebConfig = configureWebSearchProvider(nextWebConfig, selected.id, { apiKey: key });
|
||||
} else if (selected.id === "gemini-api") {
|
||||
const key = await promptText(
|
||||
"Gemini API key",
|
||||
typeof nextWebConfig.geminiApiKey === "string" ? nextWebConfig.geminiApiKey : "",
|
||||
);
|
||||
nextWebConfig = configureWebSearchProvider(nextWebConfig, selected.id, { apiKey: key });
|
||||
} else if (selected.id === "gemini-browser") {
|
||||
const profile = await promptText(
|
||||
"Chrome profile (optional)",
|
||||
typeof nextWebConfig.chromeProfile === "string" ? nextWebConfig.chromeProfile : "",
|
||||
);
|
||||
nextWebConfig = configureWebSearchProvider(nextWebConfig, selected.id, { chromeProfile: profile });
|
||||
} else {
|
||||
nextWebConfig = configureWebSearchProvider(nextWebConfig, selected.id);
|
||||
}
|
||||
|
||||
saveFeynmanConfig({
|
||||
...config,
|
||||
webSearch: nextWebConfig,
|
||||
});
|
||||
printSuccess(`Saved web search provider: ${selected.label}`);
|
||||
if (selected.id === "gemini-browser") {
|
||||
printInfo("Gemini Browser relies on a signed-in Chromium profile through pi-web-access.");
|
||||
}
|
||||
}
|
||||
|
||||
function isPreviewConfigured() {
|
||||
return Boolean(loadFeynmanConfig().preview?.lastSetupAt);
|
||||
}
|
||||
|
||||
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(" feynman model providers");
|
||||
printInfo(" feynman model login <provider>");
|
||||
printInfo(" feynman model list");
|
||||
printInfo(" feynman model recommend");
|
||||
printInfo(" feynman model set <provider/model>");
|
||||
printInfo(" feynman search providers");
|
||||
printInfo(" feynman search set <provider> [value]");
|
||||
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<void> {
|
||||
const result = setupPreviewDependencies();
|
||||
printSuccess(result.message);
|
||||
saveFeynmanConfig({
|
||||
...loadFeynmanConfig(),
|
||||
preview: {
|
||||
lastSetupAt: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function printConfigurationLocation(appRoot: string): void {
|
||||
printSection("Configuration Location");
|
||||
printInfo(`Config file: ${FEYNMAN_CONFIG_PATH}`);
|
||||
printInfo(`Data folder: ${getFeynmanHome()}`);
|
||||
printInfo(`Install dir: ${appRoot}`);
|
||||
printInfo("You can edit config.json directly or use `feynman config` commands.");
|
||||
}
|
||||
|
||||
function printSetupSummary(settingsPath: string, authPath: string): void {
|
||||
const config = loadFeynmanConfig();
|
||||
const webStatus = getWebSearchStatus(config.webSearch ?? {});
|
||||
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 research: ${hasConfiguredWebProvider(config.webSearch ?? {}) ? webStatus.selected.label : "not configured"}`);
|
||||
printInfo(`Preview: ${isPreviewConfigured() ? "configured" : "not configured"}`);
|
||||
for (const line of modelStatus.guidance) {
|
||||
printInfo(line);
|
||||
}
|
||||
}
|
||||
|
||||
async function runSetupSection(section: "model" | "alpha" | "web" | "preview", options: SetupOptions): Promise<void> {
|
||||
if (section === "model") {
|
||||
await runModelSetup(options.settingsPath, options.authPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (section === "alpha") {
|
||||
if (!isAlphaLoggedIn()) {
|
||||
await loginAlpha();
|
||||
printSuccess("alphaXiv login complete");
|
||||
} else {
|
||||
printInfo("alphaXiv login already configured");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (section === "web") {
|
||||
await setupWebProvider();
|
||||
return;
|
||||
}
|
||||
|
||||
if (section === "preview") {
|
||||
await runPreviewSetup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function runFullSetup(options: SetupOptions): Promise<void> {
|
||||
printConfigurationLocation(options.appRoot);
|
||||
await runSetupSection("model", options);
|
||||
await runSetupSection("alpha", options);
|
||||
await runSetupSection("web", options);
|
||||
await runSetupSection("preview", options);
|
||||
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);
|
||||
}
|
||||
|
||||
async function runQuickSetup(options: SetupOptions): Promise<void> {
|
||||
printSection("Quick Setup");
|
||||
let changed = false;
|
||||
const modelStatus = buildModelStatusSnapshotFromRecords(
|
||||
getSupportedModelRecords(options.authPath),
|
||||
getAvailableModelRecords(options.authPath),
|
||||
getCurrentModelSpec(options.settingsPath),
|
||||
);
|
||||
|
||||
if (!modelStatus.current || !modelStatus.currentValid) {
|
||||
await runSetupSection("model", options);
|
||||
changed = true;
|
||||
}
|
||||
if (!isAlphaLoggedIn()) {
|
||||
await runSetupSection("alpha", options);
|
||||
changed = true;
|
||||
}
|
||||
if (!hasConfiguredWebProvider(loadFeynmanConfig().webSearch ?? {})) {
|
||||
await runSetupSection("web", options);
|
||||
changed = true;
|
||||
}
|
||||
if (!isPreviewConfigured()) {
|
||||
await runSetupSection("preview", options);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
printSuccess("Everything already looks configured.");
|
||||
printInfo("Run `feynman setup` and choose Full Setup if you want to reconfigure everything.");
|
||||
return;
|
||||
}
|
||||
|
||||
normalizeFeynmanSettings(
|
||||
options.settingsPath,
|
||||
options.bundledSettingsPath,
|
||||
options.defaultThinkingLevel ?? "medium",
|
||||
options.authPath,
|
||||
);
|
||||
printSetupSummary(options.settingsPath, options.authPath);
|
||||
}
|
||||
|
||||
function hasExistingSetup(settingsPath: string, authPath: string): boolean {
|
||||
const config = loadFeynmanConfig();
|
||||
const modelStatus = buildModelStatusSnapshotFromRecords(
|
||||
getSupportedModelRecords(authPath),
|
||||
getAvailableModelRecords(authPath),
|
||||
getCurrentModelSpec(settingsPath),
|
||||
);
|
||||
return Boolean(
|
||||
modelStatus.current ||
|
||||
modelStatus.availableModels.length > 0 ||
|
||||
isAlphaLoggedIn() ||
|
||||
hasConfiguredWebProvider(config.webSearch ?? {}) ||
|
||||
config.preview?.lastSetupAt,
|
||||
);
|
||||
}
|
||||
|
||||
async function runDefaultInteractiveSetup(options: SetupOptions): Promise<void> {
|
||||
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.");
|
||||
printInfo("Use `feynman setup quick` if you only want to fill missing items.");
|
||||
} else {
|
||||
printInfo("We'll walk you through:");
|
||||
printInfo(" 1. Model Selection");
|
||||
printInfo(" 2. alphaXiv Login");
|
||||
printInfo(" 3. Web Research Provider");
|
||||
printInfo(" 4. 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<void> {
|
||||
if (!isInteractiveTerminal()) {
|
||||
printNonInteractiveSetupGuidance();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!options.section) {
|
||||
await runDefaultInteractiveSetup(options);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.section === "model") {
|
||||
await runSetupSection("model", options);
|
||||
return;
|
||||
}
|
||||
if (options.section === "alpha") {
|
||||
await runSetupSection("alpha", options);
|
||||
return;
|
||||
}
|
||||
if (options.section === "web") {
|
||||
await runSetupSection("web", options);
|
||||
return;
|
||||
}
|
||||
if (options.section === "preview") {
|
||||
await runSetupSection("preview", options);
|
||||
return;
|
||||
}
|
||||
if (options.section === "quick") {
|
||||
await runQuickSetup(options);
|
||||
return;
|
||||
}
|
||||
|
||||
await runFullSetup(options);
|
||||
}
|
||||
Reference in New Issue
Block a user