Update runtime checks and installer behavior

This commit is contained in:
Advait Paliwal
2026-03-25 13:55:32 -07:00
parent 572de7ba85
commit f6dbacc9d5
24 changed files with 431 additions and 21 deletions

154
.astro/content.d.ts vendored Normal file
View File

@@ -0,0 +1,154 @@
declare module 'astro:content' {
export interface RenderResult {
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
}
interface Render {
'.md': Promise<RenderResult>;
}
export interface RenderedContent {
html: string;
metadata?: {
imagePaths: Array<string>;
[key: string]: unknown;
};
}
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
export type CollectionKey = keyof DataEntryMap;
export type CollectionEntry<C extends CollectionKey> = Flatten<DataEntryMap[C]>;
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
export type ReferenceDataEntry<
C extends CollectionKey,
E extends keyof DataEntryMap[C] = string,
> = {
collection: C;
id: E;
};
export type ReferenceLiveEntry<C extends keyof LiveContentConfig['collections']> = {
collection: C;
id: string;
};
export function getCollection<C extends keyof DataEntryMap, E extends CollectionEntry<C>>(
collection: C,
filter?: (entry: CollectionEntry<C>) => entry is E,
): Promise<E[]>;
export function getCollection<C extends keyof DataEntryMap>(
collection: C,
filter?: (entry: CollectionEntry<C>) => unknown,
): Promise<CollectionEntry<C>[]>;
export function getLiveCollection<C extends keyof LiveContentConfig['collections']>(
collection: C,
filter?: LiveLoaderCollectionFilterType<C>,
): Promise<
import('astro').LiveDataCollectionResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>
>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
entry: ReferenceDataEntry<C, E>,
): E extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
collection: C,
id: E,
): E extends keyof DataEntryMap[C]
? string extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]> | undefined
: Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getLiveEntry<C extends keyof LiveContentConfig['collections']>(
collection: C,
filter: string | LiveLoaderEntryFilterType<C>,
): Promise<import('astro').LiveDataEntryResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>>;
/** Resolve an array of entry references from the same collection */
export function getEntries<C extends keyof DataEntryMap>(
entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
): Promise<CollectionEntry<C>[]>;
export function render<C extends keyof DataEntryMap>(
entry: DataEntryMap[C][string],
): Promise<RenderResult>;
export function reference<
C extends
| keyof DataEntryMap
// Allow generic `string` to avoid excessive type errors in the config
// if `dev` is not running to update as you edit.
// Invalid collection names will be caught at build time.
| (string & {}),
>(
collection: C,
): import('astro/zod').ZodPipe<
import('astro/zod').ZodString,
import('astro/zod').ZodTransform<
C extends keyof DataEntryMap
? {
collection: C;
id: string;
}
: never,
string
>
>;
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
type InferEntrySchema<C extends keyof DataEntryMap> = import('astro/zod').infer<
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
>;
type ExtractLoaderConfig<T> = T extends { loader: infer L } ? L : never;
type InferLoaderSchema<
C extends keyof DataEntryMap,
L = ExtractLoaderConfig<ContentConfig['collections'][C]>,
> = L extends { schema: import('astro/zod').ZodSchema }
? import('astro/zod').infer<L['schema']>
: any;
type DataEntryMap = {
};
type ExtractLoaderTypes<T> = T extends import('astro/loaders').LiveLoader<
infer TData,
infer TEntryFilter,
infer TCollectionFilter,
infer TError
>
? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError }
: { data: never; entryFilter: never; collectionFilter: never; error: never };
type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>['entryFilter'];
type ExtractCollectionFilterType<T> = ExtractLoaderTypes<T>['collectionFilter'];
type ExtractErrorType<T> = ExtractLoaderTypes<T>['error'];
type LiveLoaderDataType<C extends keyof LiveContentConfig['collections']> =
LiveContentConfig['collections'][C]['schema'] extends undefined
? ExtractDataType<LiveContentConfig['collections'][C]['loader']>
: import('astro/zod').infer<
Exclude<LiveContentConfig['collections'][C]['schema'], undefined>
>;
type LiveLoaderEntryFilterType<C extends keyof LiveContentConfig['collections']> =
ExtractEntryFilterType<LiveContentConfig['collections'][C]['loader']>;
type LiveLoaderCollectionFilterType<C extends keyof LiveContentConfig['collections']> =
ExtractCollectionFilterType<LiveContentConfig['collections'][C]['loader']>;
type LiveLoaderErrorType<C extends keyof LiveContentConfig['collections']> = ExtractErrorType<
LiveContentConfig['collections'][C]['loader']
>;
export type ContentConfig = never;
export type LiveContentConfig = never;
}

2
.astro/types.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
/// <reference types="astro/client" />
/// <reference path="content.d.ts" />

View File

@@ -9,7 +9,7 @@ Operating rules:
- 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-research skill for academic paper search, paper reading, paper Q&A, repository inspection, and persistent annotations.
- Use the `alpha` CLI 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.

View File

@@ -1,5 +1,6 @@
{
"packages": [
"npm:@companion-ai/alpha-hub",
"npm:pi-subagents",
"npm:pi-btw",
"npm:pi-docparser",

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
20.18.1

View File

@@ -14,3 +14,39 @@ Use this file to track chronology, not release notes. Keep entries short, factua
- Failed / learned: ...
- Blockers: ...
- Next: ...
### 2026-03-25 00:00 local — scaling-laws
- Objective: Set up a deep research workflow for scaling laws.
- Changed: Created plan artifact at `outputs/.plans/scaling-laws.md`; defined 4 disjoint researcher dimensions and acceptance criteria.
- Verified: Read `CHANGELOG.md` and checked prior memory for related plan `scaling-laws-implications`.
- Failed / learned: No prior run-specific changelog entries existed beyond the template.
- Blockers: Waiting for user confirmation before launching researcher round 1.
- Next: On confirmation, spawn 4 parallel researcher subagents and begin evidence collection.
### 2026-03-25 00:30 local — scaling-laws (T4 inference/time-scale pass)
- Objective: Complete T4 on inference/test-time scaling and reasoning-time compute, scoped to 20232026.
- Changed: Wrote `notes/scaling-laws-research-inference.md`; updated `outputs/.plans/scaling-laws.md` to mark T4 done and log the inference-scaling verification pass.
- Verified: Cross-read 13 primary/official sources covering Tree-of-Thoughts, PRMs, repeated sampling, compute-optimal test-time scaling, provable laws, o1, DeepSeek-R1, s1, verifier failures, Anthropic extended thinking, and OpenAI reasoning API docs.
- Failed / learned: OpenAI blog fetch for `learning-to-reason-with-llms` returned malformed content, so the note leans on the o1 system card and API docs instead of that blog post.
- Blockers: T2 and T5 remain open before final synthesis; no single unified law for inference-time scaling emerged from public sources.
- Next: Complete T5 implications synthesis, then reconcile T3/T4 with foundational T2 before drafting the cited brief.
### 2026-03-25 11:20 local — scaling-laws (T6 draft synthesis)
- Objective: Synthesize the four research notes into a single user-facing draft brief for the scaling-laws workflow.
- Changed: Wrote `outputs/.drafts/scaling-laws-draft.md` with an executive summary, curated reading list, qualitative meta-analysis, core-paper comparison table, explicit training-vs-inference distinction, and numbered inline citations with direct-URL sources.
- Verified: Cross-checked the draft against `notes/scaling-laws-research-foundations.md`, `notes/scaling-laws-research-revisions.md`, `notes/scaling-laws-research-inference.md`, and `notes/scaling-laws-research-implications.md` to ensure the brief explicitly states the literature is too heterogeneous for a pooled effect-size estimate.
- Failed / learned: The requested temp-run `context.md` and `plan.md` were absent, so the synthesis used `outputs/.plans/scaling-laws.md` plus the four note files as the working context.
- Blockers: Citation/claim verification pass still pending; this draft should be treated as pre-verification.
- Next: Run verifier/reviewer passes, then promote the draft into the final cited brief and provenance sidecar.
### 2026-03-25 11:28 local — scaling-laws (final brief + pdf)
- Objective: Deliver a paper guide and qualitative meta-analysis on AI scaling laws.
- Changed: Finalized `outputs/scaling-laws.md` and sidecar `outputs/scaling-laws.provenance.md`; rendered preview PDF at `outputs/scaling-laws.pdf`; updated plan ledger and verification log in `outputs/.plans/scaling-laws.md`.
- Verified: Ran a reviewer pass recorded in `notes/scaling-laws-verification.md`; spot-checked key primary papers via alpha-backed reads for Kaplan 2020, Chinchilla 2022, and Snell 2024; confirmed PDF render output exists.
- Failed / learned: A pooled statistical meta-analysis would be misleading because the literature mixes heterogeneous outcomes, scaling axes, and evaluation regimes; final deliverable uses a qualitative meta-analysis instead.
- Blockers: None for this brief.
- Next: If needed, extend into a narrower sub-survey (e.g. only pretraining laws, only inference-time scaling, or only post-Chinchilla data-quality revisions).

View File

@@ -17,6 +17,8 @@
curl -fsSL https://feynman.is/install | bash
```
If you install via `pnpm` or `bun` instead of the standalone bundle, Feynman requires Node.js `20.18.1` or newer.
---
### What you type → what happens
@@ -92,7 +94,10 @@ Built on [Pi](https://github.com/badlogic/pi-mono) for the agent runtime, [alpha
```bash
git clone https://github.com/getcompanion-ai/feynman.git
cd feynman && pnpm install && pnpm start
cd feynman
nvm use || nvm install
pnpm install
pnpm start
```
[Docs](https://feynman.is/docs) · [MIT License](LICENSE)

View File

@@ -1,8 +1,25 @@
#!/usr/bin/env node
const v = process.versions.node.split(".").map(Number);
if (v[0] < 20) {
console.error(`feynman requires Node.js 20 or later (you have ${process.versions.node})`);
console.error("upgrade: https://nodejs.org or nvm install 20");
const MIN_NODE_VERSION = "20.18.1";
function parseNodeVersion(version) {
const [major = "0", minor = "0", patch = "0"] = version.replace(/^v/, "").split(".");
return {
major: Number.parseInt(major, 10) || 0,
minor: Number.parseInt(minor, 10) || 0,
patch: Number.parseInt(patch, 10) || 0,
};
}
function compareNodeVersions(left, right) {
if (left.major !== right.major) return left.major - right.major;
if (left.minor !== right.minor) return left.minor - right.minor;
return left.patch - right.patch;
}
if (compareNodeVersions(parseNodeVersion(process.versions.node), parseNodeVersion(MIN_NODE_VERSION)) < 0) {
console.error(`feynman requires Node.js ${MIN_NODE_VERSION} or later (detected ${process.versions.node}).`);
console.error("Switch to Node 20 with `nvm install 20 && nvm use 20`, or use the standalone installer:");
console.error("curl -fsSL https://feynman.is/install | bash");
process.exit(1);
}
await import("../scripts/patch-embedded-pi.mjs");

View File

@@ -30,6 +30,7 @@
".env.example"
],
"scripts": {
"preinstall": "node ./scripts/check-node-version.mjs",
"build": "tsc -p tsconfig.build.json",
"build:native-bundle": "node ./scripts/build-native-bundle.mjs",
"dev": "tsx src/index.ts",

View File

@@ -0,0 +1,35 @@
const MIN_NODE_VERSION = "20.18.1";
function parseNodeVersion(version) {
const [major = "0", minor = "0", patch = "0"] = version.replace(/^v/, "").split(".");
return {
major: Number.parseInt(major, 10) || 0,
minor: Number.parseInt(minor, 10) || 0,
patch: Number.parseInt(patch, 10) || 0,
};
}
function compareNodeVersions(left, right) {
if (left.major !== right.major) return left.major - right.major;
if (left.minor !== right.minor) return left.minor - right.minor;
return left.patch - right.patch;
}
function isSupportedNodeVersion(version = process.versions.node) {
return compareNodeVersions(parseNodeVersion(version), parseNodeVersion(MIN_NODE_VERSION)) >= 0;
}
function getUnsupportedNodeVersionLines(version = process.versions.node) {
return [
`feynman requires Node.js ${MIN_NODE_VERSION} or later (detected ${version}).`,
"Switch to Node 20 with `nvm install 20 && nvm use 20`, or use the standalone installer:",
"curl -fsSL https://feynman.is/install | bash",
];
}
if (!isSupportedNodeVersion()) {
for (const line of getUnsupportedNodeVersionLines()) {
console.error(line);
}
process.exit(1);
}

View File

@@ -146,6 +146,16 @@ Workarounds:
Write-Host "$installBinDir is already on PATH."
}
$resolvedCommand = Get-Command feynman -ErrorAction SilentlyContinue
if ($resolvedCommand -and $resolvedCommand.Source -ne $shimPath) {
Write-Warning "Current shell resolves feynman to $($resolvedCommand.Source)"
Write-Host "Run in a new shell, or run: `$env:Path = '$installBinDir;' + `$env:Path"
Write-Host "Then run: feynman"
if ($resolvedCommand.Source -like "*node_modules*@companion-ai*feynman*") {
Write-Host "If that path is an old global npm install, remove it with: npm uninstall -g @companion-ai/feynman"
}
}
Write-Host "Feynman $resolvedVersion installed successfully."
} finally {
if (Test-Path $tmpDir) {

View File

@@ -160,6 +160,27 @@ require_command() {
fi
}
warn_command_conflict() {
expected_path="$INSTALL_BIN_DIR/feynman"
resolved_path="$(command -v feynman 2>/dev/null || true)"
if [ -z "$resolved_path" ]; then
return
fi
if [ "$resolved_path" != "$expected_path" ]; then
step "Warning: current shell resolves feynman to $resolved_path"
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && hash -r && feynman"
step "Or launch directly: $expected_path"
case "$resolved_path" in
*"/node_modules/@companion-ai/feynman/"* | *"/node_modules/.bin/feynman")
step "If that path is an old global npm install, remove it with: npm uninstall -g @companion-ai/feynman"
;;
esac
fi
}
resolve_release_metadata() {
normalized_version="$(normalize_version "$VERSION")"
@@ -290,20 +311,22 @@ add_to_path
case "$path_action" in
added)
step "PATH updated for future shells in $path_profile"
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && feynman"
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && hash -r && feynman"
;;
configured)
step "PATH is already configured for future shells in $path_profile"
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && feynman"
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && hash -r && feynman"
;;
skipped)
step "PATH update skipped"
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && feynman"
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && hash -r && feynman"
;;
*)
step "$INSTALL_BIN_DIR is already on PATH"
step "Run: feynman"
step "Run: hash -r && feynman"
;;
esac
warn_command_conflict
printf 'Feynman %s installed successfully.\n' "$resolved_version"

View File

@@ -11,7 +11,7 @@ Use the `alpha` CLI via bash for all paper research operations.
| Command | Description |
|---------|-------------|
| `alpha search "<query>"` | Search papers. Modes: `--mode semantic`, `--mode keyword`, `--mode agentic` |
| `alpha search "<query>"` | Search papers. Prefer `--mode semantic` by default; use `--mode keyword` only for exact-term lookup and `--mode agentic` for broader retrieval. |
| `alpha get <arxiv-id-or-url>` | Fetch paper content and any local annotation |
| `alpha get --full-text <arxiv-id>` | Get raw full text instead of AI report |
| `alpha ask <arxiv-id> "<question>"` | Ask a question about a paper's PDF |
@@ -22,7 +22,7 @@ Use the `alpha` CLI via bash for all paper research operations.
## Auth
Run `alpha login` to authenticate with alphaXiv. Check status with `alpha status`.
Run `alpha login` to authenticate with alphaXiv. Check status with `feynman alpha status`, or `alpha status` once your installed `alpha-hub` version includes it.
## Examples

View File

@@ -1,6 +1,12 @@
import { main } from "./cli.js";
import { ensureSupportedNodeVersion } from "./system/node-version.js";
main().catch((error) => {
async function run(): Promise<void> {
ensureSupportedNodeVersion();
const { main } = await import("./cli.js");
await main();
}
run().catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exitCode = 1;
});

View File

@@ -2,8 +2,11 @@ import { spawn } from "node:child_process";
import { existsSync } from "node:fs";
import { buildPiArgs, buildPiEnv, type PiRuntimeOptions, resolvePiPaths } from "./runtime.js";
import { ensureSupportedNodeVersion } from "../system/node-version.js";
export async function launchPiChat(options: PiRuntimeOptions): Promise<void> {
ensureSupportedNodeVersion();
const { piCliPath, promisePolyfillPath } = resolvePiPaths(options.appRoot);
if (!existsSync(piCliPath)) {
throw new Error(`Pi CLI not found: ${piCliPath}`);

View File

@@ -1,6 +1,7 @@
import type { PackageSource } from "@mariozechner/pi-coding-agent";
export const CORE_PACKAGE_SOURCES = [
"npm:@companion-ai/alpha-hub",
"npm:pi-subagents",
"npm:pi-btw",
"npm:pi-docparser",

View File

@@ -79,7 +79,8 @@ export function buildPiEnv(options: PiRuntimeOptions): NodeJS.ProcessEnv {
const paths = resolvePiPaths(options.appRoot);
const currentPath = process.env.PATH ?? "";
const binPath = paths.nodeModulesBinPath;
const binEntries = [paths.nodeModulesBinPath, resolve(paths.piWorkspaceNodeModulesPath, ".bin")];
const binPath = binEntries.join(":");
return {
...process.env,

View File

@@ -0,0 +1,40 @@
export const MIN_NODE_VERSION = "20.18.1";
type ParsedNodeVersion = {
major: number;
minor: number;
patch: number;
};
function parseNodeVersion(version: string): ParsedNodeVersion {
const [major = "0", minor = "0", patch = "0"] = version.replace(/^v/, "").split(".");
return {
major: Number.parseInt(major, 10) || 0,
minor: Number.parseInt(minor, 10) || 0,
patch: Number.parseInt(patch, 10) || 0,
};
}
function compareNodeVersions(left: ParsedNodeVersion, right: ParsedNodeVersion): number {
if (left.major !== right.major) return left.major - right.major;
if (left.minor !== right.minor) return left.minor - right.minor;
return left.patch - right.patch;
}
export function isSupportedNodeVersion(version = process.versions.node): boolean {
return compareNodeVersions(parseNodeVersion(version), parseNodeVersion(MIN_NODE_VERSION)) >= 0;
}
export function getUnsupportedNodeVersionLines(version = process.versions.node): string[] {
return [
`feynman requires Node.js ${MIN_NODE_VERSION} or later (detected ${version}).`,
"Switch to Node 20 with `nvm install 20 && nvm use 20`, or use the standalone installer:",
"curl -fsSL https://feynman.is/install | bash",
];
}
export function ensureSupportedNodeVersion(version = process.versions.node): void {
if (!isSupportedNodeVersion(version)) {
throw new Error(getUnsupportedNodeVersionLines(version).join("\n"));
}
}

View File

@@ -0,0 +1,35 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
MIN_NODE_VERSION,
ensureSupportedNodeVersion,
getUnsupportedNodeVersionLines,
isSupportedNodeVersion,
} from "../src/system/node-version.js";
test("isSupportedNodeVersion enforces the exact minimum floor", () => {
assert.equal(isSupportedNodeVersion("20.18.1"), true);
assert.equal(isSupportedNodeVersion("20.19.0"), true);
assert.equal(isSupportedNodeVersion("21.0.0"), true);
assert.equal(isSupportedNodeVersion("20.18.0"), false);
assert.equal(isSupportedNodeVersion("18.17.0"), false);
});
test("ensureSupportedNodeVersion throws a guided upgrade message", () => {
assert.throws(
() => ensureSupportedNodeVersion("18.17.0"),
(error: unknown) =>
error instanceof Error &&
error.message.includes(`Node.js ${MIN_NODE_VERSION}`) &&
error.message.includes("nvm install 20 && nvm use 20") &&
error.message.includes("https://feynman.is/install"),
);
});
test("unsupported version guidance reports the detected version", () => {
const lines = getUnsupportedNodeVersionLines("18.17.0");
assert.equal(lines[0], "feynman requires Node.js 20.18.1 or later (detected 18.17.0).");
assert.ok(lines.some((line) => line.includes("curl -fsSL https://feynman.is/install | bash")));
});

View File

@@ -41,6 +41,7 @@ test("buildPiEnv wires Feynman paths into the Pi environment", () => {
assert.equal(env.FEYNMAN_SESSION_DIR, "/sessions");
assert.equal(env.FEYNMAN_BIN_PATH, "/repo/feynman/bin/feynman.js");
assert.equal(env.FEYNMAN_MEMORY_DIR, "/home/.feynman/memory");
assert.ok(env.PATH?.startsWith("/repo/feynman/node_modules/.bin:/repo/feynman/.feynman/npm/node_modules/.bin:"));
});
test("resolvePiPaths includes the Promise.withResolvers polyfill path", () => {

File diff suppressed because one or more lines are too long

View File

@@ -160,6 +160,27 @@ require_command() {
fi
}
warn_command_conflict() {
expected_path="$INSTALL_BIN_DIR/feynman"
resolved_path="$(command -v feynman 2>/dev/null || true)"
if [ -z "$resolved_path" ]; then
return
fi
if [ "$resolved_path" != "$expected_path" ]; then
step "Warning: current shell resolves feynman to $resolved_path"
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && hash -r && feynman"
step "Or launch directly: $expected_path"
case "$resolved_path" in
*"/node_modules/@companion-ai/feynman/"* | *"/node_modules/.bin/feynman")
step "If that path is an old global npm install, remove it with: npm uninstall -g @companion-ai/feynman"
;;
esac
fi
}
resolve_release_metadata() {
normalized_version="$(normalize_version "$VERSION")"
@@ -290,20 +311,22 @@ add_to_path
case "$path_action" in
added)
step "PATH updated for future shells in $path_profile"
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && feynman"
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && hash -r && feynman"
;;
configured)
step "PATH is already configured for future shells in $path_profile"
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && feynman"
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && hash -r && feynman"
;;
skipped)
step "PATH update skipped"
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && feynman"
step "Run now: export PATH=\"$INSTALL_BIN_DIR:\$PATH\" && hash -r && feynman"
;;
*)
step "$INSTALL_BIN_DIR is already on PATH"
step "Run: feynman"
step "Run: hash -r && feynman"
;;
esac
warn_command_conflict
printf 'Feynman %s installed successfully.\n' "$resolved_version"

View File

@@ -146,6 +146,16 @@ Workarounds:
Write-Host "$installBinDir is already on PATH."
}
$resolvedCommand = Get-Command feynman -ErrorAction SilentlyContinue
if ($resolvedCommand -and $resolvedCommand.Source -ne $shimPath) {
Write-Warning "Current shell resolves feynman to $($resolvedCommand.Source)"
Write-Host "Run in a new shell, or run: `$env:Path = '$installBinDir;' + `$env:Path"
Write-Host "Then run: feynman"
if ($resolvedCommand.Source -like "*node_modules*@companion-ai*feynman*") {
Write-Host "If that path is an old global npm install, remove it with: npm uninstall -g @companion-ai/feynman"
}
}
Write-Host "Feynman $resolvedVersion installed successfully."
} finally {
if (Test-Path $tmpDir) {

View File

@@ -17,6 +17,8 @@ curl -fsSL https://feynman.is/install | bash
The installer detects your OS and architecture automatically. On macOS it supports both Intel and Apple Silicon. On Linux it supports x64 and arm64. The launcher is installed to `~/.local/bin`, the bundled runtime is unpacked into `~/.local/share/feynman`, and your `PATH` is updated when needed.
If you previously installed Feynman via `npm`, `pnpm`, or `bun` and still see local Node.js errors after a curl install, your shell is probably still resolving the older global binary first. Run `which -a feynman`, then `hash -r`, or launch the standalone shim directly with `~/.local/bin/feynman`.
By default, the one-line installer tracks the rolling `edge` channel from `main`.
On **Windows**, open PowerShell as Administrator and run:
@@ -45,7 +47,7 @@ You can also pin an exact version by replacing `stable` with a version such as `
## pnpm
If you already have Node.js 20.18.1+ installed, you can install Feynman globally via `pnpm`:
If you already have Node.js `20.18.1` or newer installed, you can install Feynman globally via `pnpm`:
```bash
pnpm add -g @companion-ai/feynman
@@ -59,6 +61,8 @@ pnpm dlx @companion-ai/feynman
## bun
`bun add -g` and `bunx` still use your local Node runtime for Feynman itself, so the same Node.js `20.18.1+` requirement applies.
```bash
bun add -g @companion-ai/feynman
```
@@ -98,6 +102,7 @@ For contributing or running Feynman from source:
```bash
git clone https://github.com/getcompanion-ai/feynman.git
cd feynman
nvm use || nvm install
pnpm install
pnpm start
```