@companion-ai/feynman 0.5.35 → 0.5.37
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 +14 -0
- package/dist/cli.js +15 -10
- package/dist/model/commands.js +19 -22
- package/dist/setup/prompts.js +12 -1
- package/dist/setup/setup.js +13 -6
- package/package.json +1 -1
package/RELEASES.md
CHANGED
|
@@ -6,6 +6,20 @@ GitHub release notes are generated from the matching `## vX.Y.Z` section in this
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## v0.5.37 - 2026-10-12
|
|
10
|
+
|
|
11
|
+
- **A sign-in that came back without a code says what to do.** When the browser returned to `feynman model login` or `feynman setup` without an authorization code (seen mostly on Windows), Feynman printed only "Missing authorization code". It now gives the same advice as for `invalid_grant`: sign out of the provider's site or use a private window, then retry, and for ChatGPT it suggests `feynman model login openai-codex`.
|
|
12
|
+
|
|
13
|
+
## v0.5.36 - 2026-10-12
|
|
14
|
+
|
|
15
|
+
- **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.
|
|
16
|
+
- 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.
|
|
17
|
+
- 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.
|
|
18
|
+
- The first launch asks only for the model. Packages, alphaXiv and pandoc stay in `feynman setup`, where alphaXiv now defaults to No.
|
|
19
|
+
- Esc in a provider list goes back to the previous menu instead of ending setup.
|
|
20
|
+
- `feynman -p "…"` with no model now says how to add one, instead of pointing at Pi's `/login` and files inside `node_modules`.
|
|
21
|
+
- `feynman help` drops the duplicate "Getting Started" list and calls the flag section "Options".
|
|
22
|
+
|
|
9
23
|
## v0.5.35 - 2026-10-11
|
|
10
24
|
|
|
11
25
|
- **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("
|
|
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
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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 = [
|
package/dist/model/commands.js
CHANGED
|
@@ -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";
|
|
@@ -742,11 +742,12 @@ export function getOrCreateDeviceId(authPath) {
|
|
|
742
742
|
writeFileSync(settingsPath, `${JSON.stringify({ ...settings, deviceId }, null, 2)}\n`, "utf8");
|
|
743
743
|
return deviceId;
|
|
744
744
|
}
|
|
745
|
-
// A provider answers invalid_grant when it rejects the authorization code,
|
|
746
|
-
//
|
|
747
|
-
//
|
|
745
|
+
// A provider answers invalid_grant when it rejects the authorization code, and
|
|
746
|
+
// the browser comes back with no code when the sign-in was denied or lost a
|
|
747
|
+
// redirect; a stale browser session usually causes both. Signing out there (or
|
|
748
|
+
// using a private window) and retrying fixes it.
|
|
748
749
|
export function describeOAuthLoginFailure(providerId, error) {
|
|
749
|
-
if (!(error instanceof Error) ||
|
|
750
|
+
if (!(error instanceof Error) || !/invalid_grant|Missing authorization code/.test(error.message))
|
|
750
751
|
return error;
|
|
751
752
|
const alternative = providerId === "openai" ? " Or sign in with `feynman model login openai-codex` instead." : "";
|
|
752
753
|
return new Error(`${error.message}\nThe sign-in was rejected, usually because of a stale browser session. Sign out of the provider's site in your browser, or use a private window, then run \`feynman model login ${providerId}\` again.${alternative}`);
|
|
@@ -911,23 +912,19 @@ export async function runModelSetup(settingsPath, authPath) {
|
|
|
911
912
|
"Cancel",
|
|
912
913
|
];
|
|
913
914
|
const selection = await promptChoice("Choose how to configure model access:", choices, 0);
|
|
914
|
-
if (selection ===
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
}
|
|
928
|
-
else {
|
|
929
|
-
printInfo("Setup cancelled.");
|
|
930
|
-
return;
|
|
915
|
+
if (selection === 2)
|
|
916
|
+
throw new SetupCancelledError();
|
|
917
|
+
// Esc inside a provider list goes back to this menu instead of ending setup.
|
|
918
|
+
const done = await (selection === 0
|
|
919
|
+
? loginModelProvider(authPath, undefined, settingsPath)
|
|
920
|
+
: configureApiKeyProvider(authPath)).catch((error) => {
|
|
921
|
+
if (error instanceof SetupCancelledError)
|
|
922
|
+
return false;
|
|
923
|
+
throw error;
|
|
924
|
+
});
|
|
925
|
+
if (!done) {
|
|
926
|
+
status = await collectModelStatus(settingsPath, authPath);
|
|
927
|
+
continue;
|
|
931
928
|
}
|
|
932
929
|
status = await collectModelStatus(settingsPath, authPath);
|
|
933
930
|
if (status.availableModels.length === 0) {
|
package/dist/setup/prompts.js
CHANGED
|
@@ -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());
|
package/dist/setup/setup.js
CHANGED
|
@@ -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?",
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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;
|