@companion-ai/feynman 0.5.34 → 0.5.36

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,23 @@ GitHub release notes are generated from the matching `## vX.Y.Z` section in this
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## v0.5.36 - 2026-10-12
10
+
11
+ - **A new user gets to a working model instead of a dead end.** Launching `feynman` with no model opens a menu to set one up.
12
+ - Typing a question there moved the selection onto Cancel: the menu treated `h`, `j`, `k` and `l` as arrow keys. Only the arrow keys move it now.
13
+ - Cancel used to go on to optional packages, an alphaXiv sign-in that defaulted to Yes, and pandoc, then said "Feynman is ready" with no model. It now exits and says how to add a model, and that your question was not sent.
14
+ - The first launch asks only for the model. Packages, alphaXiv and pandoc stay in `feynman setup`, where alphaXiv now defaults to No.
15
+ - Esc in a provider list goes back to the previous menu instead of ending setup.
16
+ - `feynman -p "…"` with no model now says how to add one, instead of pointing at Pi's `/login` and files inside `node_modules`.
17
+ - `feynman help` drops the duplicate "Getting Started" list and calls the flag section "Options".
18
+
19
+ ## v0.5.35 - 2026-10-11
20
+
21
+ - **PubMed group authors keep their affiliations.** Consortium and study-group authors came back with no affiliations; they now keep them, as individual authors do. Thanks to @KennyMcSimpson (#335).
22
+ - **pi-subagents 0.77.0.** Subagent tools cost the lead agent far fewer tokens per request (the `subagent` tool's definition shrank from about 18,700 to 3,300 characters), and long subagent reports arrive as a preview plus the file path instead of the full text. Updating Feynman while a session is running no longer breaks every subagent launch in that session. A wrong agent or model name now comes back with suggestions.
23
+ - **pi-btw 0.8.0.** The `/btw` side-chat composer completes slash commands, and side threads no longer fail with Anthropic's "references unknown tool" error.
24
+ - `feynman -v` prints the version, and `--json`, `--work-dir` and `--path` now point to `--mode json` and `--cwd <dir>`.
25
+
9
26
  ## v0.5.34 - 2026-10-09
10
27
 
11
28
  - **OpenAlex lookups that miss now say why.** Looking up a name or title where OpenAlex expects an ID (for example `openalex_get_work:Attention is all you need` or `author: Geoffrey Hinton`), or an ID that does not exist, returned OpenAlex's raw HTML "404 Not Found" page. The error now says no such work, author or source exists and names the search command to use instead, so the agent recovers on its next call.
package/bin/feynman.js CHANGED
@@ -34,7 +34,7 @@ if (compareNodeVersions(parseNodeVersion(process.versions.node), parseNodeVersio
34
34
  const here = import.meta.dirname;
35
35
 
36
36
  // `--version` needs only package.json; answer it without loading the CLI.
37
- if (process.argv.length === 3 && process.argv[2] === "--version") {
37
+ if (process.argv.length === 3 && (process.argv[2] === "--version" || process.argv[2] === "-v")) {
38
38
  const { readFileSync } = await import("node:fs");
39
39
  console.log(JSON.parse(readFileSync(resolve(here, "..", "package.json"), "utf8")).version);
40
40
  process.exit(0);
package/dist/cli.js CHANGED
@@ -29,6 +29,7 @@ import { fetchLatestFeynmanVersion, getFeynmanUpgradeLines, isNewerVersion } fro
29
29
  import { runDoctor, runStatus } from "./setup/doctor.js";
30
30
  import { setupPreviewDependencies } from "./setup/preview.js";
31
31
  import { runSetup } from "./setup/setup.js";
32
+ import { noModelMessage } from "./setup/prompts.js";
32
33
  import { captureTelemetryEvent, captureTelemetryException, getCliTelemetryMetadata, initializePostHogTelemetry, shutdownPostHogTelemetry, telemetryErrorProperties, telemetryFirstRunNotice, } from "./telemetry/posthog.js";
33
34
  import { ASH, printAsciiHeader, printInfo, printPanel, printSection, RESET, SAGE } from "./ui/terminal.js";
34
35
  import { createModelRuntime } from "./model/registry.js";
@@ -39,6 +40,9 @@ const UNKNOWN_OPTION_HINTS = {
39
40
  "--setup": "feynman setup",
40
41
  "--list-models": "feynman model list",
41
42
  "--new": "feynman --new-session",
43
+ "--json": "feynman --mode json",
44
+ "--work-dir": "feynman --cwd <dir>",
45
+ "--path": "feynman --cwd <dir>",
42
46
  };
43
47
  // Removed commands fail instead of falling through to a chat prompt.
44
48
  const REMOVED_COMMANDS = new Set(["jobs", "paper", "rank", "serve", "watch"]);
@@ -51,15 +55,9 @@ function printHelpLine(usage, description) {
51
55
  function printHelp(appRoot) {
52
56
  const workflowCommands = readPromptSpecs(appRoot).filter((command) => command.section === "Research Workflows" && command.topLevelCli);
53
57
  printAsciiHeader([
54
- "Research-first agent shell built on Pi.",
58
+ "The open source AI research agent.",
55
59
  "Use `feynman setup` first if this is a new machine.",
56
60
  ]);
57
- printSection("Getting Started");
58
- printInfo("feynman");
59
- printInfo("feynman setup");
60
- printInfo("feynman doctor");
61
- printInfo("feynman model");
62
- printInfo("feynman search status");
63
61
  printSection("Commands");
64
62
  for (const section of cliCommandSections) {
65
63
  for (const command of section.commands) {
@@ -70,7 +68,7 @@ function printHelp(appRoot) {
70
68
  for (const command of workflowCommands) {
71
69
  printHelpLine(formatCliWorkflowUsage(command), command.description);
72
70
  }
73
- printSection("Legacy Flags");
71
+ printSection("Options");
74
72
  for (const flag of legacyFlags) {
75
73
  printHelpLine(flag.usage, flag.description);
76
74
  }
@@ -465,7 +463,7 @@ async function runMain(input) {
465
463
  export: { type: "string" },
466
464
  fork: { type: "string" },
467
465
  help: { type: "boolean", short: "h" },
468
- version: { type: "boolean" },
466
+ version: { type: "boolean", short: "v" },
469
467
  "alpha-login": { type: "boolean" },
470
468
  "alpha-logout": { type: "boolean" },
471
469
  "alpha-status": { type: "boolean" },
@@ -649,6 +647,10 @@ async function runMain(input) {
649
647
  }
650
648
  explicitModelSpec = canonicalModelSpec;
651
649
  }
650
+ const workflowCommandNames = new Set(readPromptSpecs(appRoot).filter((s) => s.topLevelCli).map((s) => s.name));
651
+ const workflowRest = appendWorkflowFlagPositionals(command, rest, values);
652
+ const promptOptions = resolvePiPromptOptions(command, workflowRest, values.prompt, workflowCommandNames);
653
+ const promptGiven = Boolean(promptOptions.oneShotPrompt || promptOptions.initialPrompt);
652
654
  const currentModelSpec = getCurrentModelSpec(feynmanSettingsPath);
653
655
  if (await shouldRunInteractiveSetup(explicitModelSpec, currentModelSpec, Boolean(process.stdin.isTTY && process.stdout.isTTY), feynmanAuthPath)) {
654
656
  await runSetup({
@@ -659,15 +661,21 @@ async function runMain(input) {
659
661
  sessionDir,
660
662
  appRoot,
661
663
  defaultThinkingLevel,
664
+ modelOnly: true,
662
665
  });
663
666
  if (!getCurrentModelSpec(feynmanSettingsPath)) {
667
+ console.error(noModelMessage(promptGiven));
668
+ process.exitCode = 1;
664
669
  return;
665
670
  }
666
671
  await ensureFeynmanSettings(feynmanSettingsPath, bundledSettingsPath, appRoot, defaultThinkingLevel, feynmanAuthPath);
667
672
  }
668
- const workflowCommandNames = new Set(readPromptSpecs(appRoot).filter((s) => s.topLevelCli).map((s) => s.name));
669
- const workflowRest = appendWorkflowFlagPositionals(command, rest, values);
670
- const promptOptions = resolvePiPromptOptions(command, workflowRest, values.prompt, workflowCommandNames);
673
+ if (!explicitModelSpec && mode !== "rpc" && !(process.stdin.isTTY && process.stdout.isTTY)
674
+ && (await getAuthenticatedModelRecords(feynmanAuthPath)).length === 0) {
675
+ console.error(noModelMessage(promptGiven));
676
+ process.exitCode = 1;
677
+ return;
678
+ }
671
679
  // Pi's own session flags pass through; Feynman continues the latest
672
680
  // session only when none of them is given.
673
681
  const piArgs = [
@@ -4,7 +4,7 @@ import { randomUUID } from "node:crypto";
4
4
  import { dirname, resolve } from "node:path";
5
5
  import { promisify } from "node:util";
6
6
  import { readJson } from "../pi/settings.js";
7
- import { promptChoice, promptSecret, promptSelect, promptText, validateApiKeyInput } from "../setup/prompts.js";
7
+ import { promptChoice, promptSecret, promptSelect, promptText, SetupCancelledError, validateApiKeyInput } from "../setup/prompts.js";
8
8
  import { openUrl } from "../system/open-url.js";
9
9
  import { printInfo, printSection, printSuccess, printWarning } from "../ui/terminal.js";
10
10
  import { buildModelStatusSnapshotFromRecords, chooseRecommendedModel, getAuthenticatedModelRecords, getAvailableModelRecords, getSupportedModelRecords, isProClassModelSpec, } from "./catalog.js";
@@ -911,23 +911,19 @@ export async function runModelSetup(settingsPath, authPath) {
911
911
  "Cancel",
912
912
  ];
913
913
  const selection = await promptChoice("Choose how to configure model access:", choices, 0);
914
- if (selection === 0) {
915
- const loggedIn = await loginModelProvider(authPath, undefined, settingsPath);
916
- if (!loggedIn) {
917
- status = await collectModelStatus(settingsPath, authPath);
918
- continue;
919
- }
920
- }
921
- else if (selection === 1) {
922
- const configured = await configureApiKeyProvider(authPath);
923
- if (!configured) {
924
- status = await collectModelStatus(settingsPath, authPath);
925
- continue;
926
- }
927
- }
928
- else {
929
- printInfo("Setup cancelled.");
930
- return;
914
+ if (selection === 2)
915
+ throw new SetupCancelledError();
916
+ // Esc inside a provider list goes back to this menu instead of ending setup.
917
+ const done = await (selection === 0
918
+ ? loginModelProvider(authPath, undefined, settingsPath)
919
+ : configureApiKeyProvider(authPath)).catch((error) => {
920
+ if (error instanceof SetupCancelledError)
921
+ return false;
922
+ throw error;
923
+ });
924
+ if (!done) {
925
+ status = await collectModelStatus(settingsPath, authPath);
926
+ continue;
931
927
  }
932
928
  status = await collectModelStatus(settingsPath, authPath);
933
929
  if (status.availableModels.length === 0) {
@@ -1,4 +1,8 @@
1
- import { confirm as clackConfirm, intro as clackIntro, isCancel, multiselect as clackMultiselect, outro as clackOutro, password as clackPassword, select as clackSelect, text as clackText, } from "@clack/prompts";
1
+ import { confirm as clackConfirm, intro as clackIntro, isCancel, multiselect as clackMultiselect, outro as clackOutro, password as clackPassword, select as clackSelect, text as clackText, settings as clackSettings, } from "@clack/prompts";
2
+ // clack treats h/j/k/l as arrow keys, so a question typed into a setup menu
3
+ // moved the selection (often onto Cancel). Only the arrow keys move it.
4
+ for (const key of ["h", "j", "k", "l"])
5
+ clackSettings.aliases.delete(key);
2
6
  export class SetupCancelledError extends Error {
3
7
  constructor(message = "setup cancelled") {
4
8
  super(message);
@@ -14,6 +18,13 @@ export function nonInteractiveTerminalMessage(env = process.env, platform = proc
14
18
  "To skip the prompts, set your provider's API key variable (for example OPENAI_API_KEY or ANTHROPIC_API_KEY) and run `feynman model set <provider/model>`.",
15
19
  ].join("\n");
16
20
  }
21
+ export function noModelMessage(promptGiven = false) {
22
+ return [
23
+ "No model is set up yet, so Feynman can't answer.",
24
+ "Sign in with `feynman model login`, or set ANTHROPIC_API_KEY or OPENAI_API_KEY, then run feynman again.",
25
+ ...(promptGiven ? ["Your question was not sent."] : []),
26
+ ].join("\n");
27
+ }
17
28
  function ensureInteractiveTerminal() {
18
29
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
19
30
  throw new Error(nonInteractiveTerminalMessage());
@@ -8,7 +8,7 @@ import { buildModelStatusSnapshotFromRecords, getAvailableModelRecords, getSuppo
8
8
  import { PANDOC_FALLBACK_PATHS, resolveExecutable } from "../system/executables.js";
9
9
  import { setupPreviewDependencies } from "./preview.js";
10
10
  import { printInfo, printSection, printSuccess } from "../ui/terminal.js";
11
- import { isInteractiveTerminal, promptConfirm, promptIntro, promptMultiSelect, promptOutro, SetupCancelledError, } from "./prompts.js";
11
+ import { isInteractiveTerminal, noModelMessage, promptConfirm, promptIntro, promptMultiSelect, promptOutro, SetupCancelledError, } from "./prompts.js";
12
12
  function printNonInteractiveSetupGuidance() {
13
13
  printInfo("Non-interactive terminal. Use explicit commands:");
14
14
  printInfo(" feynman model login <provider>");
@@ -49,7 +49,7 @@ async function maybeLoginAlpha() {
49
49
  printInfo("alphaXiv already configured.");
50
50
  return;
51
51
  }
52
- const shouldLogin = await promptConfirm("Connect alphaXiv now?", true);
52
+ const shouldLogin = await promptConfirm("Connect alphaXiv now?", false);
53
53
  if (!shouldLogin) {
54
54
  printInfo("Skipping alphaXiv login for now.");
55
55
  return;
@@ -88,9 +88,11 @@ export async function runSetup(options) {
88
88
  try {
89
89
  await promptIntro("Feynman setup");
90
90
  await runModelSetup(options.settingsPath, options.authPath);
91
- await maybeInstallOptionalPackages(options);
92
- await maybeLoginAlpha();
93
- await maybeInstallPreviewDependencies();
91
+ if (!options.modelOnly) {
92
+ await maybeInstallOptionalPackages(options);
93
+ await maybeLoginAlpha();
94
+ await maybeInstallPreviewDependencies();
95
+ }
94
96
  await ensureFeynmanSettings(options.settingsPath, options.bundledSettingsPath, options.appRoot, options.defaultThinkingLevel ?? "medium", options.authPath);
95
97
  const modelStatus = buildModelStatusSnapshotFromRecords(await getSupportedModelRecords(options.authPath), await getAvailableModelRecords(options.authPath), getCurrentModelSpec(options.settingsPath));
96
98
  printSection("Ready");
@@ -105,7 +107,12 @@ export async function runSetup(options) {
105
107
  }
106
108
  catch (error) {
107
109
  if (error instanceof SetupCancelledError) {
108
- printInfo("Setup cancelled.");
110
+ // The first launch reports the missing model itself.
111
+ if (!options.modelOnly) {
112
+ printInfo("Setup cancelled.");
113
+ if (!getCurrentModelSpec(options.settingsPath))
114
+ printInfo(noModelMessage());
115
+ }
109
116
  return;
110
117
  }
111
118
  throw error;
@@ -133,11 +133,11 @@ function parseAuthors(value: unknown): Array<Record<string, unknown>> {
133
133
  return listValue(value).map((item) => {
134
134
  const author = recordValue(item);
135
135
  const collective = textValue(author.CollectiveName);
136
- if (collective) return { collectiveName: collective, affiliations: [] };
137
136
  const affiliations = listValue(author.AffiliationInfo)
138
137
  .flatMap((info) => listValue(recordValue(info).Affiliation))
139
138
  .map((affiliation) => textValue(affiliation))
140
139
  .filter((affiliation): affiliation is string => Boolean(affiliation));
140
+ if (collective) return { collectiveName: collective, affiliations };
141
141
  return prune({
142
142
  lastName: textValue(author.LastName),
143
143
  foreName: textValue(author.ForeName),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@companion-ai/feynman",
3
- "version": "0.5.34",
4
- "description": "Research-first CLI agent built on Pi and alphaXiv",
3
+ "version": "0.5.36",
4
+ "description": "The open source AI research agent.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "engines": {
@@ -80,9 +80,9 @@
80
80
  "@clack/prompts": "^1.8.1",
81
81
  "@companion-ai/alpha-hub": "0.1.6",
82
82
  "fast-xml-parser": "5.11.1",
83
- "pi-btw": "0.7.1",
83
+ "pi-btw": "0.8.0",
84
84
  "pi-docparser": "4.0.0",
85
- "pi-subagents": "0.76.1",
85
+ "pi-subagents": "0.77.0",
86
86
  "pi-web-access": "0.38.0",
87
87
  "posthog-node": "^5.53.0"
88
88
  },