Commit guided setup and clack dependency updates

This commit is contained in:
Advait Paliwal
2026-04-15 17:58:10 -07:00
parent 8fade18b98
commit d5b6f9cd00
3 changed files with 236 additions and 33 deletions

53
package-lock.json generated
View File

@@ -10,6 +10,7 @@
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@clack/prompts": "^1.2.0",
"@companion-ai/alpha-hub": "^0.1.3", "@companion-ai/alpha-hub": "^0.1.3",
"@mariozechner/pi-ai": "^0.66.1", "@mariozechner/pi-ai": "^0.66.1",
"@mariozechner/pi-coding-agent": "^0.66.1", "@mariozechner/pi-coding-agent": "^0.66.1",
@@ -780,6 +781,28 @@
"url": "https://github.com/sponsors/Borewit" "url": "https://github.com/sponsors/Borewit"
} }
}, },
"node_modules/@clack/core": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@clack/core/-/core-1.2.0.tgz",
"integrity": "sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg==",
"license": "MIT",
"dependencies": {
"fast-wrap-ansi": "^0.1.3",
"sisteransi": "^1.0.5"
}
},
"node_modules/@clack/prompts": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.2.0.tgz",
"integrity": "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w==",
"license": "MIT",
"dependencies": {
"@clack/core": "1.2.0",
"fast-string-width": "^1.1.0",
"fast-wrap-ansi": "^0.1.3",
"sisteransi": "^1.0.5"
}
},
"node_modules/@companion-ai/alpha-hub": { "node_modules/@companion-ai/alpha-hub": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@companion-ai/alpha-hub/-/alpha-hub-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@companion-ai/alpha-hub/-/alpha-hub-0.1.3.tgz",
@@ -3206,6 +3229,21 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-string-truncated-width": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-1.2.1.tgz",
"integrity": "sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow==",
"license": "MIT"
},
"node_modules/fast-string-width": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-1.1.0.tgz",
"integrity": "sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ==",
"license": "MIT",
"dependencies": {
"fast-string-truncated-width": "^1.2.0"
}
},
"node_modules/fast-uri": { "node_modules/fast-uri": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
@@ -3222,6 +3260,15 @@
], ],
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/fast-wrap-ansi": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.1.6.tgz",
"integrity": "sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w==",
"license": "MIT",
"dependencies": {
"fast-string-width": "^1.1.0"
}
},
"node_modules/fast-xml-builder": { "node_modules/fast-xml-builder": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz",
@@ -4611,6 +4658,12 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
"license": "MIT"
},
"node_modules/smart-buffer": { "node_modules/smart-buffer": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",

View File

@@ -5,7 +5,7 @@
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",
"engines": { "engines": {
"node": ">=20.19.0" "node": ">=20.19.0 <25"
}, },
"bin": { "bin": {
"feynman": "bin/feynman.js" "feynman": "bin/feynman.js"
@@ -59,6 +59,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@clack/prompts": "^1.2.0",
"@companion-ai/alpha-hub": "^0.1.3", "@companion-ai/alpha-hub": "^0.1.3",
"@mariozechner/pi-ai": "^0.66.1", "@mariozechner/pi-ai": "^0.66.1",
"@mariozechner/pi-coding-agent": "^0.66.1", "@mariozechner/pi-coding-agent": "^0.66.1",

View File

@@ -1,15 +1,24 @@
import { isLoggedIn as isAlphaLoggedIn, login as loginAlpha } from "@companion-ai/alpha-hub/lib"; import { isLoggedIn as isAlphaLoggedIn, login as loginAlpha } from "@companion-ai/alpha-hub/lib";
import { dirname } from "node:path";
import { getDefaultSessionDir, getFeynmanHome } from "../config/paths.js"; import { getPiWebAccessStatus } from "../pi/web-access.js";
import { getPiWebAccessStatus, getPiWebSearchConfigPath } from "../pi/web-access.js";
import { normalizeFeynmanSettings } from "../pi/settings.js"; import { normalizeFeynmanSettings } from "../pi/settings.js";
import type { ThinkingLevel } from "../pi/settings.js"; import type { ThinkingLevel } from "../pi/settings.js";
import { getMissingConfiguredPackages, installPackageSources } from "../pi/package-ops.js";
import { listOptionalPackagePresets } from "../pi/package-presets.js";
import { getCurrentModelSpec, runModelSetup } from "../model/commands.js"; import { getCurrentModelSpec, runModelSetup } from "../model/commands.js";
import { buildModelStatusSnapshotFromRecords, getAvailableModelRecords, getSupportedModelRecords } from "../model/catalog.js"; import { buildModelStatusSnapshotFromRecords, getAvailableModelRecords, getSupportedModelRecords } from "../model/catalog.js";
import { PANDOC_FALLBACK_PATHS, resolveExecutable } from "../system/executables.js"; import { PANDOC_FALLBACK_PATHS, resolveExecutable } from "../system/executables.js";
import { setupPreviewDependencies } from "./preview.js"; import { setupPreviewDependencies } from "./preview.js";
import { runDoctor } from "./doctor.js";
import { printInfo, printSection, printSuccess } from "../ui/terminal.js"; import { printInfo, printSection, printSuccess } from "../ui/terminal.js";
import {
isInteractiveTerminal,
promptConfirm,
promptIntro,
promptMultiSelect,
promptOutro,
SetupCancelledError,
} from "./prompts.js";
type SetupOptions = { type SetupOptions = {
settingsPath: string; settingsPath: string;
@@ -21,10 +30,6 @@ type SetupOptions = {
defaultThinkingLevel?: ThinkingLevel; defaultThinkingLevel?: ThinkingLevel;
}; };
function isInteractiveTerminal(): boolean {
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
}
function printNonInteractiveSetupGuidance(): void { function printNonInteractiveSetupGuidance(): void {
printInfo("Non-interactive terminal. Use explicit commands:"); printInfo("Non-interactive terminal. Use explicit commands:");
printInfo(" feynman model login <provider>"); printInfo(" feynman model login <provider>");
@@ -34,21 +39,152 @@ function printNonInteractiveSetupGuidance(): void {
printInfo(" feynman doctor"); printInfo(" feynman doctor");
} }
function summarizePackageSources(sources: string[]): string {
if (sources.length <= 3) {
return sources.join(", ");
}
return `${sources.slice(0, 3).join(", ")} +${sources.length - 3} more`;
}
async function maybeInstallBundledPackages(options: SetupOptions): Promise<void> {
const agentDir = dirname(options.authPath);
const { missing, bundled } = getMissingConfiguredPackages(options.workingDir, agentDir, options.appRoot);
const userMissing = missing.filter((entry) => entry.scope === "user").map((entry) => entry.source);
const projectMissing = missing.filter((entry) => entry.scope === "project").map((entry) => entry.source);
printSection("Packages");
if (bundled.length > 0) {
printInfo(`Bundled research packages ready: ${summarizePackageSources(bundled.map((entry) => entry.source))}`);
}
if (missing.length === 0) {
printInfo("No additional package install required.");
return;
}
printInfo(`Missing packages: ${summarizePackageSources(missing.map((entry) => entry.source))}`);
const shouldInstall = await promptConfirm("Install missing Feynman packages now?", true);
if (!shouldInstall) {
printInfo("Skipping package install. Feynman may install missing packages later if needed.");
return;
}
if (userMissing.length > 0) {
try {
await installPackageSources(options.workingDir, agentDir, userMissing);
printSuccess(`Installed bundled packages: ${summarizePackageSources(userMissing)}`);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
printInfo(message.includes("No supported package manager found")
? "No package manager available for additional installs. The standalone bundle can still run with its shipped packages."
: `Package install skipped: ${message}`);
}
}
if (projectMissing.length > 0) {
try {
await installPackageSources(options.workingDir, agentDir, projectMissing, { local: true });
printSuccess(`Installed project packages: ${summarizePackageSources(projectMissing)}`);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
printInfo(`Project package install skipped: ${message}`);
}
}
}
async function maybeInstallOptionalPackages(options: SetupOptions): Promise<void> {
const agentDir = dirname(options.authPath);
const presets = listOptionalPackagePresets();
if (presets.length === 0) {
return;
}
const selectedPresets = await promptMultiSelect(
"Optional packages",
presets.map((preset) => ({
value: preset.name,
label: preset.name,
hint: preset.description,
})),
[],
);
if (selectedPresets.length === 0) {
printInfo("No optional packages selected.");
return;
}
for (const presetName of selectedPresets) {
const preset = presets.find((entry) => entry.name === presetName);
if (!preset) continue;
try {
await installPackageSources(options.workingDir, agentDir, preset.sources, {
persist: true,
});
printSuccess(`Installed optional preset: ${preset.name}`);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
printInfo(message.includes("No supported package manager found")
? `Skipped optional preset ${preset.name}: no package manager available.`
: `Skipped optional preset ${preset.name}: ${message}`);
}
}
}
async function maybeLoginAlpha(): Promise<void> {
if (isAlphaLoggedIn()) {
printInfo("alphaXiv already configured.");
return;
}
const shouldLogin = await promptConfirm("Connect alphaXiv now?", true);
if (!shouldLogin) {
printInfo("Skipping alphaXiv login for now.");
return;
}
try {
await loginAlpha();
printSuccess("alphaXiv login complete");
} catch (error) {
printInfo(`alphaXiv login skipped: ${error instanceof Error ? error.message : String(error)}`);
}
}
async function maybeInstallPreviewDependencies(): Promise<void> {
if (resolveExecutable("pandoc", PANDOC_FALLBACK_PATHS)) {
printInfo("Preview support already configured.");
return;
}
const shouldInstall = await promptConfirm("Install pandoc for preview/export support?", false);
if (!shouldInstall) {
printInfo("Skipping preview dependency install.");
return;
}
try {
const result = setupPreviewDependencies();
printSuccess(result.message);
} catch (error) {
printInfo(`Preview setup skipped: ${error instanceof Error ? error.message : String(error)}`);
}
}
export async function runSetup(options: SetupOptions): Promise<void> { export async function runSetup(options: SetupOptions): Promise<void> {
if (!isInteractiveTerminal()) { if (!isInteractiveTerminal()) {
printNonInteractiveSetupGuidance(); printNonInteractiveSetupGuidance();
return; return;
} }
try {
await promptIntro("Feynman setup");
await runModelSetup(options.settingsPath, options.authPath); await runModelSetup(options.settingsPath, options.authPath);
await maybeInstallBundledPackages(options);
if (!isAlphaLoggedIn()) { await maybeInstallOptionalPackages(options);
await loginAlpha(); await maybeLoginAlpha();
printSuccess("alphaXiv login complete"); await maybeInstallPreviewDependencies();
}
const result = setupPreviewDependencies();
printSuccess(result.message);
normalizeFeynmanSettings( normalizeFeynmanSettings(
options.settingsPath, options.settingsPath,
@@ -67,4 +203,17 @@ export async function runSetup(options: SetupOptions): Promise<void> {
printInfo(`alphaXiv: ${isAlphaLoggedIn() ? "configured" : "not configured"}`); printInfo(`alphaXiv: ${isAlphaLoggedIn() ? "configured" : "not configured"}`);
printInfo(`Preview: ${resolveExecutable("pandoc", PANDOC_FALLBACK_PATHS) ? "configured" : "not configured"}`); printInfo(`Preview: ${resolveExecutable("pandoc", PANDOC_FALLBACK_PATHS) ? "configured" : "not configured"}`);
printInfo(`Web: ${getPiWebAccessStatus().routeLabel}`); printInfo(`Web: ${getPiWebAccessStatus().routeLabel}`);
if (modelStatus.recommended && !modelStatus.currentValid) {
printInfo(`Recommended model: ${modelStatus.recommended}`);
}
await promptOutro("Feynman is ready");
} catch (error) {
if (error instanceof SetupCancelledError) {
printInfo("Setup cancelled.");
return;
}
throw error;
}
} }