Unify installers on tagged releases

This commit is contained in:
Advait Paliwal
2026-03-26 18:17:48 -07:00
parent 4c62e78ca5
commit 404c8b5469
12 changed files with 112 additions and 231 deletions

View File

@@ -52,6 +52,7 @@ jobs:
build-native-bundles: build-native-bundles:
needs: version-check needs: version-check
if: needs.version-check.outputs.should_publish == 'true'
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -97,47 +98,6 @@ jobs:
name: native-${{ matrix.id }} name: native-${{ matrix.id }}
path: dist/release/* path: dist/release/*
release-edge:
needs:
- version-check
- build-native-bundles
if: needs.build-native-bundles.result == 'success'
runs-on: blacksmith-4vcpu-ubuntu-2404
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
path: release-assets
merge-multiple: true
- shell: bash
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ needs.version-check.outputs.version }}
run: |
NOTES="Rolling Feynman bundles from main for the curl/PowerShell installer."
if gh release view edge >/dev/null 2>&1; then
gh release view edge --json assets --jq '.assets[].name' | while IFS= read -r asset; do
[ -n "$asset" ] || continue
gh release delete-asset edge "$asset" --yes
done
gh release upload edge release-assets/*
gh release edit edge \
--title "edge" \
--notes "$NOTES" \
--prerelease \
--draft=false \
--target "$GITHUB_SHA"
else
gh release create edge release-assets/* \
--title "edge" \
--notes "$NOTES" \
--prerelease \
--latest=false \
--target "$GITHUB_SHA"
fi
release-github: release-github:
needs: needs:
- version-check - version-check

View File

@@ -59,3 +59,12 @@ Use this file to track chronology, not release notes. Keep entries short, factua
- Failed / learned: PowerShell installer behavior was not executed locally because PowerShell is not installed in this environment. - Failed / learned: PowerShell installer behavior was not executed locally because PowerShell is not installed in this environment.
- Blockers: None for the Unix installer flow; Windows remains syntax-only by inspection. - Blockers: None for the Unix installer flow; Windows remains syntax-only by inspection.
- Next: If users want this exposed more prominently, add a dedicated docs/reference page and a homepage-specific skills-only CTA instead of a text link. - Next: If users want this exposed more prominently, add a dedicated docs/reference page and a homepage-specific skills-only CTA instead of a text link.
### 2026-03-26 18:08 PDT — installer-release-unification
- Objective: Remove the moving `edge` installer channel and unify installs on tagged releases only.
- Changed: Updated `scripts/install/install.sh`, `scripts/install/install.ps1`, `scripts/install/install-skills.sh`, and `scripts/install/install-skills.ps1` so the default target is the latest tagged release, latest-version resolution uses public GitHub release pages instead of `api.github.com`, and explicit `edge` requests now fail with a removal message; removed the `release-edge` job from `.github/workflows/publish.yml`; updated `README.md` and `website/src/content/docs/getting-started/installation.md`; re-synced `website/public/install*`.
- Verified: Ran `sh -n` on the Unix installer copies; confirmed `sh scripts/install/install.sh edge` and `sh scripts/install/install-skills.sh edge --dir <tmp>` fail with the intended removal message; executed `sh scripts/install/install.sh` into temp dirs and confirmed the installed binary reports `0.2.14`; executed `sh scripts/install/install-skills.sh --dir <tmp>` and confirmed extracted `SKILL.md` files; ran `cd website && npm run build`.
- Failed / learned: The install failure was caused by unauthenticated GitHub API rate limiting on the `edge` path, so renaming channels without removing the API dependency would not have fixed the root cause.
- Blockers: `npm run build` still emits a pre-existing duplicate-content warning for `getting-started/installation`; the build succeeds.
- Next: If desired, remove the now-unused `stable` alias too and clean up the duplicate docs-content warning separately.

View File

@@ -25,6 +25,8 @@ curl -fsSL https://feynman.is/install | bash
irm https://feynman.is/install.ps1 | iex irm https://feynman.is/install.ps1 | iex
``` ```
The one-line installer fetches the latest tagged release. To pin a version, pass it explicitly, for example `curl -fsSL https://feynman.is/install | bash -s -- 0.2.14`.
If you install via `pnpm` or `bun` instead of the standalone bundle, Feynman requires Node.js `20.19.0` or newer. If you install via `pnpm` or `bun` instead of the standalone bundle, Feynman requires Node.js `20.19.0` or newer.
### Skills Only ### Skills Only

View File

@@ -1,5 +1,5 @@
param( param(
[string]$Version = "edge", [string]$Version = "latest",
[ValidateSet("User", "Repo")] [ValidateSet("User", "Repo")]
[string]$Scope = "User", [string]$Scope = "User",
[string]$TargetDir = "" [string]$TargetDir = ""
@@ -11,37 +11,34 @@ function Normalize-Version {
param([string]$RequestedVersion) param([string]$RequestedVersion)
if (-not $RequestedVersion) { if (-not $RequestedVersion) {
return "edge" return "latest"
} }
switch ($RequestedVersion.ToLowerInvariant()) { switch ($RequestedVersion.ToLowerInvariant()) {
"edge" { return "edge" }
"latest" { return "latest" } "latest" { return "latest" }
"stable" { return "latest" } "stable" { return "latest" }
"edge" { throw "The edge channel has been removed. Use the default installer for the latest tagged release or pass an exact version." }
default { return $RequestedVersion.TrimStart("v") } default { return $RequestedVersion.TrimStart("v") }
} }
} }
function Resolve-LatestReleaseVersion {
$page = Invoke-WebRequest -Uri "https://github.com/getcompanion-ai/feynman/releases/latest"
$match = [regex]::Match($page.Content, 'releases/tag/v([0-9][^"''<>\s]*)')
if (-not $match.Success) {
throw "Failed to resolve the latest Feynman release version."
}
return $match.Groups[1].Value
}
function Resolve-VersionMetadata { function Resolve-VersionMetadata {
param([string]$RequestedVersion) param([string]$RequestedVersion)
$normalizedVersion = Normalize-Version -RequestedVersion $RequestedVersion $normalizedVersion = Normalize-Version -RequestedVersion $RequestedVersion
if ($normalizedVersion -eq "edge") {
return [PSCustomObject]@{
ResolvedVersion = "edge"
GitRef = "main"
DownloadUrl = "https://github.com/getcompanion-ai/feynman/archive/refs/heads/main.zip"
}
}
if ($normalizedVersion -eq "latest") { if ($normalizedVersion -eq "latest") {
$release = Invoke-RestMethod -Uri "https://api.github.com/repos/getcompanion-ai/feynman/releases/latest" $resolvedVersion = Resolve-LatestReleaseVersion
if (-not $release.tag_name) {
throw "Failed to resolve the latest Feynman release version."
}
$resolvedVersion = $release.tag_name.TrimStart("v")
} else { } else {
$resolvedVersion = $normalizedVersion $resolvedVersion = $normalizedVersion
} }

View File

@@ -2,7 +2,7 @@
set -eu set -eu
VERSION="edge" VERSION="latest"
SCOPE="${FEYNMAN_SKILLS_SCOPE:-user}" SCOPE="${FEYNMAN_SKILLS_SCOPE:-user}"
TARGET_DIR="${FEYNMAN_SKILLS_DIR:-}" TARGET_DIR="${FEYNMAN_SKILLS_DIR:-}"
@@ -12,12 +12,16 @@ step() {
normalize_version() { normalize_version() {
case "$1" in case "$1" in
"" | edge) "")
printf 'edge\n' printf 'latest\n'
;; ;;
latest | stable) latest | stable)
printf 'latest\n' printf 'latest\n'
;; ;;
edge)
echo "The edge channel has been removed. Use the default installer for the latest tagged release or pass an exact version." >&2
exit 1
;;
v*) v*)
printf '%s\n' "${1#v}" printf '%s\n' "${1#v}"
;; ;;
@@ -73,14 +77,9 @@ download_text() {
resolve_version() { resolve_version() {
normalized_version="$(normalize_version "$VERSION")" normalized_version="$(normalize_version "$VERSION")"
if [ "$normalized_version" = "edge" ]; then
printf 'edge\nmain\n'
return
fi
if [ "$normalized_version" = "latest" ]; then if [ "$normalized_version" = "latest" ]; then
release_json="$(download_text "https://api.github.com/repos/getcompanion-ai/feynman/releases/latest")" release_page="$(download_text "https://github.com/getcompanion-ai/feynman/releases/latest")"
resolved_version="$(printf '%s\n' "$release_json" | sed -n 's/.*"tag_name":[[:space:]]*"v\([^"]*\)".*/\1/p' | head -n 1)" resolved_version="$(printf '%s\n' "$release_page" | sed -n 's@.*releases/tag/v\([0-9][^"<>[:space:]]*\).*@\1@p' | head -n 1)"
if [ -z "$resolved_version" ]; then if [ -z "$resolved_version" ]; then
echo "Failed to resolve the latest Feynman release version." >&2 echo "Failed to resolve the latest Feynman release version." >&2
@@ -125,7 +124,7 @@ while [ $# -gt 0 ]; do
;; ;;
--dir) --dir)
if [ $# -lt 2 ]; then if [ $# -lt 2 ]; then
echo "Usage: install-skills.sh [edge|stable|latest|<version>] [--user|--repo] [--dir <path>]" >&2 echo "Usage: install-skills.sh [stable|latest|<version>] [--user|--repo] [--dir <path>]" >&2
exit 1 exit 1
fi fi
TARGET_DIR="$2" TARGET_DIR="$2"
@@ -136,7 +135,7 @@ while [ $# -gt 0 ]; do
;; ;;
*) *)
echo "Unknown argument: $1" >&2 echo "Unknown argument: $1" >&2
echo "Usage: install-skills.sh [edge|stable|latest|<version>] [--user|--repo] [--dir <path>]" >&2 echo "Usage: install-skills.sh [stable|latest|<version>] [--user|--repo] [--dir <path>]" >&2
exit 1 exit 1
;; ;;
esac esac

View File

@@ -1,5 +1,5 @@
param( param(
[string]$Version = "edge" [string]$Version = "latest"
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
@@ -8,17 +8,27 @@ function Normalize-Version {
param([string]$RequestedVersion) param([string]$RequestedVersion)
if (-not $RequestedVersion) { if (-not $RequestedVersion) {
return "edge" return "latest"
} }
switch ($RequestedVersion.ToLowerInvariant()) { switch ($RequestedVersion.ToLowerInvariant()) {
"edge" { return "edge" }
"latest" { return "latest" } "latest" { return "latest" }
"stable" { return "latest" } "stable" { return "latest" }
"edge" { throw "The edge channel has been removed. Use the default installer for the latest tagged release or pass an exact version." }
default { return $RequestedVersion.TrimStart("v") } default { return $RequestedVersion.TrimStart("v") }
} }
} }
function Resolve-LatestReleaseVersion {
$page = Invoke-WebRequest -Uri "https://github.com/getcompanion-ai/feynman/releases/latest"
$match = [regex]::Match($page.Content, 'releases/tag/v([0-9][^"''<>\s]*)')
if (-not $match.Success) {
throw "Failed to resolve the latest Feynman release version."
}
return $match.Groups[1].Value
}
function Resolve-ReleaseMetadata { function Resolve-ReleaseMetadata {
param( param(
[string]$RequestedVersion, [string]$RequestedVersion,
@@ -28,34 +38,8 @@ function Resolve-ReleaseMetadata {
$normalizedVersion = Normalize-Version -RequestedVersion $RequestedVersion $normalizedVersion = Normalize-Version -RequestedVersion $RequestedVersion
if ($normalizedVersion -eq "edge") {
$release = Invoke-RestMethod -Uri "https://api.github.com/repos/getcompanion-ai/feynman/releases/tags/edge"
$asset = $release.assets | Where-Object { $_.name -like "feynman-*-$AssetTarget.$BundleExtension" } | Select-Object -First 1
if (-not $asset) {
throw "Failed to resolve the latest Feynman edge bundle."
}
$archiveName = $asset.name
$suffix = ".$BundleExtension"
$bundleName = $archiveName.Substring(0, $archiveName.Length - $suffix.Length)
$resolvedVersion = $bundleName.Substring("feynman-".Length)
$resolvedVersion = $resolvedVersion.Substring(0, $resolvedVersion.Length - ("-$AssetTarget").Length)
return [PSCustomObject]@{
ResolvedVersion = $resolvedVersion
BundleName = $bundleName
ArchiveName = $archiveName
DownloadUrl = $asset.browser_download_url
}
}
if ($normalizedVersion -eq "latest") { if ($normalizedVersion -eq "latest") {
$release = Invoke-RestMethod -Uri "https://api.github.com/repos/getcompanion-ai/feynman/releases/latest" $resolvedVersion = Resolve-LatestReleaseVersion
if (-not $release.tag_name) {
throw "Failed to resolve the latest Feynman release version."
}
$resolvedVersion = $release.tag_name.TrimStart("v")
} else { } else {
$resolvedVersion = $normalizedVersion $resolvedVersion = $normalizedVersion
} }

View File

@@ -2,7 +2,7 @@
set -eu set -eu
VERSION="${1:-edge}" VERSION="${1:-latest}"
INSTALL_BIN_DIR="${FEYNMAN_INSTALL_BIN_DIR:-$HOME/.local/bin}" INSTALL_BIN_DIR="${FEYNMAN_INSTALL_BIN_DIR:-$HOME/.local/bin}"
INSTALL_APP_DIR="${FEYNMAN_INSTALL_APP_DIR:-$HOME/.local/share/feynman}" INSTALL_APP_DIR="${FEYNMAN_INSTALL_APP_DIR:-$HOME/.local/share/feynman}"
SKIP_PATH_UPDATE="${FEYNMAN_INSTALL_SKIP_PATH_UPDATE:-0}" SKIP_PATH_UPDATE="${FEYNMAN_INSTALL_SKIP_PATH_UPDATE:-0}"
@@ -54,12 +54,16 @@ run_with_spinner() {
normalize_version() { normalize_version() {
case "$1" in case "$1" in
"" | edge) "")
printf 'edge\n' printf 'latest\n'
;; ;;
latest | stable) latest | stable)
printf 'latest\n' printf 'latest\n'
;; ;;
edge)
echo "The edge channel has been removed. Use the default installer for the latest tagged release or pass an exact version." >&2
exit 1
;;
v*) v*)
printf '%s\n' "${1#v}" printf '%s\n' "${1#v}"
;; ;;
@@ -184,36 +188,9 @@ warn_command_conflict() {
resolve_release_metadata() { resolve_release_metadata() {
normalized_version="$(normalize_version "$VERSION")" normalized_version="$(normalize_version "$VERSION")"
if [ "$normalized_version" = "edge" ]; then
release_json="$(download_text "https://api.github.com/repos/getcompanion-ai/feynman/releases/tags/edge")"
asset_url=""
for candidate in $(printf '%s\n' "$release_json" | sed -n 's/.*"browser_download_url":[[:space:]]*"\([^"]*\)".*/\1/p'); do
case "$candidate" in
*/feynman-*-${asset_target}.${archive_extension})
asset_url="$candidate"
break
;;
esac
done
if [ -z "$asset_url" ]; then
echo "Failed to resolve the latest Feynman edge bundle." >&2
exit 1
fi
archive_name="${asset_url##*/}"
bundle_name="${archive_name%.$archive_extension}"
resolved_version="${bundle_name#feynman-}"
resolved_version="${resolved_version%-${asset_target}}"
printf '%s\n%s\n%s\n%s\n' "$resolved_version" "$bundle_name" "$archive_name" "$asset_url"
return
fi
if [ "$normalized_version" = "latest" ]; then if [ "$normalized_version" = "latest" ]; then
release_json="$(download_text "https://api.github.com/repos/getcompanion-ai/feynman/releases/latest")" release_page="$(download_text "https://github.com/getcompanion-ai/feynman/releases/latest")"
resolved_version="$(printf '%s\n' "$release_json" | sed -n 's/.*"tag_name":[[:space:]]*"v\([^"]*\)".*/\1/p' | head -n 1)" resolved_version="$(printf '%s\n' "$release_page" | sed -n 's@.*releases/tag/v\([0-9][^"<>[:space:]]*\).*@\1@p' | head -n 1)"
if [ -z "$resolved_version" ]; then if [ -z "$resolved_version" ]; then
echo "Failed to resolve the latest Feynman release version." >&2 echo "Failed to resolve the latest Feynman release version." >&2

View File

@@ -2,7 +2,7 @@
set -eu set -eu
VERSION="${1:-edge}" VERSION="${1:-latest}"
INSTALL_BIN_DIR="${FEYNMAN_INSTALL_BIN_DIR:-$HOME/.local/bin}" INSTALL_BIN_DIR="${FEYNMAN_INSTALL_BIN_DIR:-$HOME/.local/bin}"
INSTALL_APP_DIR="${FEYNMAN_INSTALL_APP_DIR:-$HOME/.local/share/feynman}" INSTALL_APP_DIR="${FEYNMAN_INSTALL_APP_DIR:-$HOME/.local/share/feynman}"
SKIP_PATH_UPDATE="${FEYNMAN_INSTALL_SKIP_PATH_UPDATE:-0}" SKIP_PATH_UPDATE="${FEYNMAN_INSTALL_SKIP_PATH_UPDATE:-0}"
@@ -54,12 +54,16 @@ run_with_spinner() {
normalize_version() { normalize_version() {
case "$1" in case "$1" in
"" | edge) "")
printf 'edge\n' printf 'latest\n'
;; ;;
latest | stable) latest | stable)
printf 'latest\n' printf 'latest\n'
;; ;;
edge)
echo "The edge channel has been removed. Use the default installer for the latest tagged release or pass an exact version." >&2
exit 1
;;
v*) v*)
printf '%s\n' "${1#v}" printf '%s\n' "${1#v}"
;; ;;
@@ -184,36 +188,9 @@ warn_command_conflict() {
resolve_release_metadata() { resolve_release_metadata() {
normalized_version="$(normalize_version "$VERSION")" normalized_version="$(normalize_version "$VERSION")"
if [ "$normalized_version" = "edge" ]; then
release_json="$(download_text "https://api.github.com/repos/getcompanion-ai/feynman/releases/tags/edge")"
asset_url=""
for candidate in $(printf '%s\n' "$release_json" | sed -n 's/.*"browser_download_url":[[:space:]]*"\([^"]*\)".*/\1/p'); do
case "$candidate" in
*/feynman-*-${asset_target}.${archive_extension})
asset_url="$candidate"
break
;;
esac
done
if [ -z "$asset_url" ]; then
echo "Failed to resolve the latest Feynman edge bundle." >&2
exit 1
fi
archive_name="${asset_url##*/}"
bundle_name="${archive_name%.$archive_extension}"
resolved_version="${bundle_name#feynman-}"
resolved_version="${resolved_version%-${asset_target}}"
printf '%s\n%s\n%s\n%s\n' "$resolved_version" "$bundle_name" "$archive_name" "$asset_url"
return
fi
if [ "$normalized_version" = "latest" ]; then if [ "$normalized_version" = "latest" ]; then
release_json="$(download_text "https://api.github.com/repos/getcompanion-ai/feynman/releases/latest")" release_page="$(download_text "https://github.com/getcompanion-ai/feynman/releases/latest")"
resolved_version="$(printf '%s\n' "$release_json" | sed -n 's/.*"tag_name":[[:space:]]*"v\([^"]*\)".*/\1/p' | head -n 1)" resolved_version="$(printf '%s\n' "$release_page" | sed -n 's@.*releases/tag/v\([0-9][^"<>[:space:]]*\).*@\1@p' | head -n 1)"
if [ -z "$resolved_version" ]; then if [ -z "$resolved_version" ]; then
echo "Failed to resolve the latest Feynman release version." >&2 echo "Failed to resolve the latest Feynman release version." >&2

View File

@@ -2,7 +2,7 @@
set -eu set -eu
VERSION="edge" VERSION="latest"
SCOPE="${FEYNMAN_SKILLS_SCOPE:-user}" SCOPE="${FEYNMAN_SKILLS_SCOPE:-user}"
TARGET_DIR="${FEYNMAN_SKILLS_DIR:-}" TARGET_DIR="${FEYNMAN_SKILLS_DIR:-}"
@@ -12,12 +12,16 @@ step() {
normalize_version() { normalize_version() {
case "$1" in case "$1" in
"" | edge) "")
printf 'edge\n' printf 'latest\n'
;; ;;
latest | stable) latest | stable)
printf 'latest\n' printf 'latest\n'
;; ;;
edge)
echo "The edge channel has been removed. Use the default installer for the latest tagged release or pass an exact version." >&2
exit 1
;;
v*) v*)
printf '%s\n' "${1#v}" printf '%s\n' "${1#v}"
;; ;;
@@ -73,14 +77,9 @@ download_text() {
resolve_version() { resolve_version() {
normalized_version="$(normalize_version "$VERSION")" normalized_version="$(normalize_version "$VERSION")"
if [ "$normalized_version" = "edge" ]; then
printf 'edge\nmain\n'
return
fi
if [ "$normalized_version" = "latest" ]; then if [ "$normalized_version" = "latest" ]; then
release_json="$(download_text "https://api.github.com/repos/getcompanion-ai/feynman/releases/latest")" release_page="$(download_text "https://github.com/getcompanion-ai/feynman/releases/latest")"
resolved_version="$(printf '%s\n' "$release_json" | sed -n 's/.*"tag_name":[[:space:]]*"v\([^"]*\)".*/\1/p' | head -n 1)" resolved_version="$(printf '%s\n' "$release_page" | sed -n 's@.*releases/tag/v\([0-9][^"<>[:space:]]*\).*@\1@p' | head -n 1)"
if [ -z "$resolved_version" ]; then if [ -z "$resolved_version" ]; then
echo "Failed to resolve the latest Feynman release version." >&2 echo "Failed to resolve the latest Feynman release version." >&2
@@ -125,7 +124,7 @@ while [ $# -gt 0 ]; do
;; ;;
--dir) --dir)
if [ $# -lt 2 ]; then if [ $# -lt 2 ]; then
echo "Usage: install-skills.sh [edge|stable|latest|<version>] [--user|--repo] [--dir <path>]" >&2 echo "Usage: install-skills.sh [stable|latest|<version>] [--user|--repo] [--dir <path>]" >&2
exit 1 exit 1
fi fi
TARGET_DIR="$2" TARGET_DIR="$2"
@@ -136,7 +135,7 @@ while [ $# -gt 0 ]; do
;; ;;
*) *)
echo "Unknown argument: $1" >&2 echo "Unknown argument: $1" >&2
echo "Usage: install-skills.sh [edge|stable|latest|<version>] [--user|--repo] [--dir <path>]" >&2 echo "Usage: install-skills.sh [stable|latest|<version>] [--user|--repo] [--dir <path>]" >&2
exit 1 exit 1
;; ;;
esac esac

View File

@@ -1,5 +1,5 @@
param( param(
[string]$Version = "edge", [string]$Version = "latest",
[ValidateSet("User", "Repo")] [ValidateSet("User", "Repo")]
[string]$Scope = "User", [string]$Scope = "User",
[string]$TargetDir = "" [string]$TargetDir = ""
@@ -11,37 +11,34 @@ function Normalize-Version {
param([string]$RequestedVersion) param([string]$RequestedVersion)
if (-not $RequestedVersion) { if (-not $RequestedVersion) {
return "edge" return "latest"
} }
switch ($RequestedVersion.ToLowerInvariant()) { switch ($RequestedVersion.ToLowerInvariant()) {
"edge" { return "edge" }
"latest" { return "latest" } "latest" { return "latest" }
"stable" { return "latest" } "stable" { return "latest" }
"edge" { throw "The edge channel has been removed. Use the default installer for the latest tagged release or pass an exact version." }
default { return $RequestedVersion.TrimStart("v") } default { return $RequestedVersion.TrimStart("v") }
} }
} }
function Resolve-LatestReleaseVersion {
$page = Invoke-WebRequest -Uri "https://github.com/getcompanion-ai/feynman/releases/latest"
$match = [regex]::Match($page.Content, 'releases/tag/v([0-9][^"''<>\s]*)')
if (-not $match.Success) {
throw "Failed to resolve the latest Feynman release version."
}
return $match.Groups[1].Value
}
function Resolve-VersionMetadata { function Resolve-VersionMetadata {
param([string]$RequestedVersion) param([string]$RequestedVersion)
$normalizedVersion = Normalize-Version -RequestedVersion $RequestedVersion $normalizedVersion = Normalize-Version -RequestedVersion $RequestedVersion
if ($normalizedVersion -eq "edge") {
return [PSCustomObject]@{
ResolvedVersion = "edge"
GitRef = "main"
DownloadUrl = "https://github.com/getcompanion-ai/feynman/archive/refs/heads/main.zip"
}
}
if ($normalizedVersion -eq "latest") { if ($normalizedVersion -eq "latest") {
$release = Invoke-RestMethod -Uri "https://api.github.com/repos/getcompanion-ai/feynman/releases/latest" $resolvedVersion = Resolve-LatestReleaseVersion
if (-not $release.tag_name) {
throw "Failed to resolve the latest Feynman release version."
}
$resolvedVersion = $release.tag_name.TrimStart("v")
} else { } else {
$resolvedVersion = $normalizedVersion $resolvedVersion = $normalizedVersion
} }

View File

@@ -1,5 +1,5 @@
param( param(
[string]$Version = "edge" [string]$Version = "latest"
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
@@ -8,17 +8,27 @@ function Normalize-Version {
param([string]$RequestedVersion) param([string]$RequestedVersion)
if (-not $RequestedVersion) { if (-not $RequestedVersion) {
return "edge" return "latest"
} }
switch ($RequestedVersion.ToLowerInvariant()) { switch ($RequestedVersion.ToLowerInvariant()) {
"edge" { return "edge" }
"latest" { return "latest" } "latest" { return "latest" }
"stable" { return "latest" } "stable" { return "latest" }
"edge" { throw "The edge channel has been removed. Use the default installer for the latest tagged release or pass an exact version." }
default { return $RequestedVersion.TrimStart("v") } default { return $RequestedVersion.TrimStart("v") }
} }
} }
function Resolve-LatestReleaseVersion {
$page = Invoke-WebRequest -Uri "https://github.com/getcompanion-ai/feynman/releases/latest"
$match = [regex]::Match($page.Content, 'releases/tag/v([0-9][^"''<>\s]*)')
if (-not $match.Success) {
throw "Failed to resolve the latest Feynman release version."
}
return $match.Groups[1].Value
}
function Resolve-ReleaseMetadata { function Resolve-ReleaseMetadata {
param( param(
[string]$RequestedVersion, [string]$RequestedVersion,
@@ -28,34 +38,8 @@ function Resolve-ReleaseMetadata {
$normalizedVersion = Normalize-Version -RequestedVersion $RequestedVersion $normalizedVersion = Normalize-Version -RequestedVersion $RequestedVersion
if ($normalizedVersion -eq "edge") {
$release = Invoke-RestMethod -Uri "https://api.github.com/repos/getcompanion-ai/feynman/releases/tags/edge"
$asset = $release.assets | Where-Object { $_.name -like "feynman-*-$AssetTarget.$BundleExtension" } | Select-Object -First 1
if (-not $asset) {
throw "Failed to resolve the latest Feynman edge bundle."
}
$archiveName = $asset.name
$suffix = ".$BundleExtension"
$bundleName = $archiveName.Substring(0, $archiveName.Length - $suffix.Length)
$resolvedVersion = $bundleName.Substring("feynman-".Length)
$resolvedVersion = $resolvedVersion.Substring(0, $resolvedVersion.Length - ("-$AssetTarget").Length)
return [PSCustomObject]@{
ResolvedVersion = $resolvedVersion
BundleName = $bundleName
ArchiveName = $archiveName
DownloadUrl = $asset.browser_download_url
}
}
if ($normalizedVersion -eq "latest") { if ($normalizedVersion -eq "latest") {
$release = Invoke-RestMethod -Uri "https://api.github.com/repos/getcompanion-ai/feynman/releases/latest" $resolvedVersion = Resolve-LatestReleaseVersion
if (-not $release.tag_name) {
throw "Failed to resolve the latest Feynman release version."
}
$resolvedVersion = $release.tag_name.TrimStart("v")
} else { } else {
$resolvedVersion = $normalizedVersion $resolvedVersion = $normalizedVersion
} }

View File

@@ -19,8 +19,6 @@ The installer detects your OS and architecture automatically. On macOS it suppor
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`. 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: On **Windows**, open PowerShell as Administrator and run:
```powershell ```powershell
@@ -59,22 +57,20 @@ Or install them repo-locally:
These installers download only the `skills/` tree from the Feynman repository. They do not install the Feynman terminal, bundled Node runtime, auth storage, or Pi packages. These installers download only the `skills/` tree from the Feynman repository. They do not install the Feynman terminal, bundled Node runtime, auth storage, or Pi packages.
## Stable or pinned releases ## Pinned releases
If you want the latest tagged release instead of the rolling `edge` channel: The one-line installer already targets the latest tagged release. To pin an exact version, pass it explicitly:
```bash ```bash
curl -fsSL https://feynman.is/install | bash -s -- stable curl -fsSL https://feynman.is/install | bash -s -- 0.2.14
``` ```
On Windows: On Windows:
```powershell ```powershell
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version stable & ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version 0.2.14
``` ```
You can also pin an exact version by replacing `stable` with a version such as `0.2.14`.
## pnpm ## pnpm
If you already have Node.js `20.19.0` or newer installed, you can install Feynman globally via `pnpm`: If you already have Node.js `20.19.0` or newer installed, you can install Feynman globally via `pnpm`: