@companion-ai/feynman 0.5.43 → 0.5.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/RELEASES.md CHANGED
@@ -6,6 +6,11 @@ GitHub release notes are generated from the matching `## vX.Y.Z` section in this
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## v0.5.44 - 2026-10-13
10
+
11
+ - **`feynman search set` works with every search provider pi-web-access supports.** It accepted only `auto`, `exa`, `perplexity` and `gemini`; it now takes all 39, including Brave, Tavily, Kagi, Serper, You.com, SearXNG and DuckDuckGo, and saves each key where pi-web-access reads it. `feynman search status` and `feynman doctor` name the provider you chose and list every saved key, and no longer describe an outdated automatic search order.
12
+ - **pi-web-access 0.40.0.** Asking about a YouTube or local video works again when a thumbnail comes back with the transcript, excerpts no longer cut an emoji in half, and a new opt-in setting (`fetchContent.redirects: "same-origin"`) refuses redirects to another site.
13
+
9
14
  ## v0.5.43 - 2026-10-13
10
15
 
11
16
  - **Feynman reads and writes Pi's configuration only through Pi.** Settings now go through Pi's settings manager, which locks the file and changes only the keys Feynman sets, so a change Pi or another Feynman window makes at the same moment is no longer overwritten. Model checks, local-model detection and `feynman doctor` read your models through Pi, so comments in `models.json`, a `baseUrl` set on one model, and a key saved with `feynman model login` count the same way they do when Feynman runs.
