@companion-ai/feynman 0.5.35 → 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,16 @@ 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
+
9
19
  ## v0.5.35 - 2026-10-11
10
20
 
11
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).
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";
@@ -57,12 +58,6 @@ function printHelp(appRoot) {
57
58
  "The open source AI research agent.",
58
59
  "Use `feynman setup` first if this is a new machine.",
59
60
  ]);
60
- printSection("Getting Started");
61
- printInfo("feynman");
62
- printInfo("feynman setup");
63
- printInfo("feynman doctor");
64
- printInfo("feynman model");
65
- printInfo("feynman search status");
66
61
  printSection("Commands");
67
62
  for (const section of cliCommandSections) {
68
63
  for (const command of section.commands) {
@@ -73,7 +68,7 @@ function printHelp(appRoot) {
73
68
  for (const command of workflowCommands) {
74
69
  printHelpLine(formatCliWorkflowUsage(command), command.description);
75
70
  }
76
- printSection("Legacy Flags");
71
+ printSection("Options");
77
72
  for (const flag of legacyFlags) {
78
73
  printHelpLine(flag.usage, flag.description);
79
74
  }
@@ -652,6 +647,10 @@ async function runMain(input) {
652
647
  }
653
648
  explicitModelSpec = canonicalModelSpec;
654
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);
655
654
  const currentModelSpec = getCurrentModelSpec(feynmanSettingsPath);
656
655
  if (await shouldRunInteractiveSetup(explicitModelSpec, currentModelSpec, Boolean(process.stdin.isTTY && process.stdout.isTTY), feynmanAuthPath)) {
657
656
  await runSetup({
@@ -662,15 +661,21 @@ async function runMain(input) {
662
661
  sessionDir,
663
662
  appRoot,
664
663
  defaultThinkingLevel,
664
+ modelOnly: true,
665
665
  });
666
666
  if (!getCurrentModelSpec(feynmanSettingsPath)) {
667
+ console.error(noModelMessage(promptGiven));
668
+ process.exitCode = 1;
667
669
  return;
668
670
  }
669
671
  await ensureFeynmanSettings(feynmanSettingsPath, bundledSettingsPath, appRoot, defaultThinkingLevel, feynmanAuthPath);
670
672
  }
671
- const workflowCommandNames = new Set(readPromptSpecs(appRoot).filter((s) => s.topLevelCli).map((s) => s.name));
672
- const workflowRest = appendWorkflowFlagPositionals(command, rest, values);
673
- 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
+ }
674
679
  // Pi's own session flags pass through; Feynman continues the latest
675
680
  // session only when none of them is given.
676
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@companion-ai/feynman",
3
- "version": "0.5.35",
3
+ "version": "0.5.36",
4
4
  "description": "The open source AI research agent.",
5
5
  "license": "MIT",
6
6
  "type": "module",