Fix release installers and package manager fallbacks

This commit is contained in:
Advait Paliwal
2026-03-24 19:10:21 -07:00
parent 762ca66a68
commit 3ee6ff4199
9 changed files with 162 additions and 33 deletions

View File

@@ -39,20 +39,29 @@ jobs:
publish-npm:
needs: version-check
if: needs.version-check.outputs.should_publish == 'true'
if: needs.version-check.outputs.should_build_release == 'true'
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: read
steps:
- name: Skip npm publish
if: needs.version-check.outputs.should_publish != 'true'
run: echo "Skipping npm publish; version ${{ needs.version-check.outputs.version }} is already on npm."
- uses: actions/checkout@v6
if: needs.version-check.outputs.should_publish == 'true'
- uses: actions/setup-node@v5
if: needs.version-check.outputs.should_publish == 'true'
with:
node-version: 24.14.0
registry-url: https://registry.npmjs.org
- run: npm ci --ignore-scripts
- run: npm run build
- run: npm test
- if: needs.version-check.outputs.should_publish == 'true'
run: npm ci --ignore-scripts
- if: needs.version-check.outputs.should_publish == 'true'
run: npm run build
- if: needs.version-check.outputs.should_publish == 'true'
run: npm test
- run: npm publish --access public
if: needs.version-check.outputs.should_publish == 'true'
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -109,7 +118,7 @@ jobs:
- version-check
- publish-npm
- build-native-bundles
if: always() && needs.version-check.outputs.should_build_release == 'true' && needs.build-native-bundles.result == 'success' && (needs.publish-npm.result == 'success' || needs.publish-npm.result == 'skipped')
if: needs.version-check.outputs.should_build_release == 'true' && needs.build-native-bundles.result == 'success' && needs.publish-npm.result == 'success'
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write

View File

@@ -26,6 +26,12 @@ irm https://feynman.is/install.ps1 | iex
```bash
# npm fallback
npm install -g @companion-ai/feynman
# pnpm fallback
pnpm add -g @companion-ai/feynman
# bun fallback
bun add -g @companion-ai/feynman
```
Then run `feynman setup` to configure your model and get started.

View File

