Overhaul Feynman harness: streamline agents, prompts, and extensions
Remove legacy chains, skills, and config modules. Add citation agent, SYSTEM.md, modular research-tools extension, and web-access layer. Add ralph-wiggum to Pi package stack for long-running loops. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
171
src/cli.ts
171
src/cli.ts
@@ -11,33 +11,26 @@ import {
|
||||
login as loginAlpha,
|
||||
logout as logoutAlpha,
|
||||
} from "@companion-ai/alpha-hub/lib";
|
||||
import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
|
||||
import { AuthStorage, DefaultPackageManager, ModelRegistry, SettingsManager } from "@mariozechner/pi-coding-agent";
|
||||
|
||||
import { syncBundledAssets } from "./bootstrap/sync.js";
|
||||
import { editConfig, printConfig, printConfigPath, printConfigValue, setConfigValue } from "./config/commands.js";
|
||||
import { getConfiguredSessionDir, loadFeynmanConfig } from "./config/feynman-config.js";
|
||||
import { ensureFeynmanHome, getFeynmanAgentDir, getFeynmanHome } from "./config/paths.js";
|
||||
import { buildFeynmanSystemPrompt } from "./feynman-prompt.js";
|
||||
import { ensureFeynmanHome, getDefaultSessionDir, getFeynmanAgentDir, getFeynmanHome } from "./config/paths.js";
|
||||
import { launchPiChat } from "./pi/launch.js";
|
||||
import { normalizeFeynmanSettings, normalizeThinkingLevel, parseModelSpec } from "./pi/settings.js";
|
||||
import {
|
||||
loginModelProvider,
|
||||
logoutModelProvider,
|
||||
printModelList,
|
||||
printModelProviders,
|
||||
printModelRecommendation,
|
||||
printModelStatus,
|
||||
setDefaultModelSpec,
|
||||
} from "./model/commands.js";
|
||||
import { printSearchProviders, printSearchStatus, setSearchProvider } from "./search/commands.js";
|
||||
import { printSearchStatus } from "./search/commands.js";
|
||||
import { runDoctor, runStatus } from "./setup/doctor.js";
|
||||
import { setupPreviewDependencies } from "./setup/preview.js";
|
||||
import { runSetup } from "./setup/setup.js";
|
||||
import { printInfo, printPanel, printSection } from "./ui/terminal.js";
|
||||
|
||||
const TOP_LEVEL_COMMANDS = new Set(["alpha", "chat", "config", "doctor", "help", "model", "search", "setup", "status"]);
|
||||
const TOP_LEVEL_COMMANDS = new Set(["alpha", "chat", "doctor", "help", "model", "search", "setup", "status", "update"]);
|
||||
const RESEARCH_WORKFLOW_COMMANDS = new Set([
|
||||
"ablate",
|
||||
"audit",
|
||||
"autoresearch",
|
||||
"compare",
|
||||
@@ -46,11 +39,7 @@ const RESEARCH_WORKFLOW_COMMANDS = new Set([
|
||||
"jobs",
|
||||
"lit",
|
||||
"log",
|
||||
"memo",
|
||||
"reading",
|
||||
"related",
|
||||
"replicate",
|
||||
"rebuttal",
|
||||
"review",
|
||||
"watch",
|
||||
]);
|
||||
@@ -64,40 +53,32 @@ function printHelp(): void {
|
||||
printSection("Getting Started");
|
||||
printInfo("feynman");
|
||||
printInfo("feynman setup");
|
||||
printInfo("feynman setup quick");
|
||||
printInfo("feynman doctor");
|
||||
printInfo("feynman model");
|
||||
printInfo("feynman search");
|
||||
printInfo("feynman search status");
|
||||
|
||||
printSection("Commands");
|
||||
printInfo("feynman chat [prompt] Start chat explicitly, optionally with an initial prompt");
|
||||
printInfo("feynman setup [section] Run setup for model, alpha, web, preview, or all");
|
||||
printInfo("feynman setup quick Configure only missing items");
|
||||
printInfo("feynman setup Run the guided setup");
|
||||
printInfo("feynman doctor Diagnose config, auth, Pi runtime, and preview deps");
|
||||
printInfo("feynman status Show the current setup summary");
|
||||
printInfo("feynman model list Show available models in auth storage");
|
||||
printInfo("feynman model providers Show Pi-supported providers and auth state");
|
||||
printInfo("feynman model recommend Show the recommended research model");
|
||||
printInfo("feynman model login [id] Login to a Pi OAuth model provider");
|
||||
printInfo("feynman model logout [id] Logout from a Pi OAuth model provider");
|
||||
printInfo("feynman model set <spec> Set the default model");
|
||||
printInfo("feynman search status Show web research provider status");
|
||||
printInfo("feynman search set <id> Set web research provider");
|
||||
printInfo("feynman config show Print ~/.feynman/config.json");
|
||||
printInfo("feynman config get <key> Read a config value");
|
||||
printInfo("feynman config set <key> <value>");
|
||||
printInfo("feynman config edit Open config in $EDITOR");
|
||||
printInfo("feynman config path Print the config path");
|
||||
printInfo("feynman update [package] Update installed packages (or a specific one)");
|
||||
printInfo("feynman search status Show Pi web-access status and config path");
|
||||
printInfo("feynman alpha login|logout|status");
|
||||
|
||||
printSection("Research Workflows");
|
||||
printInfo("feynman lit <topic> Start the literature-review workflow");
|
||||
printInfo("feynman review <artifact> Start the peer-review workflow");
|
||||
printInfo("feynman audit <item> Start the paper/code audit workflow");
|
||||
printInfo("feynman replicate <target> Start the replication workflow");
|
||||
printInfo("feynman memo <topic> Start the research memo workflow");
|
||||
printInfo("feynman draft <topic> Start the paper-style draft workflow");
|
||||
printInfo("feynman watch <topic> Start the recurring research watch workflow");
|
||||
printInfo("feynman deepresearch <topic> Start a thorough source-heavy investigation");
|
||||
printInfo("feynman lit <topic> Start the literature-review workflow");
|
||||
printInfo("feynman review <artifact> Start the peer-review workflow");
|
||||
printInfo("feynman audit <item> Start the paper/code audit workflow");
|
||||
printInfo("feynman replicate <target> Start the replication workflow");
|
||||
printInfo("feynman draft <topic> Start the paper-style draft workflow");
|
||||
printInfo("feynman compare <topic> Start the source-comparison workflow");
|
||||
printInfo("feynman watch <topic> Start the recurring research watch workflow");
|
||||
|
||||
printSection("Legacy Flags");
|
||||
printInfo('--prompt "<text>" Run one prompt and exit');
|
||||
@@ -148,71 +129,19 @@ async function handleAlphaCommand(action: string | undefined): Promise<void> {
|
||||
throw new Error(`Unknown alpha command: ${action}`);
|
||||
}
|
||||
|
||||
function handleConfigCommand(subcommand: string | undefined, args: string[]): void {
|
||||
if (!subcommand || subcommand === "show") {
|
||||
printConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
if (subcommand === "path") {
|
||||
printConfigPath();
|
||||
return;
|
||||
}
|
||||
|
||||
if (subcommand === "edit") {
|
||||
editConfig();
|
||||
return;
|
||||
}
|
||||
|
||||
if (subcommand === "get") {
|
||||
const key = args[0];
|
||||
if (!key) {
|
||||
throw new Error("Usage: feynman config get <key>");
|
||||
}
|
||||
printConfigValue(key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (subcommand === "set") {
|
||||
const [key, ...valueParts] = args;
|
||||
if (!key || valueParts.length === 0) {
|
||||
throw new Error("Usage: feynman config set <key> <value>");
|
||||
}
|
||||
setConfigValue(key, valueParts.join(" "));
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`Unknown config command: ${subcommand}`);
|
||||
}
|
||||
|
||||
async function handleModelCommand(subcommand: string | undefined, args: string[], settingsPath: string, authPath: string): Promise<void> {
|
||||
if (!subcommand || subcommand === "status" || subcommand === "current") {
|
||||
printModelStatus(settingsPath, authPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (subcommand === "list") {
|
||||
printModelList(settingsPath, authPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (subcommand === "providers") {
|
||||
printModelProviders(settingsPath, authPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (subcommand === "recommend") {
|
||||
printModelRecommendation(authPath);
|
||||
async function handleModelCommand(subcommand: string | undefined, args: string[], feynmanSettingsPath: string, feynmanAuthPath: string): Promise<void> {
|
||||
if (!subcommand || subcommand === "list") {
|
||||
printModelList(feynmanSettingsPath, feynmanAuthPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (subcommand === "login") {
|
||||
await loginModelProvider(authPath, args[0]);
|
||||
await loginModelProvider(feynmanAuthPath, args[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (subcommand === "logout") {
|
||||
await logoutModelProvider(authPath, args[0]);
|
||||
await logoutModelProvider(feynmanAuthPath, args[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -221,33 +150,42 @@ async function handleModelCommand(subcommand: string | undefined, args: string[]
|
||||
if (!spec) {
|
||||
throw new Error("Usage: feynman model set <provider/model>");
|
||||
}
|
||||
setDefaultModelSpec(settingsPath, authPath, spec);
|
||||
setDefaultModelSpec(feynmanSettingsPath, feynmanAuthPath, spec);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`Unknown model command: ${subcommand}`);
|
||||
}
|
||||
|
||||
function handleSearchCommand(subcommand: string | undefined, args: string[]): void {
|
||||
async function handleUpdateCommand(workingDir: string, feynmanAgentDir: string, source?: string): Promise<void> {
|
||||
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"}`);
|
||||
}
|
||||
});
|
||||
|
||||
await packageManager.update(source);
|
||||
await settingsManager.flush();
|
||||
console.log("All packages up to date.");
|
||||
}
|
||||
|
||||
function handleSearchCommand(subcommand: string | undefined): void {
|
||||
if (!subcommand || subcommand === "status") {
|
||||
printSearchStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (subcommand === "providers" || subcommand === "list") {
|
||||
printSearchProviders();
|
||||
return;
|
||||
}
|
||||
|
||||
if (subcommand === "set") {
|
||||
const provider = args[0];
|
||||
if (!provider) {
|
||||
throw new Error("Usage: feynman search set <provider> [value]");
|
||||
}
|
||||
setSearchProvider(provider, args[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`Unknown search command: ${subcommand}`);
|
||||
}
|
||||
|
||||
@@ -317,9 +255,8 @@ export async function main(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
const config = loadFeynmanConfig();
|
||||
const workingDir = resolve(values.cwd ?? process.cwd());
|
||||
const sessionDir = resolve(values["session-dir"] ?? getConfiguredSessionDir(config));
|
||||
const sessionDir = resolve(values["session-dir"] ?? getDefaultSessionDir(feynmanHome));
|
||||
const feynmanSettingsPath = resolve(feynmanAgentDir, "settings.json");
|
||||
const feynmanAuthPath = resolve(feynmanAgentDir, "auth.json");
|
||||
const thinkingLevel = normalizeThinkingLevel(values.thinking ?? process.env.FEYNMAN_THINKING) ?? "medium";
|
||||
@@ -366,7 +303,6 @@ export async function main(): Promise<void> {
|
||||
|
||||
if (command === "setup") {
|
||||
await runSetup({
|
||||
section: rest[0],
|
||||
settingsPath: feynmanSettingsPath,
|
||||
bundledSettingsPath,
|
||||
authPath: feynmanAuthPath,
|
||||
@@ -400,18 +336,18 @@ export async function main(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === "config") {
|
||||
handleConfigCommand(rest[0], rest.slice(1));
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === "model") {
|
||||
await handleModelCommand(rest[0], rest.slice(1), feynmanSettingsPath, feynmanAuthPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === "search") {
|
||||
handleSearchCommand(rest[0], rest.slice(1));
|
||||
handleSearchCommand(rest[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === "update") {
|
||||
await handleUpdateCommand(workingDir, feynmanAgentDir, rest[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -439,6 +375,5 @@ export async function main(): Promise<void> {
|
||||
explicitModelSpec,
|
||||
oneShotPrompt: values.prompt,
|
||||
initialPrompt: resolveInitialPrompt(command, rest, values.prompt),
|
||||
systemPrompt: buildFeynmanSystemPrompt(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
|
||||
import { FEYNMAN_CONFIG_PATH, loadFeynmanConfig, saveFeynmanConfig } from "./feynman-config.js";
|
||||
|
||||
function coerceConfigValue(raw: string): unknown {
|
||||
const trimmed = raw.trim();
|
||||
if (trimmed === "true") return true;
|
||||
if (trimmed === "false") return false;
|
||||
if (trimmed === "null") return null;
|
||||
if (trimmed === "") return "";
|
||||
if (/^-?\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed);
|
||||
|
||||
try {
|
||||
return JSON.parse(trimmed);
|
||||
} catch {
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
|
||||
function getNestedValue(record: Record<string, unknown>, path: string): unknown {
|
||||
return path.split(".").reduce<unknown>((current, segment) => {
|
||||
if (!current || typeof current !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
return (current as Record<string, unknown>)[segment];
|
||||
}, record);
|
||||
}
|
||||
|
||||
function setNestedValue(record: Record<string, unknown>, path: string, value: unknown): void {
|
||||
const segments = path.split(".");
|
||||
let current: Record<string, unknown> = record;
|
||||
|
||||
for (const segment of segments.slice(0, -1)) {
|
||||
const existing = current[segment];
|
||||
if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
|
||||
current[segment] = {};
|
||||
}
|
||||
current = current[segment] as Record<string, unknown>;
|
||||
}
|
||||
|
||||
current[segments[segments.length - 1]!] = value;
|
||||
}
|
||||
|
||||
export function printConfig(): void {
|
||||
console.log(JSON.stringify(loadFeynmanConfig(), null, 2));
|
||||
}
|
||||
|
||||
export function printConfigPath(): void {
|
||||
console.log(FEYNMAN_CONFIG_PATH);
|
||||
}
|
||||
|
||||
export function editConfig(): void {
|
||||
if (!existsSync(FEYNMAN_CONFIG_PATH)) {
|
||||
saveFeynmanConfig(loadFeynmanConfig());
|
||||
}
|
||||
|
||||
const editor = process.env.VISUAL || process.env.EDITOR || "vi";
|
||||
const result = spawnSync(editor, [FEYNMAN_CONFIG_PATH], {
|
||||
stdio: "inherit",
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`Failed to open editor: ${editor}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function printConfigValue(key: string): void {
|
||||
const config = loadFeynmanConfig() as Record<string, unknown>;
|
||||
const value = getNestedValue(config, key);
|
||||
console.log(typeof value === "string" ? value : JSON.stringify(value, null, 2));
|
||||
}
|
||||
|
||||
export function setConfigValue(key: string, rawValue: string): void {
|
||||
const config = loadFeynmanConfig() as Record<string, unknown>;
|
||||
setNestedValue(config, key, coerceConfigValue(rawValue));
|
||||
saveFeynmanConfig(config as ReturnType<typeof loadFeynmanConfig>);
|
||||
console.log(`Updated ${key}`);
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
|
||||
import { getDefaultSessionDir, getFeynmanConfigPath } from "./paths.js";
|
||||
|
||||
export type WebSearchProviderId = "auto" | "perplexity" | "gemini-api" | "gemini-browser";
|
||||
export type PiWebSearchProvider = "auto" | "perplexity" | "gemini";
|
||||
|
||||
export type WebSearchConfig = Record<string, unknown> & {
|
||||
provider?: PiWebSearchProvider;
|
||||
perplexityApiKey?: string;
|
||||
geminiApiKey?: string;
|
||||
chromeProfile?: string;
|
||||
feynmanWebProvider?: WebSearchProviderId;
|
||||
};
|
||||
|
||||
export type FeynmanConfig = {
|
||||
version: 1;
|
||||
sessionDir?: string;
|
||||
webSearch?: WebSearchConfig;
|
||||
preview?: {
|
||||
lastSetupAt?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type WebSearchProviderDefinition = {
|
||||
id: WebSearchProviderId;
|
||||
label: string;
|
||||
description: string;
|
||||
runtimeProvider: PiWebSearchProvider;
|
||||
requiresApiKey: boolean;
|
||||
};
|
||||
|
||||
export type WebSearchStatus = {
|
||||
selected: WebSearchProviderDefinition;
|
||||
configPath: string;
|
||||
perplexityConfigured: boolean;
|
||||
geminiApiConfigured: boolean;
|
||||
chromeProfile?: string;
|
||||
browserHint: string;
|
||||
};
|
||||
|
||||
export const FEYNMAN_CONFIG_PATH = getFeynmanConfigPath();
|
||||
export const LEGACY_WEB_SEARCH_CONFIG_PATH = resolve(process.env.HOME ?? "", ".pi", "web-search.json");
|
||||
export const DEFAULT_WEB_SEARCH_PROVIDER: WebSearchProviderId = "gemini-browser";
|
||||
|
||||
export const WEB_SEARCH_PROVIDERS: ReadonlyArray<WebSearchProviderDefinition> = [
|
||||
{
|
||||
id: "auto",
|
||||
label: "Auto",
|
||||
description: "Prefer Perplexity when configured, otherwise fall back to Gemini.",
|
||||
runtimeProvider: "auto",
|
||||
requiresApiKey: false,
|
||||
},
|
||||
{
|
||||
id: "perplexity",
|
||||
label: "Perplexity API",
|
||||
description: "Use Perplexity Sonar directly for web answers and source lists.",
|
||||
runtimeProvider: "perplexity",
|
||||
requiresApiKey: true,
|
||||
},
|
||||
{
|
||||
id: "gemini-api",
|
||||
label: "Gemini API",
|
||||
description: "Use Gemini directly with an API key.",
|
||||
runtimeProvider: "gemini",
|
||||
requiresApiKey: true,
|
||||
},
|
||||
{
|
||||
id: "gemini-browser",
|
||||
label: "Gemini Browser",
|
||||
description: "Use your signed-in Chromium browser session through pi-web-access.",
|
||||
runtimeProvider: "gemini",
|
||||
requiresApiKey: false,
|
||||
},
|
||||
] as const;
|
||||
|
||||
function readJsonFile<T>(path: string): T | undefined {
|
||||
if (!existsSync(path)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(readFileSync(path, "utf8")) as T;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeWebSearchConfig(value: unknown): WebSearchConfig | undefined {
|
||||
if (!value || typeof value !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return { ...(value as WebSearchConfig) };
|
||||
}
|
||||
|
||||
function migrateLegacyWebSearchConfig(): WebSearchConfig | undefined {
|
||||
return normalizeWebSearchConfig(readJsonFile<WebSearchConfig>(LEGACY_WEB_SEARCH_CONFIG_PATH));
|
||||
}
|
||||
|
||||
export function loadFeynmanConfig(configPath = FEYNMAN_CONFIG_PATH): FeynmanConfig {
|
||||
const config = readJsonFile<FeynmanConfig>(configPath);
|
||||
if (config && typeof config === "object") {
|
||||
return {
|
||||
version: 1,
|
||||
sessionDir: typeof config.sessionDir === "string" && config.sessionDir.trim() ? config.sessionDir : undefined,
|
||||
webSearch: normalizeWebSearchConfig(config.webSearch),
|
||||
preview: config.preview && typeof config.preview === "object" ? { ...config.preview } : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const legacyWebSearch = migrateLegacyWebSearchConfig();
|
||||
return {
|
||||
version: 1,
|
||||
sessionDir: getDefaultSessionDir(),
|
||||
webSearch: legacyWebSearch,
|
||||
};
|
||||
}
|
||||
|
||||
export function saveFeynmanConfig(config: FeynmanConfig, configPath = FEYNMAN_CONFIG_PATH): void {
|
||||
mkdirSync(dirname(configPath), { recursive: true });
|
||||
writeFileSync(
|
||||
configPath,
|
||||
JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
...(config.sessionDir ? { sessionDir: config.sessionDir } : {}),
|
||||
...(config.webSearch ? { webSearch: config.webSearch } : {}),
|
||||
...(config.preview ? { preview: config.preview } : {}),
|
||||
},
|
||||
null,
|
||||
2,
|
||||
) + "\n",
|
||||
"utf8",
|
||||
);
|
||||
}
|
||||
|
||||
export function getConfiguredSessionDir(config = loadFeynmanConfig()): string {
|
||||
return typeof config.sessionDir === "string" && config.sessionDir.trim()
|
||||
? config.sessionDir
|
||||
: getDefaultSessionDir();
|
||||
}
|
||||
|
||||
export function loadWebSearchConfig(): WebSearchConfig {
|
||||
return loadFeynmanConfig().webSearch ?? {};
|
||||
}
|
||||
|
||||
export function saveWebSearchConfig(config: WebSearchConfig): void {
|
||||
const current = loadFeynmanConfig();
|
||||
saveFeynmanConfig({
|
||||
...current,
|
||||
webSearch: config,
|
||||
});
|
||||
}
|
||||
|
||||
export function getWebSearchProviderById(id: WebSearchProviderId): WebSearchProviderDefinition {
|
||||
return WEB_SEARCH_PROVIDERS.find((provider) => provider.id === id) ?? WEB_SEARCH_PROVIDERS[0];
|
||||
}
|
||||
|
||||
export function hasPerplexityApiKey(config: WebSearchConfig = loadWebSearchConfig()): boolean {
|
||||
return typeof config.perplexityApiKey === "string" && config.perplexityApiKey.trim().length > 0;
|
||||
}
|
||||
|
||||
export function hasGeminiApiKey(config: WebSearchConfig = loadWebSearchConfig()): boolean {
|
||||
return typeof config.geminiApiKey === "string" && config.geminiApiKey.trim().length > 0;
|
||||
}
|
||||
|
||||
export function hasConfiguredWebProvider(config: WebSearchConfig = loadWebSearchConfig()): boolean {
|
||||
return hasPerplexityApiKey(config) || hasGeminiApiKey(config) || getConfiguredWebSearchProvider(config).id === DEFAULT_WEB_SEARCH_PROVIDER;
|
||||
}
|
||||
|
||||
export function getConfiguredWebSearchProvider(
|
||||
config: WebSearchConfig = loadWebSearchConfig(),
|
||||
): WebSearchProviderDefinition {
|
||||
const explicit = config.feynmanWebProvider;
|
||||
if (explicit === "auto" || explicit === "perplexity" || explicit === "gemini-api" || explicit === "gemini-browser") {
|
||||
return getWebSearchProviderById(explicit);
|
||||
}
|
||||
|
||||
if (config.provider === "perplexity") {
|
||||
return getWebSearchProviderById("perplexity");
|
||||
}
|
||||
|
||||
if (config.provider === "gemini") {
|
||||
return hasGeminiApiKey(config)
|
||||
? getWebSearchProviderById("gemini-api")
|
||||
: getWebSearchProviderById("gemini-browser");
|
||||
}
|
||||
|
||||
return getWebSearchProviderById(DEFAULT_WEB_SEARCH_PROVIDER);
|
||||
}
|
||||
|
||||
export function configureWebSearchProvider(
|
||||
current: WebSearchConfig,
|
||||
providerId: WebSearchProviderId,
|
||||
values: { apiKey?: string; chromeProfile?: string } = {},
|
||||
): WebSearchConfig {
|
||||
const next: WebSearchConfig = { ...current };
|
||||
next.feynmanWebProvider = providerId;
|
||||
|
||||
switch (providerId) {
|
||||
case "auto":
|
||||
next.provider = "auto";
|
||||
if (typeof values.chromeProfile === "string" && values.chromeProfile.trim()) {
|
||||
next.chromeProfile = values.chromeProfile.trim();
|
||||
}
|
||||
return next;
|
||||
case "perplexity":
|
||||
next.provider = "perplexity";
|
||||
if (typeof values.apiKey === "string" && values.apiKey.trim()) {
|
||||
next.perplexityApiKey = values.apiKey.trim();
|
||||
}
|
||||
if (typeof values.chromeProfile === "string" && values.chromeProfile.trim()) {
|
||||
next.chromeProfile = values.chromeProfile.trim();
|
||||
}
|
||||
return next;
|
||||
case "gemini-api":
|
||||
next.provider = "gemini";
|
||||
if (typeof values.apiKey === "string" && values.apiKey.trim()) {
|
||||
next.geminiApiKey = values.apiKey.trim();
|
||||
}
|
||||
if (typeof values.chromeProfile === "string" && values.chromeProfile.trim()) {
|
||||
next.chromeProfile = values.chromeProfile.trim();
|
||||
}
|
||||
return next;
|
||||
case "gemini-browser":
|
||||
next.provider = "gemini";
|
||||
delete next.geminiApiKey;
|
||||
if (typeof values.chromeProfile === "string") {
|
||||
const profile = values.chromeProfile.trim();
|
||||
if (profile) {
|
||||
next.chromeProfile = profile;
|
||||
} else {
|
||||
delete next.chromeProfile;
|
||||
}
|
||||
}
|
||||
return next;
|
||||
}
|
||||
}
|
||||
|
||||
export function getWebSearchStatus(config: WebSearchConfig = loadWebSearchConfig()): WebSearchStatus {
|
||||
const selected = getConfiguredWebSearchProvider(config);
|
||||
return {
|
||||
selected,
|
||||
configPath: FEYNMAN_CONFIG_PATH,
|
||||
perplexityConfigured: hasPerplexityApiKey(config),
|
||||
geminiApiConfigured: hasGeminiApiKey(config),
|
||||
chromeProfile: typeof config.chromeProfile === "string" && config.chromeProfile.trim()
|
||||
? config.chromeProfile.trim()
|
||||
: undefined,
|
||||
browserHint: selected.id === "gemini-browser" ? "selected" : "fallback only",
|
||||
};
|
||||
}
|
||||
|
||||
export function formatWebSearchDoctorLines(config: WebSearchConfig = loadWebSearchConfig()): string[] {
|
||||
const status = getWebSearchStatus(config);
|
||||
const configured = [];
|
||||
if (status.perplexityConfigured) configured.push("Perplexity API");
|
||||
if (status.geminiApiConfigured) configured.push("Gemini API");
|
||||
if (status.selected.id === "gemini-browser" || status.chromeProfile) configured.push("Gemini Browser");
|
||||
|
||||
return [
|
||||
`web research provider: ${status.selected.label}`,
|
||||
` runtime route: ${status.selected.runtimeProvider}`,
|
||||
` configured credentials: ${configured.length > 0 ? configured.join(", ") : "none"}`,
|
||||
` browser mode: ${status.browserHint}${status.chromeProfile ? ` (profile: ${status.chromeProfile})` : ""}`,
|
||||
` config path: ${status.configPath}`,
|
||||
];
|
||||
}
|
||||
@@ -22,10 +22,6 @@ export function getDefaultSessionDir(home = getFeynmanHome()): string {
|
||||
return resolve(home, "sessions");
|
||||
}
|
||||
|
||||
export function getFeynmanConfigPath(home = getFeynmanHome()): string {
|
||||
return resolve(home, "config.json");
|
||||
}
|
||||
|
||||
export function getBootstrapStatePath(home = getFeynmanHome()): string {
|
||||
return resolve(getFeynmanStateDir(home), "bootstrap.json");
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
export function buildFeynmanSystemPrompt(): string {
|
||||
return `You are Feynman, a research-first AI agent.
|
||||
|
||||
Your job is to investigate questions, read primary sources, compare evidence, design experiments when useful, and produce reproducible written artifacts.
|
||||
|
||||
Operating rules:
|
||||
- Evidence over fluency.
|
||||
- Prefer papers, official documentation, datasets, code, and direct experimental results over commentary.
|
||||
- Separate observations from inferences.
|
||||
- State uncertainty explicitly.
|
||||
- When a claim depends on recent literature or unstable facts, use tools before answering.
|
||||
- When discussing papers, cite title, year, and identifier or URL when possible.
|
||||
- Use the alpha-backed research tools for academic paper search, paper reading, paper Q&A, repository inspection, and persistent annotations.
|
||||
- Use \`web_search\`, \`fetch_content\`, and \`get_search_content\` first for current topics: products, companies, markets, regulations, software releases, model availability, model pricing, benchmarks, docs, or anything phrased as latest/current/recent/today.
|
||||
- For mixed topics, combine both: use web sources for current reality and paper sources for background literature.
|
||||
- Never answer a latest/current question from arXiv or alpha-backed paper search alone.
|
||||
- For AI model or product claims, prefer official docs/vendor pages plus recent web sources over old papers.
|
||||
- Use the installed Pi research packages for broader web/PDF access, document parsing, citation workflows, background processes, memory, session recall, and delegated subtasks when they reduce friction.
|
||||
- Feynman ships project subagents for research work. Prefer the \`researcher\`, \`verifier\`, \`reviewer\`, and \`writer\` subagents for larger research tasks when decomposition clearly helps.
|
||||
- Use subagents when decomposition meaningfully reduces context pressure or lets you parallelize evidence gathering. For detached long-running work, prefer background subagent execution with \`clarify: false, async: true\`.
|
||||
- For deep research, act like a lead researcher by default: plan first, use hidden worker batches only when breadth justifies them, synthesize batch results, and finish with a verification/citation pass.
|
||||
- Do not force chain-shaped orchestration onto the user. Multi-agent decomposition is an internal tactic, not the primary UX.
|
||||
- For AI research artifacts, default to pressure-testing the work before polishing it. Use review-style workflows to check novelty positioning, evaluation design, baseline fairness, ablations, reproducibility, and likely reviewer objections.
|
||||
- Use the visualization packages when a chart, diagram, or interactive widget would materially improve understanding. Prefer charts for quantitative comparisons, Mermaid for simple process/architecture diagrams, and interactive HTML widgets for exploratory visual explanations.
|
||||
- Persistent memory is package-backed. Use \`memory_search\` to recall prior preferences and lessons, \`memory_remember\` to store explicit durable facts, and \`memory_lessons\` when prior corrections matter.
|
||||
- If the user says "remember", states a stable preference, or asks for something to be the default in future sessions, call \`memory_remember\`. Do not just say you will remember it.
|
||||
- Session recall is package-backed. Use \`session_search\` when the user references prior work, asks what has been done before, or when you suspect relevant past context exists.
|
||||
- Feynman is intended to support always-on research work. Use the scheduling package when recurring or deferred work is appropriate instead of telling the user to remember manually.
|
||||
- Use \`schedule_prompt\` for recurring scans, delayed follow-ups, reminders, and periodic research jobs.
|
||||
- If the user asks you to remind, check later, run something nightly, or keep watching something over time, call \`schedule_prompt\`. Do not just promise to do it later.
|
||||
- For long-running local work such as experiments, crawls, or log-following, use the process package instead of blocking the main thread unnecessarily. Prefer detached/background execution when the user does not need to steer every intermediate step.
|
||||
- Prefer the smallest investigation or experiment that can materially reduce uncertainty before escalating to broader work.
|
||||
- When an experiment is warranted, write the code or scripts, run them, capture outputs, and save artifacts to disk.
|
||||
- Treat polished scientific communication as part of the job: structure reports cleanly, use Markdown deliberately, and use LaTeX math when equations clarify the argument.
|
||||
- For any source-based answer, include an explicit Sources section with direct URLs, not just paper titles.
|
||||
- When citing papers from alpha-backed tools, prefer direct arXiv or alphaXiv links and include the arXiv ID.
|
||||
- After writing a polished artifact, use \`preview_file\` only when the user wants review or export. Prefer browser preview by default; use PDF only when explicitly requested.
|
||||
- Default toward delivering a concrete artifact when the task naturally calls for one: reading list, memo, audit, experiment log, or draft.
|
||||
- For user-facing workflows, produce exactly one canonical durable Markdown artifact unless the user explicitly asks for multiple deliverables.
|
||||
- Do not create extra user-facing intermediate markdown files just because the workflow has multiple reasoning stages.
|
||||
- Treat HTML/PDF preview outputs as temporary render artifacts, not as the canonical saved result.
|
||||
- Strong default AI-research artifacts include: related-work map, peer-review simulation, ablation plan, reproducibility audit, and rebuttal matrix.
|
||||
- Default artifact locations:
|
||||
- outputs/ for reviews, reading lists, and summaries
|
||||
- experiments/ for runnable experiment code and result logs
|
||||
- notes/ for scratch notes and intermediate synthesis
|
||||
- papers/ for polished paper-style drafts and writeups
|
||||
- Default deliverables should include: summary, strongest evidence, disagreements or gaps, open questions, recommended next steps, and links to the source material.
|
||||
|
||||
Default workflow:
|
||||
1. Clarify the research objective if needed.
|
||||
2. Search for relevant primary sources.
|
||||
3. Inspect the most relevant papers or materials directly.
|
||||
4. Synthesize consensus, disagreements, and missing evidence.
|
||||
5. Design and run experiments when they would resolve uncertainty.
|
||||
6. Write the requested output artifact.
|
||||
|
||||
Style:
|
||||
- Concise, skeptical, and explicit.
|
||||
- Avoid fake certainty.
|
||||
- Do not present unverified claims as facts.
|
||||
- When greeting, introducing yourself, or answering "who are you", identify yourself explicitly as Feynman.`;
|
||||
}
|
||||
@@ -6,21 +6,11 @@ import { promptChoice, promptText } from "../setup/prompts.js";
|
||||
import { printInfo, printSection, printSuccess, printWarning } from "../ui/terminal.js";
|
||||
import {
|
||||
buildModelStatusSnapshotFromRecords,
|
||||
chooseRecommendedModel,
|
||||
getAvailableModelRecords,
|
||||
getSupportedModelRecords,
|
||||
type ModelStatusSnapshot,
|
||||
} from "./catalog.js";
|
||||
|
||||
function formatProviderSummaryLine(status: ModelStatusSnapshot["providers"][number]): string {
|
||||
const state = status.configured ? `${status.availableModels} authenticated` : "not authenticated";
|
||||
const flags = [
|
||||
status.current ? "current" : undefined,
|
||||
status.recommended ? "recommended" : undefined,
|
||||
].filter(Boolean);
|
||||
return `${status.label}: ${state}, ${status.supportedModels} supported${flags.length > 0 ? ` (${flags.join(", ")})` : ""}`;
|
||||
}
|
||||
|
||||
function collectModelStatus(settingsPath: string, authPath: string): ModelStatusSnapshot {
|
||||
return buildModelStatusSnapshotFromRecords(
|
||||
getSupportedModelRecords(authPath),
|
||||
@@ -94,50 +84,6 @@ export function getCurrentModelSpec(settingsPath: string): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function printModelStatus(settingsPath: string, authPath: string): void {
|
||||
const status = collectModelStatus(settingsPath, authPath);
|
||||
|
||||
printInfo(`Current default model: ${status.current ?? "not set"}`);
|
||||
printInfo(`Current default valid: ${status.currentValid ? "yes" : "no"}`);
|
||||
printInfo(`Authenticated models: ${status.availableModels.length}`);
|
||||
printInfo(`Providers with auth: ${status.providers.filter((provider) => provider.configured).length}`);
|
||||
printInfo(`Research recommendation: ${status.recommended ?? "none available"}`);
|
||||
if (status.recommendationReason) {
|
||||
printInfo(`Recommendation reason: ${status.recommendationReason}`);
|
||||
}
|
||||
|
||||
if (status.providers.length > 0) {
|
||||
printSection("Providers");
|
||||
for (const provider of status.providers) {
|
||||
printInfo(formatProviderSummaryLine(provider));
|
||||
}
|
||||
}
|
||||
|
||||
if (status.guidance.length > 0) {
|
||||
printSection("Next Steps");
|
||||
for (const line of status.guidance) {
|
||||
printWarning(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function printModelProviders(settingsPath: string, authPath: string): void {
|
||||
const status = collectModelStatus(settingsPath, authPath);
|
||||
for (const provider of status.providers) {
|
||||
printInfo(formatProviderSummaryLine(provider));
|
||||
}
|
||||
const oauthProviders = getOAuthProviders(authPath);
|
||||
if (oauthProviders.length > 0) {
|
||||
printSection("OAuth Login");
|
||||
for (const provider of oauthProviders) {
|
||||
printInfo(`${provider.id} — ${provider.name ?? provider.id}`);
|
||||
}
|
||||
}
|
||||
if (status.providers.length === 0) {
|
||||
printWarning("No Pi model providers are visible in the current runtime.");
|
||||
}
|
||||
}
|
||||
|
||||
export function printModelList(settingsPath: string, authPath: string): void {
|
||||
const status = collectModelStatus(settingsPath, authPath);
|
||||
if (status.availableModels.length === 0) {
|
||||
@@ -163,18 +109,6 @@ export function printModelList(settingsPath: string, authPath: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function printModelRecommendation(authPath: string): void {
|
||||
const recommendation = chooseRecommendedModel(authPath);
|
||||
if (!recommendation) {
|
||||
printWarning("No authenticated Pi models are available to recommend.");
|
||||
printInfo("Run `feynman model login <provider>` or add provider credentials that Pi can see, then rerun this command.");
|
||||
return;
|
||||
}
|
||||
|
||||
printSuccess(`Recommended model: ${recommendation.spec}`);
|
||||
printInfo(recommendation.reason);
|
||||
}
|
||||
|
||||
export async function loginModelProvider(authPath: string, providerId?: string): Promise<void> {
|
||||
const provider = providerId ? resolveOAuthProvider(authPath, providerId) : await selectOAuthProvider(authPath, "login");
|
||||
if (!provider) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
|
||||
import {
|
||||
@@ -18,7 +18,6 @@ export type PiRuntimeOptions = {
|
||||
explicitModelSpec?: string;
|
||||
oneShotPrompt?: string;
|
||||
initialPrompt?: string;
|
||||
systemPrompt: string;
|
||||
};
|
||||
|
||||
export function resolvePiPaths(appRoot: string) {
|
||||
@@ -27,8 +26,8 @@ export function resolvePiPaths(appRoot: string) {
|
||||
piCliPath: resolve(appRoot, "node_modules", "@mariozechner", "pi-coding-agent", "dist", "cli.js"),
|
||||
promisePolyfillPath: resolve(appRoot, "dist", "system", "promise-polyfill.js"),
|
||||
researchToolsPath: resolve(appRoot, "extensions", "research-tools.ts"),
|
||||
skillsPath: resolve(appRoot, "skills"),
|
||||
promptTemplatePath: resolve(appRoot, "prompts"),
|
||||
systemPromptPath: resolve(appRoot, ".pi", "SYSTEM.md"),
|
||||
piWorkspaceNodeModulesPath: resolve(appRoot, ".pi", "npm", "node_modules"),
|
||||
};
|
||||
}
|
||||
@@ -40,7 +39,6 @@ export function validatePiInstallation(appRoot: string): string[] {
|
||||
if (!existsSync(paths.piCliPath)) missing.push(paths.piCliPath);
|
||||
if (!existsSync(paths.promisePolyfillPath)) missing.push(paths.promisePolyfillPath);
|
||||
if (!existsSync(paths.researchToolsPath)) missing.push(paths.researchToolsPath);
|
||||
if (!existsSync(paths.skillsPath)) missing.push(paths.skillsPath);
|
||||
if (!existsSync(paths.promptTemplatePath)) missing.push(paths.promptTemplatePath);
|
||||
|
||||
return missing;
|
||||
@@ -53,14 +51,14 @@ export function buildPiArgs(options: PiRuntimeOptions): string[] {
|
||||
options.sessionDir,
|
||||
"--extension",
|
||||
paths.researchToolsPath,
|
||||
"--skill",
|
||||
paths.skillsPath,
|
||||
"--prompt-template",
|
||||
paths.promptTemplatePath,
|
||||
"--system-prompt",
|
||||
options.systemPrompt,
|
||||
];
|
||||
|
||||
if (existsSync(paths.systemPromptPath)) {
|
||||
args.push("--system-prompt", readFileSync(paths.systemPromptPath, "utf8"));
|
||||
}
|
||||
|
||||
if (options.explicitModelSpec) {
|
||||
args.push("--model", options.explicitModelSpec);
|
||||
}
|
||||
@@ -81,16 +79,13 @@ export function buildPiEnv(options: PiRuntimeOptions): NodeJS.ProcessEnv {
|
||||
|
||||
return {
|
||||
...process.env,
|
||||
PI_CODING_AGENT_DIR: options.feynmanAgentDir,
|
||||
FEYNMAN_CODING_AGENT_DIR: options.feynmanAgentDir,
|
||||
FEYNMAN_VERSION: options.feynmanVersion,
|
||||
FEYNMAN_PI_NPM_ROOT: paths.piWorkspaceNodeModulesPath,
|
||||
FEYNMAN_SESSION_DIR: options.sessionDir,
|
||||
PI_SESSION_DIR: options.sessionDir,
|
||||
FEYNMAN_MEMORY_DIR: resolve(dirname(options.feynmanAgentDir), "memory"),
|
||||
FEYNMAN_NODE_EXECUTABLE: process.execPath,
|
||||
FEYNMAN_BIN_PATH: resolve(options.appRoot, "bin", "feynman.js"),
|
||||
PANDOC_PATH: process.env.PANDOC_PATH ?? resolveExecutable("pandoc", PANDOC_FALLBACK_PATHS),
|
||||
PI_HARDWARE_CURSOR: process.env.PI_HARDWARE_CURSOR ?? "1",
|
||||
PI_SKIP_VERSION_CHECK: process.env.PI_SKIP_VERSION_CHECK ?? "1",
|
||||
MERMAID_CLI_PATH: process.env.MERMAID_CLI_PATH ?? resolveExecutable("mmdc", MERMAID_FALLBACK_PATHS),
|
||||
PUPPETEER_EXECUTABLE_PATH:
|
||||
|
||||
109
src/pi/web-access.ts
Normal file
109
src/pi/web-access.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
export type PiWebSearchProvider = "auto" | "perplexity" | "gemini";
|
||||
|
||||
export type PiWebAccessConfig = Record<string, unknown> & {
|
||||
provider?: PiWebSearchProvider;
|
||||
searchProvider?: PiWebSearchProvider;
|
||||
perplexityApiKey?: string;
|
||||
geminiApiKey?: string;
|
||||
chromeProfile?: string;
|
||||
};
|
||||
|
||||
export type PiWebAccessStatus = {
|
||||
configPath: string;
|
||||
searchProvider: PiWebSearchProvider;
|
||||
requestProvider: PiWebSearchProvider;
|
||||
perplexityConfigured: boolean;
|
||||
geminiApiConfigured: boolean;
|
||||
chromeProfile?: string;
|
||||
routeLabel: string;
|
||||
note: string;
|
||||
};
|
||||
|
||||
export function getPiWebSearchConfigPath(home = process.env.HOME ?? homedir()): string {
|
||||
return resolve(home, ".pi", "web-search.json");
|
||||
}
|
||||
|
||||
function normalizeProvider(value: unknown): PiWebSearchProvider | undefined {
|
||||
return value === "auto" || value === "perplexity" || value === "gemini" ? value : undefined;
|
||||
}
|
||||
|
||||
function normalizeNonEmptyString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
export function loadPiWebAccessConfig(configPath = getPiWebSearchConfigPath()): PiWebAccessConfig {
|
||||
if (!existsSync(configPath)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(readFileSync(configPath, "utf8")) as PiWebAccessConfig;
|
||||
return parsed && typeof parsed === "object" ? parsed : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function formatRouteLabel(provider: PiWebSearchProvider): string {
|
||||
switch (provider) {
|
||||
case "perplexity":
|
||||
return "Perplexity";
|
||||
case "gemini":
|
||||
return "Gemini";
|
||||
default:
|
||||
return "Auto";
|
||||
}
|
||||
}
|
||||
|
||||
function formatRouteNote(provider: PiWebSearchProvider): string {
|
||||
switch (provider) {
|
||||
case "perplexity":
|
||||
return "Pi web-access will use Perplexity for search.";
|
||||
case "gemini":
|
||||
return "Pi web-access will use Gemini API or Gemini Browser.";
|
||||
default:
|
||||
return "Pi web-access will try Perplexity, then Gemini API, then Gemini Browser.";
|
||||
}
|
||||
}
|
||||
|
||||
export function getPiWebAccessStatus(
|
||||
config: PiWebAccessConfig = loadPiWebAccessConfig(),
|
||||
configPath = getPiWebSearchConfigPath(),
|
||||
): PiWebAccessStatus {
|
||||
const searchProvider = normalizeProvider(config.searchProvider) ?? "auto";
|
||||
const requestProvider = normalizeProvider(config.provider) ?? searchProvider;
|
||||
const perplexityConfigured = Boolean(normalizeNonEmptyString(config.perplexityApiKey));
|
||||
const geminiApiConfigured = Boolean(normalizeNonEmptyString(config.geminiApiKey));
|
||||
const chromeProfile = normalizeNonEmptyString(config.chromeProfile);
|
||||
const effectiveProvider = searchProvider;
|
||||
|
||||
return {
|
||||
configPath,
|
||||
searchProvider,
|
||||
requestProvider,
|
||||
perplexityConfigured,
|
||||
geminiApiConfigured,
|
||||
chromeProfile,
|
||||
routeLabel: formatRouteLabel(effectiveProvider),
|
||||
note: formatRouteNote(effectiveProvider),
|
||||
};
|
||||
}
|
||||
|
||||
export function formatPiWebAccessDoctorLines(
|
||||
status: PiWebAccessStatus = getPiWebAccessStatus(),
|
||||
): string[] {
|
||||
return [
|
||||
"web access: pi-web-access",
|
||||
` search route: ${status.routeLabel}`,
|
||||
` request route: ${status.requestProvider}`,
|
||||
` perplexity api: ${status.perplexityConfigured ? "configured" : "not configured"}`,
|
||||
` gemini api: ${status.geminiApiConfigured ? "configured" : "not configured"}`,
|
||||
` browser profile: ${status.chromeProfile ?? "default Chromium profile"}`,
|
||||
` config path: ${status.configPath}`,
|
||||
` note: ${status.note}`,
|
||||
];
|
||||
}
|
||||
@@ -1,49 +1,13 @@
|
||||
import {
|
||||
DEFAULT_WEB_SEARCH_PROVIDER,
|
||||
WEB_SEARCH_PROVIDERS,
|
||||
configureWebSearchProvider,
|
||||
getWebSearchStatus,
|
||||
loadFeynmanConfig,
|
||||
saveFeynmanConfig,
|
||||
type WebSearchProviderId,
|
||||
} from "../config/feynman-config.js";
|
||||
import { printInfo, printSuccess } from "../ui/terminal.js";
|
||||
import { getPiWebAccessStatus } from "../pi/web-access.js";
|
||||
import { printInfo } from "../ui/terminal.js";
|
||||
|
||||
export function printSearchStatus(): void {
|
||||
const status = getWebSearchStatus(loadFeynmanConfig().webSearch ?? {});
|
||||
printInfo(`Provider: ${status.selected.label}`);
|
||||
printInfo(`Runtime route: ${status.selected.runtimeProvider}`);
|
||||
const status = getPiWebAccessStatus();
|
||||
printInfo("Managed by: pi-web-access");
|
||||
printInfo(`Search route: ${status.routeLabel}`);
|
||||
printInfo(`Request route: ${status.requestProvider}`);
|
||||
printInfo(`Perplexity API configured: ${status.perplexityConfigured ? "yes" : "no"}`);
|
||||
printInfo(`Gemini API configured: ${status.geminiApiConfigured ? "yes" : "no"}`);
|
||||
printInfo(`Browser mode: ${status.browserHint}${status.chromeProfile ? ` (${status.chromeProfile})` : ""}`);
|
||||
}
|
||||
|
||||
export function printSearchProviders(): void {
|
||||
for (const provider of WEB_SEARCH_PROVIDERS) {
|
||||
const marker = provider.id === DEFAULT_WEB_SEARCH_PROVIDER ? " (default)" : "";
|
||||
printInfo(`${provider.id} — ${provider.label}${marker}: ${provider.description}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function setSearchProvider(providerId: string, value?: string): void {
|
||||
if (!WEB_SEARCH_PROVIDERS.some((provider) => provider.id === providerId)) {
|
||||
throw new Error(`Unknown search provider: ${providerId}`);
|
||||
}
|
||||
|
||||
const config = loadFeynmanConfig();
|
||||
const nextWebSearch = configureWebSearchProvider(
|
||||
config.webSearch ?? {},
|
||||
providerId as WebSearchProviderId,
|
||||
providerId === "gemini-browser"
|
||||
? { chromeProfile: value }
|
||||
: providerId === "perplexity" || providerId === "gemini-api"
|
||||
? { apiKey: value }
|
||||
: {},
|
||||
);
|
||||
|
||||
saveFeynmanConfig({
|
||||
...config,
|
||||
webSearch: nextWebSearch,
|
||||
});
|
||||
printSuccess(`Search provider set to ${providerId}`);
|
||||
printInfo(`Browser profile: ${status.chromeProfile ?? "default Chromium profile"}`);
|
||||
printInfo(`Config path: ${status.configPath}`);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
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 { formatPiWebAccessDoctorLines, getPiWebAccessStatus } from "../pi/web-access.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";
|
||||
@@ -32,11 +27,9 @@ export type FeynmanStatusSnapshot = {
|
||||
modelGuidance: string[];
|
||||
alphaLoggedIn: boolean;
|
||||
alphaUser?: string;
|
||||
webProviderLabel: string;
|
||||
webConfigured: boolean;
|
||||
webRouteLabel: string;
|
||||
previewConfigured: boolean;
|
||||
sessionDir: string;
|
||||
configPath: string;
|
||||
pandocReady: boolean;
|
||||
browserReady: boolean;
|
||||
piReady: boolean;
|
||||
@@ -44,11 +37,10 @@ export type FeynmanStatusSnapshot = {
|
||||
};
|
||||
|
||||
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 webStatus = getPiWebAccessStatus();
|
||||
const modelStatus = buildModelStatusSnapshotFromRecords(
|
||||
getSupportedModelRecords(options.authPath),
|
||||
getAvailableModelRecords(options.authPath),
|
||||
@@ -65,11 +57,9 @@ export function collectStatusSnapshot(options: DoctorOptions): FeynmanStatusSnap
|
||||
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),
|
||||
webRouteLabel: webStatus.routeLabel,
|
||||
previewConfigured: Boolean(pandocPath),
|
||||
sessionDir: options.sessionDir,
|
||||
configPath: FEYNMAN_CONFIG_PATH,
|
||||
pandocReady: Boolean(pandocPath),
|
||||
browserReady: Boolean(browserPath),
|
||||
piReady: missingPiBits.length === 0,
|
||||
@@ -89,11 +79,10 @@ export function runStatus(options: DoctorOptions): void {
|
||||
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(`Web access: pi-web-access (${snapshot.webRouteLabel})`);
|
||||
printInfo(`Preview: ${snapshot.previewConfigured ? "configured" : "not configured"}`);
|
||||
|
||||
printSection("Paths");
|
||||
printInfo(`Config: ${snapshot.configPath}`);
|
||||
printInfo(`Sessions: ${snapshot.sessionDir}`);
|
||||
|
||||
printSection("Runtime");
|
||||
@@ -115,7 +104,6 @@ export function runStatus(options: DoctorOptions): void {
|
||||
|
||||
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);
|
||||
@@ -127,7 +115,6 @@ export function runDoctor(options: DoctorOptions): void {
|
||||
]);
|
||||
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()) {
|
||||
@@ -159,8 +146,7 @@ export function runDoctor(options: DoctorOptions): void {
|
||||
}
|
||||
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 ?? {})) {
|
||||
for (const line of formatPiWebAccessDoctorLines()) {
|
||||
console.log(line);
|
||||
}
|
||||
console.log(`quiet startup: ${settings.quietStartup === true ? "enabled" : "disabled"}`);
|
||||
|
||||
@@ -1,28 +1,18 @@
|
||||
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 { getDefaultSessionDir, getFeynmanHome } from "../config/paths.js";
|
||||
import { getPiWebAccessStatus, getPiWebSearchConfigPath } from "../pi/web-access.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 { 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";
|
||||
|
||||
type SetupOptions = {
|
||||
section: string | undefined;
|
||||
settingsPath: string;
|
||||
bundledSettingsPath: string;
|
||||
authPath: string;
|
||||
@@ -32,62 +22,18 @@ type SetupOptions = {
|
||||
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.");
|
||||
}
|
||||
async function explainWebAccess(): Promise<void> {
|
||||
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(loadFeynmanConfig().preview?.lastSetupAt);
|
||||
return Boolean(resolveExecutable("pandoc", PANDOC_FALLBACK_PATHS));
|
||||
}
|
||||
|
||||
function isInteractiveTerminal(): boolean {
|
||||
@@ -100,13 +46,10 @@ function printNonInteractiveSetupGuidance(): void {
|
||||
]);
|
||||
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 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");
|
||||
@@ -115,25 +58,16 @@ function printNonInteractiveSetupGuidance(): void {
|
||||
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(`Sessions: ${getDefaultSessionDir()}`);
|
||||
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),
|
||||
@@ -144,46 +78,24 @@ function printSetupSummary(settingsPath: string, authPath: string): void {
|
||||
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(`Web access: pi-web-access (${getPiWebAccessStatus().routeLabel})`);
|
||||
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);
|
||||
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,
|
||||
@@ -200,49 +112,7 @@ async function runFullSetup(options: SetupOptions): Promise<void> {
|
||||
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),
|
||||
@@ -252,8 +122,7 @@ function hasExistingSetup(settingsPath: string, authPath: string): boolean {
|
||||
modelStatus.current ||
|
||||
modelStatus.availableModels.length > 0 ||
|
||||
isAlphaLoggedIn() ||
|
||||
hasConfiguredWebProvider(config.webSearch ?? {}) ||
|
||||
config.preview?.lastSetupAt,
|
||||
isPreviewConfigured(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -267,13 +136,11 @@ async function runDefaultInteractiveSetup(options: SetupOptions): Promise<void>
|
||||
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(" 3. Preview Dependencies");
|
||||
}
|
||||
printInfo("Press Enter to begin, or Ctrl+C to exit.");
|
||||
await promptText("Press Enter to start");
|
||||
@@ -286,31 +153,5 @@ export async function runSetup(options: SetupOptions): Promise<void> {
|
||||
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);
|
||||
await runDefaultInteractiveSetup(options);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
export {
|
||||
FEYNMAN_CONFIG_PATH as WEB_SEARCH_CONFIG_PATH,
|
||||
WEB_SEARCH_PROVIDERS,
|
||||
configureWebSearchProvider,
|
||||
formatWebSearchDoctorLines,
|
||||
getConfiguredWebSearchProvider,
|
||||
getWebSearchProviderById,
|
||||
getWebSearchStatus,
|
||||
hasConfiguredWebProvider,
|
||||
hasGeminiApiKey,
|
||||
hasPerplexityApiKey,
|
||||
loadWebSearchConfig,
|
||||
saveWebSearchConfig,
|
||||
formatPiWebAccessDoctorLines,
|
||||
getPiWebAccessStatus,
|
||||
getPiWebSearchConfigPath,
|
||||
loadPiWebAccessConfig,
|
||||
type PiWebAccessConfig,
|
||||
type PiWebAccessStatus,
|
||||
type PiWebSearchProvider,
|
||||
type WebSearchConfig,
|
||||
type WebSearchProviderDefinition,
|
||||
type WebSearchProviderId,
|
||||
type WebSearchStatus,
|
||||
} from "./config/feynman-config.js";
|
||||
} from "./pi/web-access.js";
|
||||
|
||||
Reference in New Issue
Block a user