Fix Feynman onboarding and local install reliability

This commit is contained in:
Advait Paliwal
2026-04-15 13:46:12 -07:00
parent fa259f5cea
commit dd3c07633b
11 changed files with 512 additions and 123 deletions

View File

@@ -1,6 +1,6 @@
import "dotenv/config";
import { readFileSync } from "node:fs";
import { existsSync, readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { parseArgs } from "node:util";
import { fileURLToPath } from "node:url";
@@ -11,11 +11,13 @@ import {
login as loginAlpha,
logout as logoutAlpha,
} from "@companion-ai/alpha-hub/lib";
import { DefaultPackageManager, SettingsManager } from "@mariozechner/pi-coding-agent";
import { SettingsManager } from "@mariozechner/pi-coding-agent";
import { syncBundledAssets } from "./bootstrap/sync.js";
import { ensureFeynmanHome, getDefaultSessionDir, getFeynmanAgentDir, getFeynmanHome } from "./config/paths.js";
import { launchPiChat } from "./pi/launch.js";
import { installPackageSources, updateConfiguredPackages } from "./pi/package-ops.js";
import { MAX_NATIVE_PACKAGE_NODE_MAJOR } from "./pi/package-presets.js";
import { CORE_PACKAGE_SOURCES, getOptionalPackagePresetSources, listOptionalPackagePresets } from "./pi/package-presets.js";
import { normalizeFeynmanSettings, normalizeThinkingLevel, parseModelSpec } from "./pi/settings.js";
import { applyFeynmanPackageManagerEnv } from "./pi/runtime.js";
@@ -28,6 +30,7 @@ import {
printModelList,
setDefaultModelSpec,
} from "./model/commands.js";
import { buildModelStatusSnapshotFromRecords, getAvailableModelRecords, getSupportedModelRecords } from "./model/catalog.js";
import { clearSearchConfig, printSearchStatus, setSearchProvider } from "./search/commands.js";
import type { PiWebSearchProvider } from "./pi/web-access.js";
import { runDoctor, runStatus } from "./setup/doctor.js";
@@ -180,27 +183,30 @@ async function handleModelCommand(subcommand: string | undefined, args: string[]
}
async function handleUpdateCommand(workingDir: string, feynmanAgentDir: string, source?: string): Promise<void> {
applyFeynmanPackageManagerEnv(feynmanAgentDir);
const settingsManager = SettingsManager.create(workingDir, feynmanAgentDir);
const packageManager = new DefaultPackageManager({
cwd: workingDir,
agentDir: feynmanAgentDir,
settingsManager,
});
packageManager.setProgressCallback((event) => {
if (event.type === "start") {
console.log(`Updating ${event.source}...`);
} else if (event.type === "complete") {
console.log(`Updated ${event.source}`);
} else if (event.type === "error") {
console.error(`Failed to update ${event.source}: ${event.message ?? "unknown error"}`);
try {
const result = await updateConfiguredPackages(workingDir, feynmanAgentDir, source);
if (result.updated.length === 0) {
console.log("All packages up to date.");
return;
}
});
await packageManager.update(source);
await settingsManager.flush();
console.log("All packages up to date.");
for (const updatedSource of result.updated) {
console.log(`Updated ${updatedSource}`);
}
for (const skippedSource of result.skipped) {
console.log(`Skipped ${skippedSource} on Node ${process.versions.node} (native packages are only supported through Node ${MAX_NATIVE_PACKAGE_NODE_MAJOR}.x).`);
}
console.log("All packages up to date.");
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (message.includes("No supported package manager found")) {
console.log("No package manager is available for live package updates.");
console.log("If you installed the standalone app, rerun the installer to get newer bundled packages.");
return;
}
throw error;
}
}
async function handlePackagesCommand(subcommand: string | undefined, args: string[], workingDir: string, feynmanAgentDir: string): Promise<void> {
@@ -244,30 +250,44 @@ async function handlePackagesCommand(subcommand: string | undefined, args: strin
throw new Error(`Unknown package preset: ${target}`);
}
const packageManager = new DefaultPackageManager({
cwd: workingDir,
agentDir: feynmanAgentDir,
settingsManager,
});
packageManager.setProgressCallback((event) => {
if (event.type === "start") {
console.log(`Installing ${event.source}...`);
} else if (event.type === "complete") {
console.log(`Installed ${event.source}`);
} else if (event.type === "error") {
console.error(`Failed to install ${event.source}: ${event.message ?? "unknown error"}`);
}
});
const appRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const isStandaloneBundle = !existsSync(resolve(appRoot, ".feynman", "runtime-workspace.tgz")) && existsSync(resolve(appRoot, ".feynman", "npm"));
if (target === "generative-ui" && process.platform === "darwin" && isStandaloneBundle) {
console.log("The generative-ui preset is currently unavailable in the standalone macOS bundle.");
console.log("Its native glimpseui dependency fails to compile reliably in that environment.");
console.log("If you need generative-ui, install Feynman through npm instead of the standalone bundle.");
return;
}
const pendingSources = sources.filter((source) => !configuredSources.has(source));
for (const source of sources) {
if (configuredSources.has(source)) {
console.log(`${source} already installed`);
continue;
}
await packageManager.install(source);
}
await settingsManager.flush();
console.log("Optional packages installed.");
if (pendingSources.length === 0) {
console.log("Optional packages installed.");
return;
}
try {
const result = await installPackageSources(workingDir, feynmanAgentDir, pendingSources, { persist: true });
for (const skippedSource of result.skipped) {
console.log(`Skipped ${skippedSource} on Node ${process.versions.node} (native packages are only supported through Node ${MAX_NATIVE_PACKAGE_NODE_MAJOR}.x).`);
}
await settingsManager.flush();
console.log("Optional packages installed.");
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (message.includes("No supported package manager found")) {
console.log("No package manager is available for optional package installs.");
console.log("Install npm, pnpm, or bun, or rerun the standalone installer for bundled package updates.");
return;
}
throw error;
}
}
function handleSearchCommand(subcommand: string | undefined, args: string[]): void {
@@ -326,6 +346,24 @@ export function resolveInitialPrompt(
return undefined;
}
export function shouldRunInteractiveSetup(
explicitModelSpec: string | undefined,
currentModelSpec: string | undefined,
isInteractiveTerminal: boolean,
authPath: string,
): boolean {
if (explicitModelSpec || !isInteractiveTerminal) {
return false;
}
const status = buildModelStatusSnapshotFromRecords(
getSupportedModelRecords(authPath),
getAvailableModelRecords(authPath),
currentModelSpec,
);
return !status.currentValid;
}
export async function main(): Promise<void> {
const here = dirname(fileURLToPath(import.meta.url));
const appRoot = resolve(here, "..");
@@ -498,7 +536,13 @@ export async function main(): Promise<void> {
}
}
if (!explicitModelSpec && !getCurrentModelSpec(feynmanSettingsPath) && process.stdin.isTTY && process.stdout.isTTY) {
const currentModelSpec = getCurrentModelSpec(feynmanSettingsPath);
if (shouldRunInteractiveSetup(
explicitModelSpec,
currentModelSpec,
Boolean(process.stdin.isTTY && process.stdout.isTTY),
feynmanAuthPath,
)) {
await runSetup({
settingsPath: feynmanSettingsPath,
bundledSettingsPath,