Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/release-notes-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
## Install

One command on macOS or Linux. No Node.js, no npm, nothing else required.

```sh
curl -fsSL https://raw.githubusercontent.com/final-run/finalrun-agent/main/scripts/install.sh | bash
```

For CI / non-interactive environments:

```sh
curl -fsSL https://raw.githubusercontent.com/final-run/finalrun-agent/main/scripts/install.sh | bash -s -- --cloud-only
```

To force the full local-dev setup (when TTY detection misfires):

```sh
curl -fsSL https://raw.githubusercontent.com/final-run/finalrun-agent/main/scripts/install.sh | bash -s -- --full-setup
```

## Artifacts

| Platform | Binary | Runtime tarball |
|---|---|---|
| macOS Apple Silicon | `finalrun-darwin-arm64` | `finalrun-runtime-<version>-darwin-arm64.tar.gz` |
| macOS Intel | `finalrun-darwin-x64` | `finalrun-runtime-<version>-darwin-x64.tar.gz` |
| Linux x86_64 | `finalrun-linux-x64` | `finalrun-runtime-<version>-linux-x64.tar.gz` |
| Linux ARM64 | `finalrun-linux-arm64` | `finalrun-runtime-<version>-linux-arm64.tar.gz` |

Each artifact ships with a matching `.sha256` sidecar.

## Upgrading from a previous version

```sh
finalrun upgrade
```

The CLI re-runs the install script with sensible defaults (auto-detects whether you previously installed the runtime tarball, preserves your install directory).
177 changes: 177 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
name: Release

# Manual-only trigger. Click "Run workflow" in the Actions tab.
#
# Flow when triggered from main (or any branch):
# 1. Reads version from packages/cli/package.json on the chosen branch.
# 2. Fails fast if the matching tag (v<version>) already exists.
# 3. Builds all 4 Bun binaries (darwin-arm64/x64, linux-x64/arm64).
# 4. Builds all 4 runtime tarballs (per-platform).
# 5. Tags the exact build SHA as v<version> and pushes the tag.
# 6. Creates a GitHub Release at that tag, attaches all artifacts +
# their .sha256 sidecars, marks it as "latest".
#
# To cut a release:
# 1. Bump packages/cli/package.json version on a branch (default main).
# 2. Commit + push to the branch.
# 3. Actions tab → Release → Run workflow → choose branch → Run.
on:
workflow_dispatch:
inputs:
branch:
description: 'Branch to release from'
required: false
default: 'main'

permissions:
contents: write # create tag + GitHub Release

# Serialize releases so two clicks of "Run workflow" can't race on tag
# creation, both build the full artifact set, and then duel on `git push`.
# cancel-in-progress: false → queue rather than abort the in-flight run.
concurrency:
group: release-${{ github.event.inputs.branch }}
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-24.04
outputs:
version: ${{ steps.version.outputs.version }}
tag: ${{ steps.version.outputs.tag }}
sha: ${{ steps.version.outputs.sha }}
is_latest: ${{ steps.version.outputs.is_latest }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch }}
fetch-depth: 0 # need history + tags so we can validate uniqueness

- name: Resolve & validate version
id: version
run: |
set -euo pipefail
VERSION=$(node -p "require('./packages/cli/package.json').version")
TAG="v${VERSION}"
SHA=$(git rev-parse HEAD)
BRANCH="${{ github.event.inputs.branch }}"

# Strict semver: 0.1.7, 1.2.3, 0.2.0-rc.1, 0.2.0-beta.1, etc.
# Rejects empty, non-numeric, or trailing-junk versions.
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
echo "::error::Invalid semver in packages/cli/package.json: '$VERSION'"
exit 1
fi

# Refuse if the tag already exists locally OR on origin (the local
# check alone misses tags pushed by other contributors / CI runs).
if git rev-parse --verify --quiet "refs/tags/$TAG" >/dev/null; then
echo "::error::Tag $TAG already exists locally. Bump packages/cli/package.json or delete the existing tag."
exit 1
fi
if git ls-remote --tags --exit-code origin "refs/tags/$TAG" >/dev/null 2>&1; then
echo "::error::Tag $TAG already exists on origin. Bump packages/cli/package.json."
exit 1
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# Pre-release tags (e.g. 0.2.0-rc.1) should NOT clobber the
# "latest" pointer; only stable releases from main do that.
IS_LATEST="false"
if [[ "$BRANCH" == "main" && ! "$VERSION" =~ - ]]; then
IS_LATEST="true"
fi

echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
echo "is_latest=$IS_LATEST" >> "$GITHUB_OUTPUT"
echo "Releasing $TAG from $BRANCH commit $SHA (is-latest: $IS_LATEST)"