package/dist/cli.js CHANGED
@@ -24,7 +24,7 @@ import { BUNDLED_PI_PACKAGES, buildPiEnv, findWindowsBash, WINDOWS_BASH_MISSING_
24
24
  import { getConfiguredServiceTier, normalizeServiceTier, setConfiguredServiceTier } from "./model/service-tier.js";
25
25
  import { authenticateModelProvider, getCurrentModelSpec, isLocalModelProvider, loginModelProvider, logoutModelProvider, printModelList, selectDefaultModel, setDefaultModelSpec, } from "./model/commands.js";
26
26
  import { buildModelStatusSnapshotFromRecords, getAuthenticatedModelRecords, isProClassModelSpec, getSupportedModelRecords, } from "./model/catalog.js";
27
- import { clearSearchConfig, printSearchStatus, setSearchProvider } from "./search/commands.js";
27
+ import { clearSearchConfig, printSearchStatus, searchSetUsage, setSearchProvider } from "./search/commands.js";
28
28
  import { fetchLatestFeynmanVersion, getFeynmanUpgradeLines, isNewerVersion } from "./system/self-update.js";
29
29
  import { runDoctor, runStatus } from "./setup/doctor.js";
30
30
  import { setupPreviewDependencies } from "./setup/preview.js";
@@ -307,12 +307,9 @@ function handleSearchCommand(subcommand, args) {
307
307
  return;
308
308
  }
309
309
  if (subcommand === "set") {
310
- const provider = args[0];
311
- const validProviders = ["auto", "perplexity", "exa", "gemini"];
312
- if (!provider || !validProviders.includes(provider)) {
313
- throw new Error("Usage: feynman search set <auto|perplexity|exa|gemini> [api-key]");
314
- }
315
- setSearchProvider(provider, args[1]);
310
+ if (!args[0])
311
+ throw new Error(searchSetUsage());
312
+ setSearchProvider(args[0], args[1]);
316
313
  return;
317
314
  }
318
315
  if (subcommand === "clear") {
@@ -2,13 +2,33 @@ import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { readJsonFile } from "../config/json-file.js";
4
4
  import { getFeynmanAgentDir, getFeynmanHome } from "../config/paths.js";
5
+ // pi-web-access's search providers (SEARCH_PROVIDERS in its gemini-search.ts;
6
+ // a test keeps this list in step with the installed package).
7
+ export const PI_WEB_SEARCH_PROVIDERS = [
8
+ "auto", "all", "openai", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit",
9
+ "tavily", "you", "firecrawl", "jina", "searxng", "degoog", "duckduckgo", "perplexity", "gemini", "kimi", "exa",
10
+ "serpdive", "kagi", "ollama", "anysearch", "xai", "mistral", "brightdata", "serpbase", "serpapi", "serper", "serply",
11
+ "valyu", "bocha", "xcrawl", "baizhi", "zai", "keenable", "ceramic",
12
+ ];
13
+ // The web-search.json field holding a provider's key (searxng takes its base URL);
14
+ // undefined for providers with nothing to set.
15
+ export function searchProviderKeyField(provider) {
16
+ if (provider === "auto" || provider === "all" || provider === "duckduckgo" || provider === "kimi")
17
+ return undefined;
18
+ if (provider === "parallel-mcp")
19
+ return "parallelApiKey";
20
+ if (provider === "searxng")
21
+ return "searxngBaseUrl";
22
+ return `${provider}ApiKey`;
23
+ }
24
+ const ROUTE_LABELS = { auto: "Auto", perplexity: "Perplexity", exa: "Exa", gemini: "Gemini" };
5
25
  // pi-web-access reads web-search.json from PI_CODING_AGENT_DIR.
6
26
  export function getPiWebSearchConfigPath(home) {
7
27
  const feynmanHome = home ? resolve(home, ".feynman") : getFeynmanHome();
8
28
  return resolve(getFeynmanAgentDir(feynmanHome), "web-search.json");
9
29
  }
10
30
  function normalizeProvider(value) {
11
- return value === "auto" || value === "perplexity" || value === "exa" || value === "gemini" ? value : undefined;
31
+ return PI_WEB_SEARCH_PROVIDERS.includes(value) ? value : undefined;
12
32
  }
13
33
  function normalizeWorkflow(value) {
14
34
  return value === "none" || value === "summary-review" ? value : undefined;
@@ -48,29 +68,12 @@ export function savePiWebAccessConfig(updates, configPath = getPiWebSearchConfig
48
68
  // ignore permission errors (best-effort)
49
69
  }
50
70
  }
51
- function formatRouteLabel(provider) {
52
- switch (provider) {
53
- case "perplexity":
54
- return "Perplexity";
55
- case "exa":
56
- return "Exa";
57
- case "gemini":
58
- return "Gemini";
59
- default:
60
- return "Auto";
61
- }
62
- }
63
71
  function formatRouteNote(provider) {
64
- switch (provider) {
65
- case "perplexity":
66
- return "Pi web-access will use Perplexity for search.";
67
- case "exa":
68
- return "Pi web-access will use Exa for search.";
69
- case "gemini":
70
- return "Pi web-access will use Gemini API. Browser-cookie fallback is opt-in.";
71
- default:
72
- return "Pi web-access will try Exa, then Perplexity, then Gemini API. Browser-cookie fallback is opt-in.";
73
- }
72
+ if (provider === "auto")
73
+ return "Pi web-access picks from the search providers you have keys or sign-ins for. Browser-cookie fallback is opt-in.";
74
+ if (provider === "all")
75
+ return "Pi web-access searches every eligible provider at once.";
76
+ return `Pi web-access will use ${ROUTE_LABELS[provider] ?? provider} for search.`;
74
77
  }
75
78
  export function getPiWebAccessStatus(config = loadPiWebAccessConfig(), configPath = getPiWebSearchConfigPath()) {
76
79
  const searchProvider = normalizeProvider(config.searchProvider) ?? normalizeProvider(config.route) ?? normalizeProvider(config.provider) ?? "auto";
@@ -82,6 +85,10 @@ export function getPiWebAccessStatus(config = loadPiWebAccessConfig(), configPat
82
85
  const chromeProfile = normalizeNonEmptyString(config.browserCookies?.profile);
83
86
  const geminiBrowserEnabled = config.allowBrowserCookies === true;
84
87
  const effectiveProvider = searchProvider;
88
+ const configuredKeys = PI_WEB_SEARCH_PROVIDERS.filter((provider) => {
89
+ const field = searchProviderKeyField(provider);
90
+ return field !== undefined && field.endsWith("ApiKey") && Boolean(normalizeNonEmptyString(config[field]));
91
+ }).filter((provider) => provider !== "parallel-mcp");
85
92
  return {
86
93
  configPath,
87
94
  configExists: existsSync(configPath),
@@ -93,7 +100,8 @@ export function getPiWebAccessStatus(config = loadPiWebAccessConfig(), configPat
93
100
  geminiApiConfigured,
94
101
  chromeProfile,
95
102
  geminiBrowserEnabled,
96
- routeLabel: formatRouteLabel(effectiveProvider),
103
+ configuredKeys,
104
+ routeLabel: ROUTE_LABELS[effectiveProvider] ?? effectiveProvider,
97
105
  note: formatRouteNote(effectiveProvider),
98
106
  };
99
107
  }
@@ -104,18 +112,16 @@ export function formatPiWebAccessDoctorLines(status = getPiWebAccessStatus()) {
104
112
  ` search route: ${status.routeLabel}`,
105
113
  ` request route: ${status.requestProvider}`,
106
114
  ` search workflow: ${status.workflow}`,
107
- ` perplexity api: ${status.perplexityConfigured ? "configured" : "not configured"}`,
108
- ` exa api: ${status.exaConfigured ? "configured" : "not configured"}`,
109
- ` gemini api: ${status.geminiApiConfigured ? "configured" : "not configured"}`,
115
+ ` saved api keys: ${status.configuredKeys.join(", ") || "none"}`,
110
116
  ` gemini browser fallback: ${status.geminiBrowserEnabled ? "enabled" : "disabled"}`,
111
117
  ` config path: ${status.configPath}${configPathSuffix}`,
112
118
  ` note: ${status.note}`,
113
119
  ];
114
120
  if (status.geminiBrowserEnabled && status.chromeProfile) {
115
- lines.splice(8, 0, ` gemini browser profile: ${status.chromeProfile}`);
121
+ lines.splice(6, 0, ` gemini browser profile: ${status.chromeProfile}`);
116
122
  }
117
123
  if (!status.configExists) {
118
- lines.push(" hint: run `feynman search set <auto|perplexity|exa|gemini> [api-key]` to configure web search");
124
+ lines.push(" hint: run `feynman search set <provider> [api-key]` to configure web search (`feynman search set` lists providers)");
119
125
  }
120
126
  return lines;
121
127
  }
@@ -1,20 +1,15 @@
1
- import { getPiWebAccessStatus, savePiWebAccessConfig, } from "../pi/web-access.js";
1
+ import { getPiWebAccessStatus, PI_WEB_SEARCH_PROVIDERS, savePiWebAccessConfig, searchProviderKeyField, } from "../pi/web-access.js";
2
2
  import { printInfo } from "../ui/terminal.js";
3
- const SEARCH_PROVIDERS = ["auto", "perplexity", "exa", "gemini"];
4
- const PROVIDER_API_KEY_FIELDS = {
5
- perplexity: "perplexityApiKey",
6
- exa: "exaApiKey",
7
- gemini: "geminiApiKey",
8
- };
3
+ export function searchSetUsage() {
4
+ return `Usage: feynman search set <provider> [api-key]\nProviders: ${PI_WEB_SEARCH_PROVIDERS.join(", ")}`;
5
+ }
9
6
  export function printSearchStatus(status = getPiWebAccessStatus()) {
10
7
  const configPathSuffix = status.configExists ? "" : " (not created yet)";
11
8
  printInfo("Managed by: pi-web-access");
12
9
  printInfo(`Search route: ${status.routeLabel}`);
13
10
  printInfo(`Request route: ${status.requestProvider}`);
14
11
  printInfo(`Search workflow: ${status.workflow}`);
15
- printInfo(`Perplexity API configured: ${status.perplexityConfigured ? "yes" : "no"}`);
16
- printInfo(`Exa API configured: ${status.exaConfigured ? "yes" : "no"}`);
17
- printInfo(`Gemini API configured: ${status.geminiApiConfigured ? "yes" : "no"}`);
12
+ printInfo(`Saved API keys: ${status.configuredKeys.join(", ") || "none"}`);
18
13
  printInfo(`Gemini browser fallback: ${status.geminiBrowserEnabled ? "enabled" : "disabled"}`);
19
14
  if (status.geminiBrowserEnabled && status.chromeProfile) {
20
15
  printInfo(`Gemini browser profile: ${status.chromeProfile}`);
@@ -23,17 +18,16 @@ export function printSearchStatus(status = getPiWebAccessStatus()) {
23
18
  if (!status.configExists) {
24
19
  printInfo("Not configured yet. Run one of:");
25
20
  printInfo(" feynman search set auto");
26
- printInfo(" feynman search set perplexity <api-key>");
27
- printInfo(" feynman search set exa <api-key>");
28
- printInfo(" feynman search set gemini <api-key>");
21
+ printInfo(" feynman search set <provider> <api-key> (for example exa, brave, tavily, perplexity)");
29
22
  }
30
23
  }
31
24
  export function setSearchProvider(provider, apiKey) {
32
- if (!SEARCH_PROVIDERS.includes(provider)) {
33
- throw new Error(`Usage: feynman search set <${SEARCH_PROVIDERS.join("|")}> [api-key]`);
25
+ if (!PI_WEB_SEARCH_PROVIDERS.includes(provider)) {
26
+ throw new Error(searchSetUsage());
34
27
  }
35
- if (apiKey !== undefined && provider === "auto") {
36
- throw new Error("The auto provider does not use an API key. Usage: feynman search set auto");
28
+ const keyField = searchProviderKeyField(provider);
29
+ if (apiKey !== undefined && !keyField) {
30
+ throw new Error(`${provider} takes no API key. Usage: feynman search set ${provider}`);
37
31
  }
38
32
  const updates = {
39
33
  provider,
@@ -42,9 +36,8 @@ export function setSearchProvider(provider, apiKey) {
42
36
  allowBrowserCookies: false,
43
37
  route: undefined,
44
38
  };
45
- const apiKeyField = PROVIDER_API_KEY_FIELDS[provider];
46
- if (apiKeyField && apiKey !== undefined) {
47
- updates[apiKeyField] = apiKey;
39
+ if (keyField && apiKey !== undefined) {
40
+ updates[keyField] = apiKey;
48
41
  }
49
42
  savePiWebAccessConfig(updates);
50
43
  const status = getPiWebAccessStatus();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@companion-ai/feynman",
3
- "version": "0.5.43",
3
+ "version": "0.5.44",
4
4
  "description": "The open source AI research agent.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -86,7 +86,7 @@
86
86
  "pi-btw": "0.8.0",
87
87
  "pi-docparser": "4.0.0",
88
88
  "pi-subagents": "0.78.0",
89
- "pi-web-access": "0.39.0",
89
+ "pi-web-access": "0.40.0",
90
90
  "posthog-node": "^5.53.0"
91
91
  },
92
92
  "peerDependencies": {