Move to g.fc.run/ai/byre and tag v0.1.0
This commit is contained in:
+7
-7
@@ -112,7 +112,7 @@ A team task has several agents working at once in its one worktree, branch and p
|
||||
|
||||
The listen address is `byre serve --listen`, whose default is `defaultListen` in `cmd/byre/serve.go` (`go run ./cmd/byre serve --help` prints it); keep it on loopback, since identity relies on requests arriving from `tailscale serve`.
|
||||
|
||||
`byre install` (`cmd/byre/install.go`) sets the service up for the binary it is run as, with `byre-hands` beside it: it writes `~/Library/LaunchAgents/run.fc.byre.plist` running `<that binary> serve`, with a `PATH` of that binary's directory (so `byre-hands` beside it is found) followed by the `PATH` of the shell it was run from (which must find `claude`, `codex`, `git` and `npx`, the last for the Playwright MCP server, which also needs Google Chrome) and logs to `~/Library/Logs/byre.log`; boots out the service if it is loaded, so the new plist is read; bootstraps it; and runs `tailscale serve --bg --https=443 http://<default listen address>` through the Tailscale app's CLI (`tailscale.ServeRoot`, see "Previews"), printing `byre is at https://<machine>.<tailnet>.ts.net`. Running it again after an update restarts the service on the new binary. From a release, that is `/Applications/byre.app/Contents/Helpers/byre install`. From a checkout:
|
||||
`byre install` (`cmd/byre/install.go`) sets the service up for the binary it is run as, with `byre-hands` beside it: it writes `~/Library/LaunchAgents/run.fc.byre.plist` running `<that binary> serve`, with a `PATH` of that binary's directory (so `byre-hands` beside it is found) followed by the `PATH` of the shell it was run from (which must find `claude`, `codex`, `git` and `npx`, the last for the Playwright MCP server, which also needs Google Chrome) and logs to `~/Library/Logs/byre.log`; boots out the service if it is loaded, so the new plist is read; bootstraps it; and runs `tailscale serve --bg --https=443 http://<default listen address>` through the Tailscale app's CLI (`tailscale.ServeRoot`, see "Previews"), printing `byre is at https://<machine>.<tailnet>.ts.net`. Running it again after an update restarts the service on the new binary. From a release, that is `/Applications/Byre.app/Contents/Helpers/byre install`. From a checkout:
|
||||
|
||||
```
|
||||
go install ./cmd/byre
|
||||
@@ -128,9 +128,9 @@ After rebuilding from a checkout: step 1 of "Deploying" in `AGENTS.md`, which re
|
||||
|
||||
## Releases
|
||||
|
||||
A release is one notarized disk image, `byre-<version>.dmg`, holding `byre.app`: the macOS app, with the server (`Contents/Helpers/byre`) and `byre-hands` (`Contents/Helpers/byre-hands`) 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/misc/byre/releases`.
|
||||
A release is one notarized disk image, `byre-<version>.dmg`, holding `Byre.app`: the macOS app, with the server (`Contents/Helpers/byre`) and `byre-hands` (`Contents/Helpers/byre-hands`) 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`.
|
||||
|
||||
`mac/release.sh` makes 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`. 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. Last, it creates the release as a draft through the Gitea API (`POST /api/v1/repos/misc/byre/releases`, notes the commit subjects since the previous tag), 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` makes 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`. 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. Last, it creates the release as a draft through the Gitea API (`POST /api/v1/repos/ai/byre/releases`, notes the commit subjects since the previous tag), 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.
|
||||
|
||||
It needs, on that Mac:
|
||||
|
||||
@@ -139,15 +139,15 @@ It needs, on that Mac:
|
||||
- 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>`).
|
||||
|
||||
The app checks `https://g.fc.run/misc/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://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.
|
||||
|
||||
Clients reach `tailscale serve` over HTTP/2; it proxies to byre over HTTP/1.1 and passes SSE through unbuffered.
|
||||
|
||||
## macOS app
|
||||
|
||||
**Building.** `mac/bundle.sh`, from any directory, builds the release configuration with `swift build` in `~/Library/Caches/byre/build`, one build directory every worktree shares and takes turns in (`docs/macos.md`, "Stack", says why), and assembles `mac/.build/byre.app`: the `Byre` executable in `Contents/MacOS`; the server, built with `go build ./cmd/byre`, and `byre-hands`, built from `hands/`, in `Contents/Helpers` (not beside `Byre`: the default APFS volume ignores case, so `byre` and `Byre` would be one file); Sparkle's framework in `Contents/Frameworks`; `mac/Info.plist` with a `ByreCommit` key added holding the worktree's `git rev-parse HEAD`, `CFBundleShortVersionString` its `git describe --tags --always` without the leading `v` (`0.1.0` on a release tag, `0.1.0-12-gabc1234` after it) and `CFBundleVersion` its `git rev-list --count HEAD` (the About panel shows them as "Version <version> (<count>)", and Sparkle compares the count); the `mac/fonts/` woff2 files, an icon rendered from `mac/icon.svg` with `rsvg-convert` and `iconutil` (cached as `mac/.build/byre.icns` until the SVG changes) and `mac/byre.provisionprofile`. It signs each piece with the Developer ID Application certificate, a secure timestamp and the hardened runtime, innermost first: `byre-hands` as `run.fc.byre.hands` and the server as `run.fc.byre`, both with `mac/automation.entitlements` (Apple Events, which the hardened runtime otherwise refuses), Sparkle's `Autoupdate`, `Updater.app` and framework (its XPC services, only needed by sandboxed apps, are removed), then the bundle with `mac/byre.entitlements`. It prints the bundle's path. It needs Xcode selected (`xcode-select --print-path` prints `/Applications/Xcode.app/Contents/Developer`), Go, `rsvg-convert` (Homebrew's `librsvg`), the team's Developer ID Application certificate and network access for the timestamp, and it stops first unless `mac/byre.provisionprofile` has `ProvisionsAllDevices` (a Developer ID profile; a development profile can't authorize the Developer ID signature); `docs/macos.md`, "Signing", covers the certificate and profile. Debug builds are refused by an `#error` in `ByreApp.swift`. The first build on a Mac fetches the Swift packages pinned in `mac/Package.resolved` and compiles them and the generated API client; later builds, from any worktree, recompile only the targets whose sources differ from the last build's, usually just `Byre`. The API client is generated from `api/openapi.yaml` at build time. The palette is `mac/Sources/Byre/Palette.swift`, one light and one dark value per colour.
|
||||
**Building.** `mac/bundle.sh`, from any directory, builds the release configuration with `swift build` in `~/Library/Caches/byre/build`, one build directory every worktree shares and takes turns in (`docs/macos.md`, "Stack", says why), and assembles `mac/.build/Byre.app`: the `Byre` executable in `Contents/MacOS`; the server, built with `go build ./cmd/byre`, and `byre-hands`, built from `hands/`, in `Contents/Helpers` (not beside `Byre`: the default APFS volume ignores case, so `byre` and `Byre` would be one file); Sparkle's framework in `Contents/Frameworks`; `mac/Info.plist` with a `ByreCommit` key added holding the worktree's `git rev-parse HEAD`, `CFBundleShortVersionString` its `git describe --tags --always` without the leading `v` (`0.1.0` on a release tag, `0.1.0-12-gabc1234` after it) and `CFBundleVersion` its `git rev-list --count HEAD` (the About panel shows them as "Version <version> (<count>)", and Sparkle compares the count); the `mac/fonts/` woff2 files, an icon rendered from `mac/icon.svg` with `rsvg-convert` and `iconutil` (cached as `mac/.build/byre.icns` until the SVG changes) and `mac/byre.provisionprofile`. It signs each piece with the Developer ID Application certificate, a secure timestamp and the hardened runtime, innermost first: `byre-hands` as `run.fc.byre.hands` and the server as `run.fc.byre`, both with `mac/automation.entitlements` (Apple Events, which the hardened runtime otherwise refuses), Sparkle's `Autoupdate`, `Updater.app` and framework (its XPC services, only needed by sandboxed apps, are removed), then the bundle with `mac/byre.entitlements`. It prints the bundle's path. It needs Xcode selected (`xcode-select --print-path` prints `/Applications/Xcode.app/Contents/Developer`), Go, `rsvg-convert` (Homebrew's `librsvg`), the team's Developer ID Application certificate and network access for the timestamp, and it stops first unless `mac/byre.provisionprofile` has `ProvisionsAllDevices` (a Developer ID profile; a development profile can't authorize the Developer ID signature); `docs/macos.md`, "Signing", covers the certificate and profile. Debug builds are refused by an `#error` in `ByreApp.swift`. The first build on a Mac fetches the Swift packages pinned in `mac/Package.resolved` and compiles them and the generated API client; later builds, from any worktree, recompile only the targets whose sources differ from the last build's, usually just `Byre`. The API client is generated from `api/openapi.yaml` at build time. The palette is `mac/Sources/Byre/Palette.swift`, one light and one dark value per colour.
|
||||
|
||||
**Installing.** A release is installed from its disk image into `/Applications` (see "Releases") and updates itself (`docs/macos.md`, "Updates"). A build from a checkout runs from where it was built: `open mac/.build/byre.app` in the worktree that built it. A new build replaces the bundle, so quit the running app first (`pkill -x Byre` quits every running copy, whichever worktree built it) and open the new one; step 4 of "Deploying" in `AGENTS.md` does this after a deploy. Every build shares the bundle identifier `run.fc.byre.mac` and the same store, so any build reconnects to the server the last one used. Closing the window leaves the app running for notifications; ⌘Q quits it.
|
||||
**Installing.** A release is installed from its disk image into `/Applications` (see "Releases") and updates itself (`docs/macos.md`, "Updates"). A build from a checkout runs from where it was built: `open mac/.build/Byre.app` in the worktree that built it. A new build replaces the bundle, so quit the running app first (`pkill -x Byre` quits every running copy, whichever worktree built it) and open the new one; step 4 of "Deploying" in `AGENTS.md` does this after a deploy. Every build shares the bundle identifier `run.fc.byre.mac` and the same store, so any build reconnects to the server the last one used. Closing the window leaves the app running for notifications; ⌘Q quits it.
|
||||
|
||||
**Storage.** The app keeps three kinds of record in SwiftData (`mac/Sources/Byre/Persistence.swift`): the server origin, when each task was last selected, and composer and answer drafts as JSON, one record per server and login (`docs/macos.md`, "Persistence" and "Drafts"). The store is `~/Library/Application Support/run.fc.byre.mac/byre.store`, with its `-shm` and `-wal` files and CloudKit's working files beside it in that directory; the app isn't sandboxed, so it names its own file rather than SwiftData's default `default.store`, which every unsandboxed app shares. The store syncs through the iCloud private database of container `iCloud.run.fc.byre.mac`, in CloudKit's Production environment (`com.apple.developer.icloud-container-environment` in `mac/byre.entitlements`), whose schema is deployed from Development in the CloudKit console before a release that changes the SwiftData models; the CloudKit console (icloud.developer.apple.com, the container, Production, Private Database, zone `com.apple.coredata.cloudkit.zone`) shows the records. Deleting the local files while the app isn't running makes the next launch download the records again from iCloud; deleting the records in the console as well starts from the connection screen. Pasted images stay in memory and are dropped on Change Server… and quit. The app logs through `os.Logger`: `log show --last 10m --predicate 'subsystem == "run.fc.byre.mac"'` prints it.
|
||||
|
||||
@@ -351,7 +351,7 @@ Implementer sessions get eight MCP tools for it (`internal/agents/clients.go`).
|
||||
`client_read`, `client_write` and `client_edit` wait at most 2 minutes for each reply, and their paths, like `client_download`'s `path`, are absolute or start with `~/`, which the app expands.
|
||||
- `client_hands`: `op: hands`, with `tool` and `arguments` as the `hands` tools take them (see "Screen and app control"). The app runs one `byre-hands`, bundled at `Contents/Helpers/byre-hands`, started on first use and again after it exits, writes each call to its stdin as a `tools/call` line and replies with the line's `result`, which byre returns to the agent as the tool's result, images included (`mcp.Tool.Rich`; Codex gets them as `inputImage` data URLs). The macOS privacy grants it needs (Screen Recording, Accessibility, Automation of Orion) belong to byre's app on that Mac, since the app is the responsible process; `NSAppleEventsUsageDescription` in `mac/Info.plist` is what lets macOS ask for the Automation grant. Each `byre-hands` process has its own pending calls, so one that exits fails only the calls sent to it.
|
||||
|
||||
A byre restart drops every connection; each app reconnects 2s later, and calls in flight at the restart fail as disconnected. Updating a client's app goes through the tools themselves: `client_upload` the new `byre.app`, then `client_bash` a command that quits the old app and opens the new one, which outlives the app and reconnects. An app that doesn't know the `upload` op can't be updated this way, so its update is by hand.
|
||||
A byre restart drops every connection; each app reconnects 2s later, and calls in flight at the restart fail as disconnected. Updating a client's app goes through the tools themselves: `client_upload` the new `Byre.app`, then `client_bash` a command that quits the old app and opens the new one, which outlives the app and reconnects. An app that doesn't know the `upload` op can't be updated this way, so its update is by hand.
|
||||
|
||||
`log show --info --last 10m --predicate 'subsystem == "run.fc.byre.mac"'` on a client shows `client request <id>: <op>` lines and `byre-hands` starts and exits, which are info level and kept only in memory, so they cover the last few minutes at most; connection drops (`client connection closed`, `client connection got no pong`) are notices, which macOS keeps on disk, so `log show --start` reaches much further back for them; byre's log shows `client connected` and `client disconnected`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user