@@ -45,7 +45,23 @@ New-Item -ItemType Directory -Path $tmpDir | Out-Null
try {
$archivePath = Join-Path $tmpDir $archiveName
Write-Host "==> Downloading $archiveName"
Invoke-WebRequest -Uri $downloadUrl -OutFile $archivePath
try {
Invoke-WebRequest -Uri $downloadUrl -OutFile $archivePath
} catch {
throw @"
Failed to download $archiveName from:
$downloadUrl
The win32-$archSuffix bundle is missing from the GitHub release.
This usually means the release exists, but not all platform bundles were uploaded.
Workarounds:
- try again after the release finishes publishing
- install via npm instead: npm install -g @companion-ai/feynman
- install via pnpm instead: pnpm add -g @companion-ai/feynman
- install via bun instead: bun add -g @companion-ai/feynman
"@
}
New-Item -ItemType Directory -Path $installRoot -Force | Out-Null
if (Test-Path $bundleDir) {

View File

@@ -222,7 +222,22 @@ trap cleanup EXIT INT TERM
archive_path="$tmp_dir/$archive_name"
step "Downloading ${archive_name}"
download_file "$download_url" "$archive_path"
if ! download_file "$download_url" "$archive_path"; then
cat >&2 <<EOF
Failed to download ${archive_name} from:
${download_url}
The ${asset_target} bundle is missing from the GitHub release.
This usually means the release exists, but not all platform bundles were uploaded.
Workarounds:
- try again after the release finishes publishing
- install via npm instead: npm install -g @companion-ai/feynman
- install via pnpm instead: pnpm add -g @companion-ai/feynman
- install via bun instead: bun add -g @companion-ai/feynman
EOF
exit 1
fi
mkdir -p "$INSTALL_APP_DIR"
rm -rf "$INSTALL_APP_DIR/$bundle_name"

View File

@@ -56,6 +56,61 @@ const workspaceDir = resolve(appRoot, ".feynman", "npm");
const workspacePackageJsonPath = resolve(workspaceDir, "package.json");
const workspaceArchivePath = resolve(appRoot, ".feynman", "runtime-workspace.tgz");
function createInstallCommand(packageManager, packageSpecs) {
switch (packageManager) {
case "npm":
return ["install", "--prefer-offline", "--no-audit", "--no-fund", "--loglevel", "error", ...packageSpecs];
case "pnpm":
return ["add", "--prefer-offline", "--reporter", "silent", ...packageSpecs];
case "bun":
return ["add", "--silent", ...packageSpecs];
default:
throw new Error(`Unsupported package manager: ${packageManager}`);
}
}
let cachedPackageManager = undefined;
function resolvePackageManager() {
if (cachedPackageManager !== undefined) return cachedPackageManager;
const requested = process.env.FEYNMAN_PACKAGE_MANAGER?.trim();
const candidates = requested ? [requested] : ["npm", "pnpm", "bun"];
for (const candidate of candidates) {
if (resolveExecutable(candidate)) {
cachedPackageManager = candidate;
return candidate;
}
}
cachedPackageManager = null;
return null;
}
function installWorkspacePackages(packageSpecs) {
const packageManager = resolvePackageManager();
if (!packageManager) {
process.stderr.write(
"[feynman] no supported package manager found; install npm, pnpm, or bun, or set FEYNMAN_PACKAGE_MANAGER.\n",
);
return false;
}
const result = spawnSync(packageManager, createInstallCommand(packageManager, packageSpecs), {
cwd: workspaceDir,
stdio: ["ignore", "ignore", "pipe"],
timeout: 300000,
});
if (result.status !== 0) {
if (result.stderr?.length) process.stderr.write(result.stderr);
process.stderr.write(`[feynman] ${packageManager} failed while setting up bundled packages.\n`);
return false;
}
return true;
}
function parsePackageName(spec) {
const match = spec.match(/^(@?[^@]+(?:\/[^@]+)?)(?:@.+)?$/);
return match?.[1] ?? spec;
@@ -81,17 +136,7 @@ function restorePackagedWorkspace(packageSpecs) {
}
function refreshPackagedWorkspace(packageSpecs) {
const result = spawnSync("npm", ["install", "--prefer-offline", "--no-audit", "--no-fund", "--loglevel", "error", "--prefix", workspaceDir, ...packageSpecs], {
stdio: ["ignore", "ignore", "pipe"],
timeout: 300000,
});
if (result.status !== 0) {
if (result.stderr?.length) process.stderr.write(result.stderr);
return false;
}
return true;
return installWorkspacePackages(packageSpecs);
}
function resolveExecutable(name, fallbackPaths = []) {
@@ -139,17 +184,13 @@ function ensurePackageWorkspace() {
process.stderr.write(`\r${frames[frame++ % frames.length]} setting up feynman... ${elapsed}s`);
}, 80);
const result = spawnSync("npm", ["install", "--prefer-offline", "--no-audit", "--no-fund", "--loglevel", "error", "--prefix", workspaceDir, ...packageSpecs], {
stdio: ["ignore", "ignore", "pipe"],
timeout: 300000,
});
const result = installWorkspacePackages(packageSpecs);
clearInterval(spinner);
const elapsed = Math.round((Date.now() - start) / 1000);
if (result.status !== 0) {
if (!result) {
process.stderr.write(`\r✗ setup failed (${elapsed}s)\n`);
if (result.stderr?.length) process.stderr.write(result.stderr);
} else {
process.stderr.write(`\r✓ feynman ready (${elapsed}s)\n`);
}

View File

@@ -222,7 +222,22 @@ trap cleanup EXIT INT TERM
archive_path="$tmp_dir/$archive_name"
step "Downloading ${archive_name}"
download_file "$download_url" "$archive_path"
if ! download_file "$download_url" "$archive_path"; then
cat >&2 <<EOF
Failed to download ${archive_name} from:
${download_url}
The ${asset_target} bundle is missing from the GitHub release.
This usually means the release exists, but not all platform bundles were uploaded.
Workarounds:
- try again after the release finishes publishing
- install via npm instead: npm install -g @companion-ai/feynman
- install via pnpm instead: pnpm add -g @companion-ai/feynman
- install via bun instead: bun add -g @companion-ai/feynman
EOF
exit 1
fi
mkdir -p "$INSTALL_APP_DIR"
rm -rf "$INSTALL_APP_DIR/$bundle_name"

View File

@@ -45,7 +45,23 @@ New-Item -ItemType Directory -Path $tmpDir | Out-Null
try {
$archivePath = Join-Path $tmpDir $archiveName
Write-Host "==> Downloading $archiveName"
Invoke-WebRequest -Uri $downloadUrl -OutFile $archivePath
try {
Invoke-WebRequest -Uri $downloadUrl -OutFile $archivePath
} catch {
throw @"
Failed to download $archiveName from:
$downloadUrl
The win32-$archSuffix bundle is missing from the GitHub release.
This usually means the release exists, but not all platform bundles were uploaded.
Workarounds:
- try again after the release finishes publishing
- install via npm instead: npm install -g @companion-ai/feynman
- install via pnpm instead: pnpm add -g @companion-ai/feynman
- install via bun instead: bun add -g @companion-ai/feynman
"@
}
New-Item -ItemType Directory -Path $installRoot -Force | Out-Null
if (Test-Path $bundleDir) {

View File

@@ -15,8 +15,7 @@ Feynman stores all configuration and state under `~/.feynman/`. This directory i
├── web-search.json # Web search routing config
├── auth/ # OAuth tokens and API keys
├── sessions/ # Persisted conversation history
── packages/ # Installed optional packages
└── bin/ # Binary (when installed via the native installer)
── packages/ # Installed optional packages
```
The `settings.json` file is the primary configuration file. It is created by `feynman setup` and can be edited manually. A typical configuration looks like:

View File

@@ -5,7 +5,7 @@ section: Getting Started
order: 1
---
Feynman ships as a standalone binary for macOS and Linux, and as an npm package for all platforms including Windows. The recommended approach is the one-line installer, which downloads a prebuilt native binary with zero dependencies.
Feynman ships as a standalone runtime bundle for macOS, Linux, and Windows, and as an npm package for environments where Node.js is already installed. The recommended approach is the one-line installer, which downloads a prebuilt native bundle with zero external runtime dependencies.
## One-line installer (recommended)
@@ -15,7 +15,7 @@ On **macOS or Linux**, open a terminal and run:
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 binary is installed to `~/.feynman/bin` and added to your `PATH`.
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.
On **Windows**, open PowerShell as Administrator and run:
@@ -23,23 +23,35 @@ On **Windows**, open PowerShell as Administrator and run:
irm https://feynman.is/install.ps1 | iex
```
This installs the native Windows binary and adds Feynman to your user `PATH`. You can re-run either installer at any time to update to the latest version.
This installs the Windows runtime bundle under `%LOCALAPPDATA%\Programs\feynman`, adds its launcher to your user `PATH`, and lets you re-run the installer at any time to update.
## npm / npx
If you already have Node.js 18+ installed, you can install Feynman globally via npm:
If you already have Node.js 20.18.1+ installed, you can install Feynman globally via npm:
```bash
npm install -g @companion-ai/feynman
```
`pnpm` and `bun` are supported as well:
```bash
pnpm add -g @companion-ai/feynman
bun add -g @companion-ai/feynman
```
Or run it directly without installing:
```bash
npx @companion-ai/feynman
```
The npm distribution bundles the same core runtime as the native installer but depends on Node.js being present on your system. The native installer is preferred because it ships a self-contained binary with faster startup.
```bash
pnpm dlx @companion-ai/feynman
bunx @companion-ai/feynman
```
The npm distribution ships the same core application but depends on Node.js being present on your system. The standalone installer is preferred because it bundles its own Node runtime and works without a separate Node installation.
## Post-install setup