Host the site and release downloads on byre.run via Cloudflare

This commit is contained in:
Ian Gulliver
2026-10-19 16:59:59 -07:00
parent b9a988e2d5
commit 0be09b3279
9 changed files with 244 additions and 11 deletions
+4
View File
@@ -0,0 +1,4 @@
{
"account_id": "96e4d3199efaa07ddc5c33e0e2d59c01",
"project_name": "byre"
}
+1 -1
View File
@@ -24,7 +24,7 @@ What to expect:
## Releasing ## Releasing
A release is a deploy that is uploaded, and goes to every Mac through the app's updater. Make one only when the owner asks. It needs a `v<version>` tag on a commit of main, pushed by itself (`git push origin v<version>`, never moving or recreating a tag), and a clean checkout at that tag on iso, where `mac/release.sh <notes>` checks the tag, runs `mac/package.sh`, and publishes the image, its `.sha256` and `appcast.xml` as the release on g.fc.run, with the Markdown file `<notes>` as its notes. `docs/operations.md`, "Releases", has the details. A release is a deploy that is uploaded, and goes to every Mac through the app's updater. Make one only when the owner asks. It needs a `v<version>` tag on a commit of main, pushed by itself (`git push origin v<version>`, never moving or recreating a tag), and a clean checkout at that tag on iso, where `mac/release.sh <notes>` checks the tag, runs `mac/package.sh`, uploads the image, its `.sha256` and `appcast.xml` to `dl.byre.run`, and creates the release on g.fc.run with copies of the image and its `.sha256`, and the Markdown file `<notes>` as its notes. `docs/operations.md`, "Releases", has the details.
Before running it, write the notes yourself from the diffs since the previous tag (`git describe --tags --abbrev=0 v<version>^` names it; read `git diff <previous>..v<version>`, not just the commit subjects). Write the file outside the checkout, so the tree stays clean. It is a changelog for people who use byre, in this shape: Before running it, write the notes yourself from the diffs since the previous tag (`git describe --tags --abbrev=0 v<version>^` names it; read `git diff <previous>..v<version>`, not just the commit subjects). Write the file outside the checkout, so the tree stays clean. It is a changelog for people who use byre, in this shape:
+1 -1
View File
@@ -24,7 +24,7 @@ macOS, [Tailscale](https://tailscale.com) on every Mac, and on the Mac that runs
## Installing ## Installing
1. Download the latest `byre-<version>.dmg` from [the releases page](https://g.fc.run/ai/byre/releases) and drag `Byre.app` to Applications, on every Mac you'll use byre from. 1. Download [byre](https://dl.byre.run/byre.dmg) from [byre.run](https://byre.run) and drag `Byre.app` to Applications, on every Mac you'll use byre from.
2. On the Mac that runs the server, open byre and click **Run byre on this Mac**. It first checks for `git`, Tailscale (signed in, with HTTPS certificates turned on) and at least one of `claude`, `codex` and `agy`, and lists anything missing with how to install it. Then it installs the server as a launchd service, using your login shell's `PATH` to find `claude`, `codex`, `agy`, `git` and `npx`, serves it through `tailscale serve`, and connects to it. The Server tab shows its URL, turns it off and on, and lists the tools it found and how to install the missing ones. 2. On the Mac that runs the server, open byre and click **Run byre on this Mac**. It first checks for `git`, Tailscale (signed in, with HTTPS certificates turned on) and at least one of `claude`, `codex` and `agy`, and lists anything missing with how to install it. Then it installs the server as a launchd service, using your login shell's `PATH` to find `claude`, `codex`, `agy`, `git` and `npx`, serves it through `tailscale serve`, and connects to it. The Server tab shows its URL, turns it off and on, and lists the tools it found and how to install the missing ones.
3. Open byre on each other Mac; it picks up the server's URL through iCloud, or enter that URL. 3. Open byre on each other Mac; it picks up the server's URL through iCloud, or enter that URL.
+15 -4
View File
@@ -127,22 +127,33 @@ A new build reaches the service by replacing `/Applications/Byre.app`: `mac/depl
## Releases ## Releases
A release is one notarized disk image, `byre-<version>.dmg`, holding `Byre.app`: the macOS app, with the server (`Contents/Helpers/byre`), `byre-hands` (`Contents/Helpers/byre-hands`) and `byre-update` (`Contents/Helpers/byre-update`) inside it. Every Mac installs the same app; the server's Mac also runs `byre install` from it (see "Running as a service"). Releases are on the repo's releases page on g.fc.run, `https://g.fc.run/ai/byre/releases`. A release is one notarized disk image, `byre-<version>.dmg`, holding `Byre.app`: the macOS app, with the server (`Contents/Helpers/byre`), `byre-hands` (`Contents/Helpers/byre-hands`) and `byre-update` (`Contents/Helpers/byre-update`) inside it. Every Mac installs the same app; the server's Mac also runs `byre install` from it (see "Running as a service"). Release images are served from `https://dl.byre.run/` (see "Website"), and each release's notes are on the repo's releases page on g.fc.run, `https://g.fc.run/ai/byre/releases`.
`mac/package.sh` builds one for whatever the checkout holds, into `mac/.build/release/<version>/`, `<version>` being the bundle's `CFBundleShortVersionString`, and prints the image's path. It builds with `mac/bundle.sh` (signed with Developer ID, see `docs/macos.md`), zips the app and notarizes it with `xcrun notarytool submit --keychain-profile byre-notary --wait`, staples the ticket, builds the disk image with `hdiutil` (the app and an `/Applications` link), signs, notarizes and staples that too, and writes its SHA-256 beside it. It then signs the image for Sparkle with `sign_update --account byre` and writes `appcast.xml`, a feed with that one release. `mac/package.sh` builds one for whatever the checkout holds, into `mac/.build/release/<version>/`, `<version>` being the bundle's `CFBundleShortVersionString`, and prints the image's path. It builds with `mac/bundle.sh` (signed with Developer ID, see `docs/macos.md`), zips the app and notarizes it with `xcrun notarytool submit --keychain-profile byre-notary --wait`, staples the ticket, builds the disk image with `hdiutil` (the app and an `/Applications` link), signs, notarizes and staples that too, and writes its SHA-256 beside it. It then signs the image for Sparkle with `sign_update --account byre` and writes `appcast.xml`, a feed with that one release.
A deploy is that image installed rather than published: `mac/deploy.sh` runs `package.sh`, installs the image's app with `mac/install.sh <dmg>` (mounts the image, copies `Byre.app` to `/Applications/Byre.app.new`, removes the old bundle and moves the new one into place, so nothing running from the old bundle has its files written over; it prints the installed `ByreCommit`); the service sees its executable replaced and restarts itself (see "Running as a service"). Client Macs get the same image through the client tools and `install.sh` (`AGENTS.md`, "Deploying"). A deployed build's `CFBundleVersion` is at least the last release's, so the updater never offers to go back to it. A deploy is that image installed rather than published: `mac/deploy.sh` runs `package.sh`, installs the image's app with `mac/install.sh <dmg>` (mounts the image, copies `Byre.app` to `/Applications/Byre.app.new`, removes the old bundle and moves the new one into place, so nothing running from the old bundle has its files written over; it prints the installed `ByreCommit`); the service sees its executable replaced and restarts itself (see "Running as a service"). Client Macs get the same image through the client tools and `install.sh` (`AGENTS.md`, "Deploying"). A deployed build's `CFBundleVersion` is at least the last release's, so the updater never offers to go back to it.
`mac/release.sh <notes>` publishes one, run on byre's host from a checkout whose `HEAD` carries a `v<version>` tag already pushed to `origin`, with a clean tree; it fetches the tag from `origin` and stops unless it names `HEAD`, then runs `package.sh`. Last, it creates the release as a draft through the Gitea API (`POST /api/v1/repos/ai/byre/releases`, its notes the Markdown file `<notes>`, which the releasing agent writes from the diffs since the previous tag, as `AGENTS.md`, "Releasing", describes), uploads the image, its `.sha256` and `appcast.xml` as assets, and only then publishes it (`PATCH` with `draft: false`), so the `latest` release always has its appcast. A run that fails part way leaves the draft on the releases page; delete it there before running again. Everything lands in `mac/.build/release/<version>/`, and the script prints the release page. `mac/release.sh <notes>` publishes one, run on byre's host from a checkout whose `HEAD` carries a `v<version>` tag already pushed to `origin`, with a clean tree; it fetches the tag from `origin` and stops unless it names `HEAD`, then runs `package.sh`. It uploads to the R2 bucket `byre` with `wrangler r2 object put` (run through `npx`): `byre-<version>.dmg` and its `.sha256`, cached for a year, then the same image as `byre.dmg`, the site's download link, and last `appcast.xml`, both cached for 5 minutes, so the appcast never names an image that isn't there yet, and then purges those two from Cloudflare's cache so the new release shows at once. Last, it creates the release on g.fc.run as a draft through the Gitea API (`POST /api/v1/repos/ai/byre/releases`), its notes the Markdown file `<notes>`, which the releasing agent writes from the diffs since the previous tag, as `AGENTS.md`, "Releasing", describes, attaches copies of the image and its `.sha256` (not the appcast), and only then publishes it (`PATCH` with `draft: false`). A run that fails after the uploads leaves the draft on the releases page; delete it there before running again. Everything lands in `mac/.build/release/<version>/`, and the script prints the release page.
It needs, on that Mac: It needs, on that Mac:
- a Developer ID Application certificate in the keychain (`security find-identity -v -p codesigning` lists it), with its key's access answered **Always Allow** for `codesign` (the first signing asks for the login keychain password on the Mac's screen, and a prompt left waiting fails that run with "timestamps differ"), and `mac/byre.provisionprofile` being the Developer ID profile for `run.fc.byre.mac` with iCloud (`security cms -D -i mac/byre.provisionprofile` prints it); - a Developer ID Application certificate in the keychain (`security find-identity -v -p codesigning` lists it), with its key's access answered **Always Allow** for `codesign` (the first signing asks for the login keychain password on the Mac's screen, and a prompt left waiting fails that run with "timestamps differ"), and `mac/byre.provisionprofile` being the Developer ID profile for `run.fc.byre.mac` with iCloud (`security cms -D -i mac/byre.provisionprofile` prints it);
- a `notarytool` keychain profile named `byre-notary`, made with `xcrun notarytool store-credentials byre-notary --key <AuthKey.p8> --key-id <id> --issuer <issuer>` from an App Store Connect API key; - a `notarytool` keychain profile named `byre-notary`, made with `xcrun notarytool store-credentials byre-notary --key <AuthKey.p8> --key-id <id> --issuer <issuer>` from an App Store Connect API key;
- the Sparkle signing key in the login keychain under account `byre` (`~/Library/Caches/byre/sparkle-<version>/bin/generate_keys --account byre -p` prints its public key, which must match `SUPublicEDKey` in `mac/Info.plist`; `-x <file>` exports it for safekeeping). The script downloads Sparkle's tools to that cache directory when they are missing; - the Sparkle signing key in the login keychain under account `byre` (`~/Library/Caches/byre/sparkle-<version>/bin/generate_keys --account byre -p` prints its public key, which must match `SUPublicEDKey` in `mac/Info.plist`; `-x <file>` exports it for safekeeping). The script downloads Sparkle's tools to that cache directory when they are missing;
- a g.fc.run token that can write the repo's releases, as the generic password with service `byre-release` (`security add-generic-password -s byre-release -a g.fc.run -w <token>`). - a g.fc.run token that can write the repo's releases, as the generic password with service `byre-release` (`security add-generic-password -s byre-release -a g.fc.run -w <token>`);
- the Cloudflare token (see "Website").
The app checks `https://g.fc.run/ai/byre/releases/download/latest/appcast.xml` for updates (see "Updates" in `docs/macos.md`); Gitea serves the `latest` name from the newest release, so each release's appcast replaces the last. The app checks `https://dl.byre.run/appcast.xml` for updates (see "Updates" in `docs/macos.md`); each release's appcast replaces the last.
## Website
byre.run and byre.team are registered at Porkbun, with Cloudflare as their DNS, in the Cloudflare account `cloudflare@flamingcow.io`.
- `https://byre.run/` (and `www.byre.run`) is the Cloudflare Pages project `byre`: `site/index.html`, with `mac/icon.svg` and the fonts from `mac/fonts/`. `site/deploy.sh` stages those files and publishes them with `wrangler pages deploy`. Releases don't redeploy it; its download link is `https://dl.byre.run/byre.dmg`, which each release replaces.
- `https://dl.byre.run/` is the R2 bucket `byre`'s custom domain, holding what `mac/release.sh` uploads (see "Releases").
- byre.team, and every path on it, redirects to `https://byre.run/` through a redirect rule on its zone (Rules → Redirect Rules in the dashboard), over proxied `AAAA 100::` records for `byre.team` and `www.byre.team`.
`site/deploy.sh` and `mac/release.sh` read a Cloudflare API token from the generic password with service `byre-cloudflare` (`security add-generic-password -s byre-cloudflare -a cloudflare -w`, which prompts for it); it needs Account → Cloudflare Pages: Edit and Workers R2 Storage: Edit, and Zone → DNS: Edit, Single Redirect: Edit, Zone Settings: Edit and Cache Purge: Purge on both zones. byre.run's Browser Cache TTL is "Respect Existing Headers", so browsers keep the 5-minute cache on `byre.dmg` and `appcast.xml`. The account ID is in both scripts.
Clients reach `tailscale serve` over HTTP/2; it proxies to byre over HTTP/1.1 and passes SSE through unbuffered. Clients reach `tailscale serve` over HTTP/2; it proxies to byre over HTTP/1.1 and passes SSE through unbuffered.
+1 -1
View File
@@ -17,7 +17,7 @@
<key>NSHighResolutionCapable</key> <key>NSHighResolutionCapable</key>
<true/> <true/>
<key>SUFeedURL</key> <key>SUFeedURL</key>
<string>https://g.fc.run/ai/byre/releases/download/latest/appcast.xml</string> <string>https://dl.byre.run/appcast.xml</string>
<key>SUPublicEDKey</key> <key>SUPublicEDKey</key>
<string>350+qbxFCpm+LhbRlCxtBHST+FTz6o19jhGOSs8D0XU=</string> <string>350+qbxFCpm+LhbRlCxtBHST+FTz6o19jhGOSs8D0XU=</string>
<key>SUEnableAutomaticChecks</key> <key>SUEnableAutomaticChecks</key>
+1 -3
View File
@@ -1,7 +1,6 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
mac=$(cd "$(dirname "$0")" && pwd) mac=$(cd "$(dirname "$0")" && pwd)
repo=ai/byre
sparkle_version=2.10.0 sparkle_version=2.10.0
sparkle=$HOME/Library/Caches/byre/sparkle-$sparkle_version sparkle=$HOME/Library/Caches/byre/sparkle-$sparkle_version
identity="Developer ID Application" identity="Developer ID Application"
@@ -14,7 +13,6 @@ fi
"$mac/bundle.sh" "$mac/bundle.sh"
app=$mac/.build/Byre.app app=$mac/.build/Byre.app
version=$(plutil -extract CFBundleShortVersionString raw "$app/Contents/Info.plist") version=$(plutil -extract CFBundleShortVersionString raw "$app/Contents/Info.plist")
tag=v$version
out=$mac/.build/release/$version out=$mac/.build/release/$version
rm -rf "$out" rm -rf "$out"
mkdir -p "$out" mkdir -p "$out"
@@ -50,7 +48,7 @@ cat > "$out/appcast.xml" <<EOF
<sparkle:version>$build</sparkle:version> <sparkle:version>$build</sparkle:version>
<sparkle:shortVersionString>$version</sparkle:shortVersionString> <sparkle:shortVersionString>$version</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>$minimum</sparkle:minimumSystemVersion> <sparkle:minimumSystemVersion>$minimum</sparkle:minimumSystemVersion>
<enclosure url="https://g.fc.run/$repo/releases/download/$tag/$dmg" type="application/octet-stream" $signature/> <enclosure url="https://dl.byre.run/$dmg" type="application/octet-stream" $signature/>
</item> </item>
</channel> </channel>
</rss> </rss>
+13 -1
View File
@@ -17,12 +17,24 @@ if [ "$(git -C "$root" rev-parse "FETCH_HEAD^{commit}")" != "$(git -C "$root" re
exit 1 exit 1
fi fi
token=$(security find-generic-password -s byre-release -w) token=$(security find-generic-password -s byre-release -w)
CLOUDFLARE_API_TOKEN=$(security find-generic-password -s byre-cloudflare -w)
CLOUDFLARE_ACCOUNT_ID=96e4d3199efaa07ddc5c33e0e2d59c01
export CLOUDFLARE_API_TOKEN CLOUDFLARE_ACCOUNT_ID
notes=$(cat "$1") notes=$(cat "$1")
"$mac/package.sh" "$mac/package.sh"
out=$mac/.build/release/$version out=$mac/.build/release/$version
dmg=byre-$version.dmg dmg=byre-$version.dmg
put() {
npx --yes wrangler@4.151.2 r2 object put "byre/$1" --remote --file "$out/$2" --content-type "$3" --cache-control "$4"
}
put "$dmg" "$dmg" application/x-apple-diskimage "public, max-age=31536000, immutable"
put "$dmg.sha256" "$dmg.sha256" text/plain "public, max-age=31536000, immutable"
put byre.dmg "$dmg" application/x-apple-diskimage "public, max-age=300"
put appcast.xml appcast.xml application/xml "public, max-age=300"
curl -fsS -o /dev/null -X POST -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json" --data '{"files":["https://dl.byre.run/byre.dmg","https://dl.byre.run/appcast.xml"]}' https://api.cloudflare.com/client/v4/zones/b53a5f8cb60313ba6789aba3559cb4b8/purge_cache
request=$out/release.json request=$out/release.json
plutil -create xml1 "$request" plutil -create xml1 "$request"
plutil -insert tag_name -string "$tag" "$request" plutil -insert tag_name -string "$tag" "$request"
@@ -31,7 +43,7 @@ plutil -insert body -string "$notes" "$request"
plutil -insert draft -bool true "$request" plutil -insert draft -bool true "$request"
plutil -convert json "$request" plutil -convert json "$request"
id=$(curl -fsS -X POST -H "Authorization: token $token" -H "Content-Type: application/json" --data-binary "@$request" "$api/releases" | plutil -extract id raw -) id=$(curl -fsS -X POST -H "Authorization: token $token" -H "Content-Type: application/json" --data-binary "@$request" "$api/releases" | plutil -extract id raw -)
for file in "$dmg" "$dmg.sha256" appcast.xml; do for file in "$dmg" "$dmg.sha256"; do
curl -fsS -o /dev/null -H "Authorization: token $token" -F "attachment=@$out/$file" "$api/releases/$id/assets?name=$file" curl -fsS -o /dev/null -H "Authorization: token $token" -F "attachment=@$out/$file" "$api/releases/$id/assets?name=$file"
done done
curl -fsS -o /dev/null -X PATCH -H "Authorization: token $token" -H "Content-Type: application/json" --data '{"draft":false}' "$api/releases/$id" curl -fsS -o /dev/null -X PATCH -H "Authorization: token $token" -H "Content-Type: application/json" --data '{"draft":false}' "$api/releases/$id"
Executable
+14
View File
@@ -0,0 +1,14 @@
#!/bin/sh
set -eu
site=$(cd "$(dirname "$0")" && pwd)
mac=$(dirname "$site")/mac
CLOUDFLARE_API_TOKEN=$(security find-generic-password -s byre-cloudflare -w)
CLOUDFLARE_ACCOUNT_ID=96e4d3199efaa07ddc5c33e0e2d59c01
export CLOUDFLARE_API_TOKEN CLOUDFLARE_ACCOUNT_ID
stage=$(mktemp -d)
trap 'rm -rf "$stage"' EXIT
cp "$site/index.html" "$mac/icon.svg" "$stage/"
mkdir "$stage/fonts"
cp "$mac"/fonts/*.woff2 "$stage/fonts/"
npx --yes wrangler@4.151.2 pages deploy "$stage" --project-name byre --branch main --commit-dirty=true
+194
View File
@@ -0,0 +1,194 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>byre</title>
<meta name="description" content="Run many coding agents in parallel on your own Mac, each on its own branch, with a lead agent that keeps track of them for you.">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<style>
@font-face {
font-family: "Atkinson Hyperlegible Mono";
font-weight: 200 800;
src: url("/fonts/atkinson-hyperlegible-mono-latin-wght-normal.woff2") format("woff2");
}
@font-face {
font-family: "Atkinson Hyperlegible Mono";
font-weight: 200 800;
src: url("/fonts/atkinson-hyperlegible-mono-latin-ext-wght-normal.woff2") format("woff2");
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
:root {
--bg: #fbfbfa;
--surface: #f2f3f5;
--border: #d6d9de;
--text: #16181b;
--muted: #4a5260;
--accent: #1a45bd;
--accent-tint: #e7edfb;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #15171a;
--surface: #1c1f23;
--border: #353b43;
--text: #eceff3;
--muted: #a9b1bc;
--accent: #8ab4ff;
--accent-tint: #1e2a40;
}
}
* {
box-sizing: border-box;
}
body {
margin: 0;
background: var(--bg);
color: var(--text);
font: 16px/1.6 "Atkinson Hyperlegible Mono", ui-monospace, monospace;
}
main {
max-width: 960px;
margin: 0 auto;
padding: 64px 16px;
}
a {
color: var(--accent);
}
header {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 24px;
padding-bottom: 56px;
border-bottom: 1px solid var(--border);
}
header img {
width: 72px;
height: 72px;
border-radius: 16px;
}
h1 {
margin: 0;
font-size: clamp(32px, 6vw, 52px);
line-height: 1.15;
font-weight: 700;
letter-spacing: -0.02em;
}
.lede {
margin: 0;
max-width: 44em;
color: var(--muted);
font-size: 18px;
}
.actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 16px 24px;
}
.download {
display: inline-block;
padding: 12px 22px;
border-radius: 8px;
background: var(--accent);
color: var(--bg);
font-weight: 700;
text-decoration: none;
}
.download:hover {
opacity: 0.9;
}
.note {
color: var(--muted);
font-size: 14px;
}
h2 {
margin: 56px 0 24px;
font-size: 22px;
}
.features {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 16px;
margin: 0;
padding: 0;
list-style: none;
}
.features li {
padding: 20px;
border: 1px solid var(--border);
border-radius: 10px;
background: var(--surface);
}
.features h3 {
margin: 0 0 8px;
font-size: 16px;
}
.features p {
margin: 0;
color: var(--muted);
font-size: 14px;
}
ol {
margin: 0;
padding-left: 24px;
max-width: 48em;
}
ol li + li {
margin-top: 12px;
}
code {
padding: 1px 6px;
border-radius: 4px;
background: var(--accent-tint);
font-family: inherit;
font-size: 0.92em;
}
footer {
margin-top: 72px;
padding-top: 24px;
border-top: 1px solid var(--border);
color: var(--muted);
font-size: 14px;
}
</style>
</head>
<body>
<main>
<header>
<img src="/icon.svg" alt="">
<h1>A crew of coding agents,<br>running on your Mac.</h1>
<p class="lede">byre runs Claude Code, Codex and Antigravity side by side, each on its own branch and worktree, with a lead agent that watches them all and tells you what needs you.</p>
<div class="actions">
<a class="download" href="https://dl.byre.run/byre.dmg">Download for macOS</a>
<span class="note">Free and open source. Updates itself.</span>
</div>
</header>
<h2>What it does</h2>
<ul class="features">
<li><h3>Tasks</h3><p>Every task gets its own git worktree, branch and port block. Talk to its agent in a live transcript, even mid-turn.</p></li>
<li><h3>Lead agent</h3><p>One long-running agent watches every task, points you at what needs you, and starts, messages, merges or discards tasks when you ask.</p></li>
<li><h3>Questions, not interruptions</h3><p>Agents ask structured questions with a recommended answer. You answer with a click, from an inbox or a notification.</p></li>
<li><h3>Cross-vendor reviews</h3><p>Every turn that changes files is reviewed by the other vendor's model. You fix or ignore each finding.</p></li>
<li><h3>One-click merge</h3><p>The agent merges main in and resolves conflicts, then byre squashes, pushes and closes the task.</p></li>
<li><h3>Snapshots and forks</h3><p>Step through a task's snapshots, diff any of them, and fork a new task from any one, conversation included.</p></li>
<li><h3>Subtasks, teams, workflows</h3><p>Agents spawn subtasks or share a worktree as a team. Workflows chain stages with fan-out and fan-in.</p></li>
<li><h3>Hands on your Macs</h3><p>Agents can see and drive the Mac byre runs on, and the Macs you have the app open on, when a task needs it.</p></li>
</ul>
<h2>Getting started</h2>
<ol>
<li>Install <a href="https://tailscale.com">Tailscale</a> on every Mac you'll use. byre serves only your tailnet, and takes your identity from it.</li>
<li>On the Mac that will run the agents, install <code>git</code>, Node.js, Google Chrome and at least one of the <code>claude</code>, <code>codex</code> and <code>agy</code> CLIs.</li>
<li><a href="https://dl.byre.run/byre.dmg">Download byre</a>, drag it to Applications, open it and click <strong>Run byre on this Mac</strong>. It checks what's missing and tells you how to install it.</li>
<li>Open byre on your other Macs. They find the server through iCloud.</li>
</ol>
<footer>
byre is Apache 2.0 licensed. <a href="https://g.fc.run/ai/byre">Source</a> · <a href="https://g.fc.run/ai/byre/releases">Releases</a>
</footer>
</main>
</body>
</html>