- uses: actions/setup-node@v4
with:
node-version: '20.19'
cache: 'npm'
- run: npm ci

- uses: oven-sh/setup-bun@v2
with:
bun-version: '1.3.11'

- name: Build all workspace dist files
run: |
npm run build --workspace=@finalrun/common
npm run build --workspace=@finalrun/cloud-core
npm run build --workspace=@finalrun/device-node
npm run build --workspace=@finalrun/goal-executor
npm run build --workspace=@finalrun/report-web
npm run build --workspace=@finalrun/finalrun-agent

- name: Build binaries (all 4 targets via Bun cross-compile)
run: ./scripts/build-binary.sh

- name: Build runtime tarballs (all 4 targets)
run: |
for t in darwin-arm64 darwin-x64 linux-x64 linux-arm64; do
npm run build:tarball --workspace=@finalrun/local-runtime -- --target="$t"
done

- name: Stage release artifacts
run: |
mkdir -p release-artifacts
cp dist/binaries/finalrun-* release-artifacts/
cp packages/local-runtime/dist/finalrun-runtime-*.tar.gz* release-artifacts/
ls -la release-artifacts/

- uses: actions/upload-artifact@v4
with:
name: release-artifacts
path: release-artifacts/
retention-days: 7

release:
needs: build
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.build.outputs.sha }}
fetch-depth: 0

- uses: actions/download-artifact@v4
with:
name: release-artifacts
path: release-artifacts/

- name: Tag the build commit and push the tag
env:
TAG: ${{ needs.build.outputs.tag }}
SHA: ${{ needs.build.outputs.sha }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" "$SHA" -m "Release $TAG"
git push origin "$TAG"

- name: Create GitHub Release with all artifacts
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.build.outputs.tag }}
VERSION: ${{ needs.build.outputs.version }}
IS_LATEST: ${{ needs.build.outputs.is_latest }}
run: |
set -euo pipefail
# Pre-releases (e.g. 0.2.0-rc.1) and releases from non-main
# branches don't update the "latest" pointer that install.sh and
# the README badges resolve against.
extra_flags=()
if [ "$IS_LATEST" = "true" ]; then
extra_flags+=(--latest)
else
extra_flags+=(--prerelease)
fi
gh release create "$TAG" \
--title "FinalRun $VERSION" \
--notes-file .github/release-notes-template.md \
"${extra_flags[@]}" \
release-artifacts/*
Comment thread
coderabbitai[bot] marked this conversation as resolved.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,4 @@ pnpm-debug.log*
/.codex

/.gemini
dist/
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
"description": "Monorepo for the finalrun-agent CLI",
"workspaces": [
"packages/common",
"packages/cloud-core",
"packages/device-node",
"packages/goal-executor",
"packages/report-web",
"packages/cli"
"packages/cli",
"packages/local-runtime"
],
"scripts": {
"bootstrap": "npm ci",
Expand Down
21 changes: 17 additions & 4 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
</p>

<p align="center">
<a aria-label="npm package version" href="https://www.npmjs.com/package/@finalrun/finalrun-agent" target="_blank">
<img alt="npm version" src="https://img.shields.io/npm/v/@finalrun/finalrun-agent.svg?style=flat-square&label=npm&labelColor=000000&color=4630EB" />
</a>
<a aria-label="License: Apache-2.0" href="LICENSE">
<img alt="License: Apache-2.0" src="https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=flat-square&color=33CC12" />
</a>
Expand Down Expand Up @@ -55,7 +52,23 @@
curl -fsSL https://raw.githubusercontent.com/final-run/finalrun-agent/main/scripts/install.sh | bash
```

Sets up Node.js, the CLI, AI coding agent skills, and platform tools. Run `finalrun doctor` to verify host readiness.
Downloads a self-contained `finalrun` binary into `~/.finalrun/bin/` and adds it to your PATH. No Node.js required.

On a real terminal, the installer continues with local-dev setup: it downloads the runtime tarball, prompts for Android/iOS, installs host tools (`scrcpy`, Xcode CLT, `applesimutils`), and offers to install the AI agent skills.

In CI or any non-interactive shell, the script auto-detects the environment and stops after installing the binary — only `finalrun cloud …` commands are needed there. To force CI behavior explicitly:

```sh
curl -fsSL https://raw.githubusercontent.com/final-run/finalrun-agent/main/scripts/install.sh | bash -s -- --cloud-only
```

To force the full setup even when TTY detection misfires:

```sh
curl -fsSL https://raw.githubusercontent.com/final-run/finalrun-agent/main/scripts/install.sh | bash -s -- --full-setup
```

After install, run `finalrun doctor` to verify host readiness.


## Write and Run Your First Test Using AI Agents
Expand Down
Loading