Update runtime checks and installer behavior
This commit is contained in:
154
.astro/content.d.ts
vendored
Normal file
154
.astro/content.d.ts
vendored
Normal 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
2
.astro/types.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="astro/client" />
|
||||
/// <reference path="content.d.ts" />
|
||||
@@ -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.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"packages": [
|
||||
"npm:@companion-ai/alpha-hub",
|
||||
"npm:pi-subagents",
|
||||
"npm:pi-btw",
|
||||
"npm:pi-docparser",
|
||||
|
||||
36
CHANGELOG.md
36
CHANGELOG.md
@@ -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 2023–2026.
|
||||
- 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).
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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",
|
||||
|
||||
35
scripts/check-node-version.mjs
Normal file
35
scripts/check-node-version.mjs
Normal 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);
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
10
src/index.ts
10
src/index.ts
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
40
src/system/node-version.ts
Normal file
40
src/system/node-version.ts
Normal 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"));
|
||||
}
|
||||
}
|
||||
35
tests/node-version.test.ts
Normal file
35
tests/node-version.test.ts
Normal 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")));
|
||||
});
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user