7 Commits
33 changed files with 1090 additions and 473 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ A deploy is a release that isn't uploaded: the same notarized disk image, instal
2. Sort the client Macs `list_clients` shows (leaving out the iPhone) by whether they run a byre server of their own: one does when `launchctl print gui/$(id -u)/run.fc.byre` exits 0 there (`client_bash`). Update the app on each one that runs no server, one Mac at a time, through the client tools and without asking first: upload `~/byre-deploy/byre.dmg` and `~/byre-deploy/install.sh` to the same paths there (`client_upload`), then run `~/byre-deploy/install.sh ~/byre-deploy/byre.dmg` there through `client_bash`. It installs, prints the build number and the commit, then quits the app if it is running and reopens it on the new build, which ends the `client_bash` call as the client disconnects while the script carries on. Wait until `list_clients` shows that Mac back on the new build and commit, and report them. Every one of these must be done before any server restarts, since a protocol change can leave an old client unable to reach a new server to be updated.
3. Only then update each client Mac that runs a server, the same way. Its server sees its executable replaced and restarts on the new build.
4. Install on the Mac you run on with `mac/install.sh ~/byre-deploy/byre.dmg`, always as the last action of the turn. The service runs `/Applications/Byre.app/Contents/Helpers/byre`; within 5 seconds it sees that file replaced and exits, and launchd starts the new server and `byre-hands`. Everything is signed with Developer ID, so the macOS privacy grants (Screen Recording, Accessibility, Automation) survive. `install.sh` relaunches its app if it is running, and leaves it closed if not.
5. When you resume, confirm it came up: read the tail of `~/Library/Logs/byre.log`, and `GET /api/tailnet` at the loopback address (`byre check` prints it as `loopback`), whose `state` must be `Running`. Report the new build number first (`plutil -extract CFBundleVersion raw /Applications/Byre.app/Contents/Info.plist` prints it; the iPhone build uploaded to TestFlight has the same one), then what you deployed and what you checked.
5. When you resume, confirm it came up: read the tail of `~/Library/Logs/byre.log`, and `GET /api/tailnet` at the loopback address (`byre check` prints it as `loopback`), whose `state` must be `Running`. Report the new build number first (`plutil -extract CFBundleVersion raw /Applications/Byre.app/Contents/Info.plist` prints it; it is the Unix time of the build, and the iPhone build uploaded to TestFlight has its own, a few minutes later), then what you deployed and what you checked.
6. End the report with release notes for everything on main since the last release tag (`git describe --tags --abbrev=0` names it), written for a byre user in the "Releasing" shape (`## New` and `## Fixed`, each a bullet with a bold short name and what it does or what now works; no `## Install`). Read `git diff <tag>..HEAD`, not just the commit subjects, and leave out changes no user sees.
Deploying means building with `mac/deploy.sh` on the Mac you run on. Never stand in for it by running `mac/package.sh` yourself, not even to build once for several Macs. Every Mac, this one included, gets the disk image `mac/deploy.sh` left in `~/byre-deploy/`.
+60 -1
View File
@@ -461,6 +461,65 @@ paths:
schema: { $ref: "#/components/schemas/Todo" }
"404": { $ref: "#/components/responses/Error" }
/api/projects/{projectId}/dir:
parameters:
- { name: projectId, in: path, required: true, schema: { type: string } }
get:
operationId: getProjectDir
description: >
One directory on the server, directories first, then by name. Entries named .git are left out;
gitignored entries inside the project's repository checkout are included and marked.
parameters:
- { name: path, in: query, required: false, schema: { type: string }, description: "Slash-separated, absolute or relative to the project's repository root; empty or absent for the root." }
responses:
"200":
description: The directory's entries.
content:
application/json:
schema: { $ref: "#/components/schemas/DirListing" }
"400": { $ref: "#/components/responses/Error" }
"404": { $ref: "#/components/responses/Error" }
/api/projects/{projectId}/file:
parameters:
- { name: projectId, in: path, required: true, schema: { type: string } }
get:
operationId: getProjectFile
description: >
A regular file on the server. Text is returned up to a size limit; binary files return no content.
parameters:
- { name: path, in: query, required: true, schema: { type: string }, description: "Slash-separated, absolute or relative to the project's repository root." }
responses:
"200":
description: The file.
content:
application/json:
schema: { $ref: "#/components/schemas/FileContent" }
"400": { $ref: "#/components/responses/Error" }
"404": { $ref: "#/components/responses/Error" }
/api/projects/{projectId}/image:
parameters:
- { name: projectId, in: path, required: true, schema: { type: string } }
get:
operationId: getProjectImage
description: >
An image file on the server. path is absolute or relative to the project's repository root.
PNG, JPEG, GIF, WebP and SVG, chosen by extension.
parameters:
- { name: path, in: query, required: true, schema: { type: string } }
responses:
"200":
description: The file's bytes.
content:
image/png: { schema: { type: string, format: binary } }
image/jpeg: { schema: { type: string, format: binary } }
image/gif: { schema: { type: string, format: binary } }
image/webp: { schema: { type: string, format: binary } }
image/svg+xml: { schema: { type: string, format: binary } }
"400": { $ref: "#/components/responses/Error" }
"404": { $ref: "#/components/responses/Error" }
/api/projects/{projectId}/todo/tasks:
parameters:
- { name: projectId, in: path, required: true, schema: { type: string } }
@@ -2375,7 +2434,7 @@ components:
host: { type: string, description: The device's name from its Byre-Host header. }
device: { type: string, description: "The kind of device the app runs on (Mac or iPhone), from its Byre-Device header." }
commit: { type: string, description: The git commit the app was built from, from its Byre-Commit header; empty when the client sent none. }
build: { type: string, description: "The app's build number (its CFBundleVersion, the Unix time of that commit), from its Byre-Build header; empty when the client sent none." }
build: { type: string, description: "The app's build number (its CFBundleVersion, the Unix time it was built), from its Byre-Build header; empty when the client sent none." }
connected_at: { type: string, format: date-time, description: When the client's WebSocket connected. }
last_action_at: { type: string, format: date-time, description: When byre last received a request the human made from this client since byre started; absent when none. }
sync: { $ref: "#/components/schemas/ClientSync" }
+1 -1
View File
@@ -8,7 +8,7 @@ Left out on the phone: the Server tab and running byre locally, the shell, file,
## Building
`mac/phone.sh` builds the app with `xcodebuild` (a Swift package can't be built for iOS by `swift build`) in the same shared directory and under the same lock as `mac/bundle.sh`, assembles `Byre.app` itself (icon through `actool`, fonts, `Info.plist` from `mac/phone/Info.plist` plus the version, commit and `DT*` keys App Store Connect checks), signs it ad hoc with `mac/phone/byre.entitlements`, and wraps it in `mac/.build/phone/iphoneos/Byre.xcarchive`. Release only, like the Mac app. `xcodebuild -exportArchive -archivePath <archive> -exportOptionsPlist mac/phone/export.plist -exportPath <dir> -allowProvisioningUpdates` re-signs it for the App Store and uploads it to TestFlight, using the Apple account Xcode is signed into: Xcode makes the distribution certificate and profile itself, so none are kept in the repo. Uploading needs the App ID `run.fc.byre.ios` (iCloud with the container `iCloud.run.fc.byre.mac`, and Push Notifications) and its app record in App Store Connect; neither can be made through Apple's API. The version is the last release tag's (App Store Connect takes only numbers) and the build number is the Mac app's (`docs/operations.md`, "Releases"). App Store Connect refuses a build number that isn't higher than every earlier upload, so a second upload of the same commit fails; a commit count wouldn't do, since it can go down when a branch with many commits uploads before its squash merge lands on main.
`mac/phone.sh` builds the app with `xcodebuild` (a Swift package can't be built for iOS by `swift build`) in the same shared directory and under the same lock as `mac/bundle.sh`, assembles `Byre.app` itself (icon through `actool`, fonts, `Info.plist` from `mac/phone/Info.plist` plus the version, commit and `DT*` keys App Store Connect checks), signs it ad hoc with `mac/phone/byre.entitlements`, and wraps it in `mac/.build/phone/iphoneos/Byre.xcarchive`. Release only, like the Mac app. `xcodebuild -exportArchive -archivePath <archive> -exportOptionsPlist mac/phone/export.plist -exportPath <dir> -allowProvisioningUpdates` re-signs it for the App Store and uploads it to TestFlight, using the Apple account Xcode is signed into: Xcode makes the distribution certificate and profile itself, so none are kept in the repo. Uploading needs the App ID `run.fc.byre.ios` (iCloud with the container `iCloud.run.fc.byre.mac`, and Push Notifications) and its app record in App Store Connect; neither can be made through Apple's API. The version is the last release tag's (App Store Connect takes only numbers) and the build number is the Unix time of the build (`docs/operations.md`, "Releases"). App Store Connect refuses a build number that isn't higher than every earlier upload, which a build time always is, so the same commit can be uploaded again; a commit count wouldn't do, since it can go down when a branch with many commits uploads before its squash merge lands on main.
iOS finds the app's App Intents (the Run workflow shortcut the Action button can run) only through `Byre.app/Metadata.appintents`. Xcode makes that bundle for app targets but not for a Swift package's executable, so `mac/phone.sh` runs `appintentsmetadataprocessor` itself, inside the build lock, on the source list and `.swiftconstvalues` the compiler leaves in the derived data. It has to read the sources from the shared copy the build compiled, which the next build of another worktree replaces. The simulator has no Shortcuts app, so an intent can only be tried on a phone.
+3 -2
View File
@@ -38,7 +38,7 @@ Each preview gets its own port on byre's node rather than a path under byre's or
## Releases
A release is one notarized disk image, `byre-<version>.dmg`, holding `Byre.app` with the server, `byre-hands` and `byre-update` in `Contents/Helpers`. Images are served from `https://dl.byre.run/`; each release's notes are on `https://g.fc.run/ai/byre/releases`. `mac/package.sh` builds one, `mac/deploy.sh` builds one for installing on the owner's Macs and keeps a copy in `~/byre-deploy/` (see `AGENTS.md`, "Deploying"), and `mac/release.sh <notes>` publishes it from a clean checkout at a pushed `v<version>` tag. The build number (`CFBundleVersion`) of both the Mac and iPhone apps is the Unix time of the commit they were built from (`git log -1 --format=%ct`), so one deploy's apps share it; the apps send it in their `Byre-Build` header and the Status tab's client list shows it. A deployed build's `CFBundleVersion` is at least the last release's, so the updater never offers to go back to it.
A release is one notarized disk image, `byre-<version>.dmg`, holding `Byre.app` with the server, `byre-hands` and `byre-update` in `Contents/Helpers`. Images are served from `https://dl.byre.run/`; each release's notes are on `https://g.fc.run/ai/byre/releases`. `mac/package.sh` builds one, `mac/deploy.sh` builds one for installing on the owner's Macs and keeps a copy in `~/byre-deploy/` (see `AGENTS.md`, "Deploying"), and `mac/release.sh <notes>` publishes it from a clean checkout at a pushed `v<version>` tag. The build number (`CFBundleVersion`) of the Mac and iPhone apps is the Unix time each was built (`date +%s`), so a deploy's iPhone build is a few minutes after its Mac build; the apps send it in their `Byre-Build` header and the Status tab's client list shows it. A deployed build's `CFBundleVersion` is above every earlier release's, so the updater never offers to go back; a release of a commit already deployed is built later, so the updater offers it to the Macs running that deploy, which takes them to the release's version name.
The appcast must never name an image that isn't available yet, and a published g.fc.run release must already have its attachments. If a failed release run leaves a draft on the releases page, delete it there before running again.
@@ -69,7 +69,7 @@ byre.run and byre.team are registered at Porkbun, with Cloudflare as their DNS,
- `https://dl.byre.run/` is the R2 bucket `byre`'s custom domain, holding what `mac/release.sh` uploads.
- 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 short cache on `byre.dmg` and `appcast.xml`.
`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 short cache on `byre.dmg` and revalidate `appcast.xml` (`no-cache`) on every fetch. The app checks the appcast hourly (`SUScheduledCheckInterval` in `mac/Info.plist`, Sparkle's minimum).
## Agent vendors
@@ -96,6 +96,7 @@ Coordination of merges, discards, reviews, ready-for-merge prompts, workflow sta
## git
- A project's remote is `origin`, checked each time byre would fetch or push. A repo with no `origin` still works: merges land on the local default branch and byre skips the fetch, the push and the notes refspec, so a fresh `git init` is enough to start. Adding `origin` later takes effect on the next merge; the notes refspec is added at the next server start.
- Merge notes live under `refs/notes/byre`. byre adds the fetch refspec `refs/notes/byre*:refs/notes/byre*` to each checkout; it's a pattern because git refuses a fetch whose exact refspec names a ref the remote doesn't have yet, as it doesn't before the first note is pushed. Another clone adds it with `git config --add remote.origin.fetch 'refs/notes/byre*:refs/notes/byre*'`; `git log --notes=byre` shows the notes.
- `git fetch` and `git push` run non-interactively (`GIT_TERMINAL_PROMPT=0`, ssh `BatchMode=yes`, overriding any `core.sshCommand`), so a remote needing a password or an unknown host key fails at once instead of waiting for input. Every git command runs in its own process group, killed whole on timeout, so an `ssh` it started can't finish a fetch or push after byre has moved on.
- A push error other than a non-fast-forward rejection is ambiguous, since the remote may have taken the push before the connection dropped; byre then fetches and checks whether the commit landed before deciding.
+7 -2
View File
@@ -23,7 +23,7 @@ const (
clientByre = "/Applications/Byre.app/Contents/Helpers/byre"
)
func (m *Manager) clientTools(l *live) []mcp.Tool {
func (m *Manager) clientShellTools() []mcp.Tool {
return []mcp.Tool{
{
Name: "list_clients",
@@ -55,6 +55,11 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
return m.clientBash(ctx, raw)
},
},
}
}
func (m *Manager) clientTools(l *live) []mcp.Tool {
return append(m.clientShellTools(), []mcp.Tool{
{
Name: "client_upload",
Description: "Copy a file or directory from the Mac byre runs on to a client Mac, replacing whatever is at dest there. " +
@@ -272,7 +277,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
return m.clientHands(ctx, raw)
},
},
}
}...)
}
func (m *Manager) listClients() string {
+22
View File
@@ -3,6 +3,7 @@ package agents
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"os"
"strings"
@@ -143,6 +144,16 @@ func (m *Manager) codexItemCompleted(ctx context.Context, l *live, s *codex.Sess
return nil
}
return m.emit(ctx, l, agentActor, "agent.thinking", map[string]any{"text": text})
case codex.ThreadItemTypeImageGeneration:
if item.SavedPath == "" || l.reviewID != "" || l.owner != "" {
return nil
}
path, err := copyToMedia(MediaDir(l.dir), string(item.SavedPath))
if err != nil {
return err
}
return m.emit(ctx, l, agentActor, "agent.message", map[string]any{"text": fmt.Sprintf("![%s](<%s>)", imageAlt(item.RevisedPrompt), path)})
}
if !codexToolItems[item.Type] {
@@ -188,6 +199,17 @@ func (m *Manager) codexItemCompleted(ctx context.Context, l *live, s *codex.Sess
return nil
}
func imageAlt(prompt string) string {
alt := strings.NewReplacer("[", "", "]", "", "\n", " ").Replace(strings.TrimSpace(prompt))
if r := []rune(alt); len(r) > 120 {
alt = string(r[:120]) + "…"
}
if alt == "" {
return "generated image"
}
return alt
}
func codexToolResult(item codex.ThreadItem) (string, bool) {
status := codexStatus(item)
switch item.Type {
+125
View File
@@ -0,0 +1,125 @@
package agents
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"g.fc.run/ai/byre/internal/codex"
"g.fc.run/ai/byre/internal/mcp"
)
const imageGenGuidance = "You make images for another agent. Call your image_gen tool exactly once with the prompt you are given, unchanged, then reply with only the word done."
func (m *Manager) generateImageTool(l *live) mcp.Tool {
return mcp.Tool{
Name: "generate_image",
Description: "Generate an image (a photo, illustration, texture, sprite, mockup) with OpenAI's image model through byre's Codex sign-in, saved as a PNG in the task's media folder. " +
"The reply gives its path: show it inline by embedding it in your final message as a Markdown image, ![what it shows](<path>), or copy it into the worktree when the project uses it. " +
"Use it whenever the human asks for a picture, rather than drawing one in SVG or HTML. It takes a minute or two.",
InputSchema: json.RawMessage(`{
"type": "object",
"properties": {
"prompt": {"type": "string", "description": "What to draw: subject, style, composition, lighting, and any exact text."}
},
"required": ["prompt"]
}`),
Handler: func(ctx context.Context, raw json.RawMessage) (string, error) {
return m.generateImage(ctx, l, raw)
},
}
}
func (m *Manager) generateImage(ctx context.Context, l *live, raw json.RawMessage) (string, error) {
args := struct {
Prompt string `json:"prompt"`
}{}
err := json.Unmarshal(raw, &args)
if err != nil {
return "", fmt.Errorf("malformed generate_image call: %w", err)
}
if strings.TrimSpace(args.Prompt) == "" {
return "", fmt.Errorf("generate_image needs a prompt")
}
media := MediaDir(l.dir)
err = os.MkdirAll(m.codexHome, 0o700)
if err != nil {
return "", err
}
s, err := codex.Start(ctx, codex.Options{
Cwd: media,
Env: []string{"CODEX_HOME=" + m.codexHome},
Guidance: imageGenGuidance,
})
if err != nil {
return "", fmt.Errorf("start codex: %w", err)
}
defer s.Close()
_, err = s.Send(ctx, args.Prompt)
if err != nil {
return "", fmt.Errorf("start codex turn: %w", err)
}
saved := ""
for {
select {
case <-ctx.Done():
s.Kill()
return "", fmt.Errorf("no image after %s", mcp.Timeout(ctx))
case ev, ok := <-s.Events():
if !ok {
return "", fmt.Errorf("codex exited before the image was made")
}
switch ev.Method {
case codex.MethodItemCompleted:
n := codex.ItemCompletedNotification{}
err := json.Unmarshal(ev.Params, &n)
if err != nil {
return "", err
}
if n.Item.Type != codex.ThreadItemTypeImageGeneration {
continue
}
if n.Item.Failure != nil {
return "", fmt.Errorf("image generation failed: %s (limit %s)", n.Item.Failure.Type, n.Item.Failure.LimitID)
}
saved = string(n.Item.SavedPath)
case codex.MethodTurnCompleted:
n := codex.TurnCompletedNotification{}
err := json.Unmarshal(ev.Params, &n)
if err != nil {
return "", err
}
if n.Turn.Error != nil {
return "", fmt.Errorf("codex: %s", n.Turn.Error.Message)
}
if saved == "" {
return "", fmt.Errorf("codex finished without making an image")
}
path, err := copyToMedia(media, saved)
if err != nil {
return "", err
}
return fmt.Sprintf("Saved %s", path), nil
}
}
}
}
func copyToMedia(media, src string) (string, error) {
data, err := os.ReadFile(src)
if err != nil {
return "", err
}
err = os.MkdirAll(media, 0o755)
if err != nil {
return "", err
}
dest := filepath.Join(media, filepath.Base(src))
return dest, os.WriteFile(dest, data, 0o644)
}
+15
View File
@@ -0,0 +1,15 @@
How agents make real images (photos, illustrations) with generate_image, and how they show inline in the transcript.
# Generating images
`generate_image` makes a raster image from a prompt and saves it as a PNG in the task's media folder (`<worktree>-media/`, outside the worktree, deleted when the task closes). Embed the path it returns in your final message as `![what it shows](<path>)` and the human sees it inline in the transcript; copy it into the worktree when the project itself uses it.
It runs one Codex turn on the Mac byre runs on, under byre's Codex home, and calls Codex's built-in image tool (OpenAI's image model), so:
- It needs Codex signed in there (the `setup` article covers it); until then it fails with `401 Unauthorized`.
- Each image counts against the ChatGPT plan's Codex usage, whichever vendor the calling agent runs on.
- One call takes a minute or two; give it a timeout of at least 3 minutes.
Codex agents (gpt-* models) also have the image tool built in. An image one makes is copied into the media folder and shown inline in the transcript by byre, with no Markdown needed.
Write the prompt as a short spec: subject, style or medium, composition, lighting, and any exact text in quotes. For a change, generate again with the one change stated; it can't edit an earlier image in place.
+46
View File
@@ -0,0 +1,46 @@
How to walk the human through setting up byre: the tools the server's agents need, vendor sign-ins, and the macOS permissions on the server's Mac and each client Mac.
# Walking the human through setup
Go one step at a time: check, say in a line or two what is missing and what it is for, fix what you can yourself, and ask the human only for what needs their hands (a password, a sign-in, a click on a macOS prompt). Check again after each fix, and finish with a short list of what is ready and what was left out on purpose. Ask which vendors the human wants (Claude, GPT, Gemini) before installing agent CLIs; one is enough.
`byre` is `/Applications/Byre.app/Contents/Helpers/byre` and `byre-hands` is beside it, on every Mac with the app.
## Tools on the server's Mac
Agents run on the Mac byre runs on, the one your own Bash runs on. Check its tools with:
```
"$SHELL" -l -c '/Applications/Byre.app/Contents/Helpers/byre check'
```
It prints one JSON line: `ready` (false while something blocking is missing) and `checks`, each with `name`, `need` (`required`; `agent`, where one of claude, codex and agy is enough; or `optional`), `for` (what it is used for), `ok`, `command` (what installs it), `fix` (what to do after) and `blocking`.
- Run each missing tool's `command` yourself through the login shell, `"$SHELL" -l -c '<command>'`. `brew` must be there first; installing Homebrew asks for the human's password, so ask them to run its installer in a terminal on that Mac (the app's Server tab there lists each missing tool with a Run in Terminal button). `xcode-select --install` opens a macOS dialog on that Mac's screen for the human to click Install.
- Sign-in needs the human at a terminal on that Mac: `claude`, `codex login` or `agy`, each run once. `claude auth status` (JSON, `loggedIn`) and `codex login status` show whether one is signed in; agy has no status command, so ask.
- The service's `PATH` is fixed when it is installed, and your own shell has the same one. When a tool is found through the login shell but not by `/Applications/Byre.app/Contents/Helpers/byre check` run directly, reinstall the service from the login shell so it picks the new `PATH` up: `"$SHELL" -l -c '/Applications/Byre.app/Contents/Helpers/byre install'`. That restarts byre and cuts your turn off; you resume with a restart message, so say before running it that you will carry on afterwards.
- Google Chrome, for agents' browser tools, asks "downloaded from the Internet" on its first launch, and can't be driven until the human answers Open.
## Permissions on the server's Mac
Agents see and control the server's Mac, and read its Desktop, Documents, Downloads and iCloud Drive folders, through macOS privacy grants that belong to byre. Check them with a copy of `byre-hands` (each run needs its own path):
```
d=$(mktemp -d) && cp /Applications/Byre.app/Contents/Helpers/byre-hands "$d/" && "$d/byre-hands" check; rm -rf "$d"
```
It prints a JSON array, one entry per grant: `name`, `status` (`granted`, `missing`, or `unchecked` when the test couldn't run, such as Automation of Orion while Orion isn't running), `detail` (what to switch on) and `settings`, the `x-apple.systempreferences` URL of its System Settings pane.
- A missing folder grant makes macOS show a prompt on that Mac's screen during the check, and the check waits until it is answered, so tell the human to be at that Mac before you run it, and give the call a timeout of a few minutes.
- For Screen Recording, Accessibility and Automation, open the pane for the human with `open '<settings>'` and tell them which switch to turn on. Accessibility can list byre switched off; only the switch being on counts.
- Check again after the human says they've done it.
## Client Macs
`list_clients` lists the human's Macs with the app open (and their iPhone, which needs nothing). Agents use a client Mac through `client_hands` and the client file tools, with grants that belong to byre's app there. Check them with `client_bash` on that Mac, running the same `byre-hands` command as above, and open a missing grant's pane there the same way with `open '<settings>'`. Ask before opening anything on a client Mac's screen, and only when the human is at it. The human can also run the check from the app's Settings tab on that Mac.
Client Macs need no agent CLIs. Google Chrome is optional there, for agents' `chrome` tool on that Mac; `"$SHELL" -l -c 'brew install --cask google-chrome'` through `client_bash` installs it when `brew` is there.
## What to leave alone
Don't change anything the check reports as found, and don't install optional tools the human doesn't want.
+1
View File
@@ -31,6 +31,7 @@ const leadGuidance = `You are the human's lead agent in byre, shown in the left
- Reviews: after each new snapshot of a task, the stages of its project's review workflow (a saved workflow whose stages are single agents, set per project with set_review_workflow) review the change, each a reviewer agent on its stage's model; a stage after others gets their reviews. A project with no review workflow isn't reviewed. Once every stage has reviewed the snapshot, the task's agent gets all their findings in one message. The results are in the task's transcript, where its agent answers each finding with respond_to_finding (fixing, or ignoring with a reason), and read_transcript shows each review's findings. They are for the human to look at when they want; mention one only when its findings matter to what the human is deciding.
- Dig into context with list_tasks, read_transcript, read_inbox and read_status (vendor rate limits and agent health), and with your own Read, Grep and Bash on a task's worktree path (from list_tasks). Don't edit files in task worktrees; ask the task's agent with send_message.
- You can create tasks, start TODO items, message, interrupt and compact task agents, and merge or discard tasks. Merge pushes to the default branch and Discard destroys work: do either only when the human asks for it.
- Setup: when the human asks you to set byre up, or about the tools or macOS permissions its agents need, read the setup knowledge-base article and walk them through it, running its checks and fixes yourself with your own Bash on this Mac and with list_clients and client_bash on their Macs.
- Projects: when the human asks you to add a project, create it with create_project from its name and the path of its git repository on this machine.
- Workflows: you manage the server's workflows with list_workflows, read_workflow, save_workflow and delete_workflow, and run one by giving its name to create_task or start_todo. A workflow is a tree of steps run in order, each a stage, an order or a parallel; every stage runs as its own task on the stage's model once the step before it has finished, so stages fan out (a parallel after a stage) and fan in (a stage after a parallel); a stage's agent calls finish_stage to hand its output to the stages after it, signals tell you when a stage starts and when the workflow finishes, and the workflow finishes once every stage has; byre then discards the run's other stage tasks, leaving the task of each last stage (one per branch when it ends in a parallel) to merge. Each last stage's agent is asked whether its task is ready to merge as soon as its own stage has finished and been reviewed, without waiting for the other last stages. Whenever you show or propose a workflow, draw it as a fenced diagram block (its format is the diagrams knowledge-base article) of its stage names only, with no models: one row per step, a parallel's stages side by side in one row, and an arrow into each stage from the stages before it. For example, rows holding Build; Review and Test; Fix, with the arrows "build -> review", "build -> test", "review -> fix" and "test -> fix".
- Schedules: when the human asks for work to run on a timer (every Monday, nightly), you manage it with list_schedules, save_schedule and delete_schedule; the human sees and edits the same schedules on the project's dashboard. Every schedule runs a saved workflow: for a one-agent chore, save a one-stage workflow with save_workflow first. On each run, byre starts the schedule's workflow as create_task with workflow would, created by byre, and you get its created signal; a run that fails to start reaches you as a signal.
+1 -2
View File
@@ -27,8 +27,7 @@ func (m *Manager) prepareMainCommit(ctx context.Context, task api.Task, project
return commit, notes, err
}
func advanceMainCheckout(project api.Project, commit, notes string) error {
pushed := git.Remote + "/" + project.DefaultBranch
func advanceMainCheckout(project api.Project, commit, notes, pushed string) error {
err := git.AdvanceNotes(context.Background(), project.RepoPath, notes)
if err != nil {
return fmt.Errorf("pushed %s to %s, but updating %s in the project checkout failed: %w", commit, pushed, git.NotesRef, err)
+2 -1
View File
@@ -885,6 +885,7 @@ func (m *Manager) startLive(ctx context.Context, sess api.AgentSession, task api
return nil, err
}
g += screenshotGuidance
tools = append(tools, m.generateImageTool(l))
hands, err := m.handsMCP(sess.Id, media)
if err != nil {
return nil, err
@@ -914,7 +915,7 @@ func (m *Manager) startLive(ctx context.Context, sess api.AgentSession, task api
return nil, err
}
l.notes = "\n\n" + notes
tools = append(m.leadTools(l), m.readKnowledgeTool(), m.renderDiagramTool())
tools = append(append(m.leadTools(l), m.readKnowledgeTool(), m.renderDiagramTool()), m.clientShellTools()...)
}
env := git.IdentityEnv(l.gitName, l.gitEmail)
servers := []*mcp.Server{{Name: "byre", Version: "0.0.0", Tools: tools}}
+15 -5
View File
@@ -218,8 +218,11 @@ func (m *Manager) deliverMerge(ctx context.Context, sess api.AgentSession, task
}
func pushLanded(ctx context.Context, project api.Project, commit string) (bool, error) {
target := git.Remote + "/" + project.DefaultBranch
err := git.Fetch(ctx, project.RepoPath)
target, err := git.Upstream(ctx, project.RepoPath, project.DefaultBranch)
if err != nil {
return false, &unfinishedMergeError{fmt.Errorf("checking whether %s reached %s failed: %w", commit, project.DefaultBranch, err)}
}
err = git.Fetch(ctx, project.RepoPath)
if err != nil {
return false, &unfinishedMergeError{fmt.Errorf("checking whether %s reached %s failed: %w", commit, target, err)}
}
@@ -341,7 +344,11 @@ func (m *Manager) landMerge(ctx context.Context, f store.FinalizingMerge) error
}
return m.closeUnchanged(ctx, task, project, f.By)
}
err = m.store.SetFinalizingCommit(ctx, task.Id, commit, notes, git.Remote+"/"+project.DefaultBranch)
pushed, err := git.Upstream(ctx, project.RepoPath, project.DefaultBranch)
if err != nil {
return err
}
err = m.store.SetFinalizingCommit(ctx, task.Id, commit, notes, pushed)
if err != nil {
return err
}
@@ -376,8 +383,11 @@ func (m *Manager) closeUnchanged(ctx context.Context, task api.Task, project api
}
func (m *Manager) closePushed(ctx context.Context, task api.Task, project api.Project, commit, notes, login string) error {
pushed := git.Remote + "/" + project.DefaultBranch
err := advanceMainCheckout(project, commit, notes)
pushed, err := git.Upstream(ctx, project.RepoPath, project.DefaultBranch)
if err != nil {
return &unfinishedMergeError{err}
}
err = advanceMainCheckout(project, commit, notes, pushed)
if err != nil {
return &unfinishedMergeError{err}
}
+9 -3
View File
@@ -102,7 +102,10 @@ func (m *Manager) mergeToMain(ctx context.Context, l *live, raw json.RawMessage)
return fmt.Sprintf("Nothing to push: %s already has all of this branch's work, so byre merged %s into this branch. Keep working; the task stays open.", project.DefaultBranch, project.DefaultBranch), nil
}
pushed := git.Remote + "/" + project.DefaultBranch
pushed, err := git.Upstream(ctx, project.RepoPath, project.DefaultBranch)
if err != nil {
return "", err
}
err = m.emit(ctx, l, agentActor, "task.merged_to_main", map[string]any{"id": task.Id, "commit": commit, "pushed": pushed, "merge_title": title})
if err != nil {
return "", err
@@ -142,8 +145,11 @@ func (m *Manager) pushToMain(ctx context.Context, l *live, task api.Task, projec
return "", err
}
pushed := git.Remote + "/" + project.DefaultBranch
err = advanceMainCheckout(project, commit, notes)
pushed, err := git.Upstream(ctx, project.RepoPath, project.DefaultBranch)
if err != nil {
return "", err
}
err = advanceMainCheckout(project, commit, notes, pushed)
if err != nil {
return "", err
}
+514 -358
View File
@@ -882,7 +882,7 @@ type CompactCreate struct {
// ConnectedClient defines model for ConnectedClient.
type ConnectedClient struct {
// Build The app's build number (its CFBundleVersion, the Unix time of that commit), from its Byre-Build header; empty when the client sent none.
// Build The app's build number (its CFBundleVersion, the Unix time it was built), from its Byre-Build header; empty when the client sent none.
Build string `json:"build"`
// Commit The git commit the app was built from
@@ -1873,6 +1873,23 @@ type ListLeadEventsParams struct {
Turns *int `form:"turns,omitempty" json:"turns,omitempty"`
}
// GetProjectDirParams defines parameters for GetProjectDir.
type GetProjectDirParams struct {
// Path Slash-separated, absolute or relative to the project's repository root; empty or absent for the root.
Path *string `form:"path,omitempty" json:"path,omitempty"`
}
// GetProjectFileParams defines parameters for GetProjectFile.
type GetProjectFileParams struct {
// Path Slash-separated, absolute or relative to the project's repository root.
Path string `form:"path" json:"path"`
}
// GetProjectImageParams defines parameters for GetProjectImage.
type GetProjectImageParams struct {
Path string `form:"path" json:"path"`
}
// GetTaskChangesParams defines parameters for GetTaskChanges.
type GetTaskChangesParams struct {
// Since A commit to compare with instead of the merge-base, such as a user.message event's head. Not allowed with snapshot.
@@ -2170,6 +2187,15 @@ type ServerInterface interface {
// (PUT /api/projects/{projectId}/audit-workflow)
SetAuditWorkflow(w http.ResponseWriter, r *http.Request, projectId string)
// (GET /api/projects/{projectId}/dir)
GetProjectDir(w http.ResponseWriter, r *http.Request, projectId string, params GetProjectDirParams)
// (GET /api/projects/{projectId}/file)
GetProjectFile(w http.ResponseWriter, r *http.Request, projectId string, params GetProjectFileParams)
// (GET /api/projects/{projectId}/image)
GetProjectImage(w http.ResponseWriter, r *http.Request, projectId string, params GetProjectImageParams)
// (PUT /api/projects/{projectId}/review-workflow)
SetReviewWorkflow(w http.ResponseWriter, r *http.Request, projectId string)
@@ -3147,6 +3173,132 @@ func (siw *ServerInterfaceWrapper) SetAuditWorkflow(w http.ResponseWriter, r *ht
handler.ServeHTTP(w, r)
}
// GetProjectDir operation middleware
func (siw *ServerInterfaceWrapper) GetProjectDir(w http.ResponseWriter, r *http.Request) {
var err error
_ = err
// ------------- Path parameter "projectId" -------------
var projectId string
err = runtime.BindStyledParameterWithOptions("simple", "projectId", r.PathValue("projectId"), &projectId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "", ValueIsUnescaped: true})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectId", Err: err})
return
}
// Parameter object where we will unmarshal all parameters from the context
var params GetProjectDirParams
// ------------- Optional query parameter "path" -------------
err = runtime.BindQueryParameterWithOptions("form", true, false, "path", r.URL.Query(), &params.Path, runtime.BindQueryParameterOptions{Type: "string", Format: ""})
if err != nil {
var requiredError *runtime.RequiredParameterError
if errors.As(err, &requiredError) {
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"})
} else {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err})
}
return
}
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.GetProjectDir(w, r, projectId, params)
}))
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
}
// GetProjectFile operation middleware
func (siw *ServerInterfaceWrapper) GetProjectFile(w http.ResponseWriter, r *http.Request) {
var err error
_ = err
// ------------- Path parameter "projectId" -------------
var projectId string
err = runtime.BindStyledParameterWithOptions("simple", "projectId", r.PathValue("projectId"), &projectId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "", ValueIsUnescaped: true})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectId", Err: err})
return
}
// Parameter object where we will unmarshal all parameters from the context
var params GetProjectFileParams
// ------------- Required query parameter "path" -------------
err = runtime.BindQueryParameterWithOptions("form", true, true, "path", r.URL.Query(), &params.Path, runtime.BindQueryParameterOptions{Type: "string", Format: ""})
if err != nil {
var requiredError *runtime.RequiredParameterError
if errors.As(err, &requiredError) {
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"})
} else {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err})
}
return
}
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.GetProjectFile(w, r, projectId, params)
}))
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
}
// GetProjectImage operation middleware
func (siw *ServerInterfaceWrapper) GetProjectImage(w http.ResponseWriter, r *http.Request) {
var err error
_ = err
// ------------- Path parameter "projectId" -------------
var projectId string
err = runtime.BindStyledParameterWithOptions("simple", "projectId", r.PathValue("projectId"), &projectId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "", ValueIsUnescaped: true})
if err != nil {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectId", Err: err})
return
}
// Parameter object where we will unmarshal all parameters from the context
var params GetProjectImageParams
// ------------- Required query parameter "path" -------------
err = runtime.BindQueryParameterWithOptions("form", true, true, "path", r.URL.Query(), &params.Path, runtime.BindQueryParameterOptions{Type: "string", Format: ""})
if err != nil {
var requiredError *runtime.RequiredParameterError
if errors.As(err, &requiredError) {
siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "path"})
} else {
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "path", Err: err})
}
return
}
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.GetProjectImage(w, r, projectId, params)
}))
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
}
// SetReviewWorkflow operation middleware
func (siw *ServerInterfaceWrapper) SetReviewWorkflow(w http.ResponseWriter, r *http.Request) {
@@ -4790,6 +4942,9 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H
m.HandleFunc(http.MethodDelete+" "+options.BaseURL+"/api/schedules/{scheduleId}", wrapper.DeleteSchedule)
m.HandleFunc(http.MethodPut+" "+options.BaseURL+"/api/schedules/{scheduleId}", wrapper.UpdateSchedule)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/projects/{projectId}/todo", wrapper.GetTodo)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/projects/{projectId}/dir", wrapper.GetProjectDir)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/projects/{projectId}/file", wrapper.GetProjectFile)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/projects/{projectId}/image", wrapper.GetProjectImage)
m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/projects/{projectId}/todo/tasks", wrapper.StartTodoItem)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/tasks/{taskId}", wrapper.GetTask)
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/tasks/{taskId}/workflow", wrapper.GetTaskWorkflow)
@@ -4856,363 +5011,364 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H
// const string: with thousands of chunks the chained `+` fold is several
// times slower for the Go compiler than parsing a slice literal.
var swaggerSpec = []string{
"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",
}
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
+39 -5
View File
@@ -53,14 +53,30 @@ func Inspect(ctx context.Context, path string) (RepoInfo, error) {
return RepoInfo{}, err
}
remote, err := run(ctx, root, "remote", "get-url", "origin")
remote, _, code, err := runCode(ctx, root, nil, "remote", "get-url", Remote)
if err != nil {
return RepoInfo{}, err
}
if code != 0 {
remote = ""
}
return RepoInfo{Root: root, DefaultBranch: branch, Remote: remote}, nil
}
func HasRemote(ctx context.Context, repo string) (bool, error) {
_, _, code, err := runCode(ctx, repo, nil, "remote", "get-url", Remote)
return err == nil && code == 0, err
}
func Upstream(ctx context.Context, repo, branch string) (string, error) {
has, err := HasRemote(ctx, repo)
if err != nil || !has {
return branch, err
}
return Remote + "/" + branch, nil
}
func AddWorktree(ctx context.Context, repo, path, branch, base string) (string, error) {
sha, err := run(ctx, repo, "rev-parse", "--verify", base+"^{commit}")
if err != nil {
@@ -92,12 +108,18 @@ func EnsureBranchCommit(ctx context.Context, repo, branch string, env []string)
if err != nil {
return err
}
_, stderr, code, err := runNetwork(ctx, repo, "push", Remote, commit+":refs/heads/"+branch)
has, err := HasRemote(ctx, repo)
if err != nil {
return err
}
if code != 0 {
return fmt.Errorf("pushing %s to %s: exit status %d: %s", branch, Remote, code, stderr)
if has {
_, stderr, code, err := runNetwork(ctx, repo, "push", Remote, commit+":refs/heads/"+branch)
if err != nil {
return err
}
if code != 0 {
return fmt.Errorf("pushing %s to %s: exit status %d: %s", branch, Remote, code, stderr)
}
}
_, err = run(ctx, repo, "update-ref", "refs/heads/"+branch, commit, "")
return err
@@ -258,6 +280,10 @@ var networkEnv = []string{
var ErrRemoteMoved = errors.New("the remote's branch has moved")
func Push(ctx context.Context, repo, commit, branch, notes string) error {
has, err := HasRemote(ctx, repo)
if err != nil || !has {
return err
}
out, stderr, code, err := runNetwork(ctx, repo, "push", "--atomic", "--porcelain", Remote, commit+":refs/heads/"+branch, notes+":"+NotesRef)
if err != nil {
return err
@@ -279,6 +305,10 @@ func FastForward(ctx context.Context, repo, rev string) error {
}
func Fetch(ctx context.Context, repo string) error {
has, err := HasRemote(ctx, repo)
if err != nil || !has {
return err
}
_, stderr, code, err := runNetwork(ctx, repo, "fetch", Remote)
if err != nil {
return err
@@ -290,7 +320,11 @@ func Fetch(ctx context.Context, repo string) error {
}
func FetchFastForward(ctx context.Context, repo, branch string) error {
err := Fetch(ctx, repo)
has, err := HasRemote(ctx, repo)
if err != nil || !has {
return err
}
err = Fetch(ctx, repo)
if err != nil {
return err
}
+4
View File
@@ -14,6 +14,10 @@ const NotesRef = "refs/notes/byre"
const notesFetch = NotesRef + "*:" + NotesRef + "*"
func ConfigureNotesFetch(ctx context.Context, repo string) error {
has, err := HasRemote(ctx, repo)
if err != nil || !has {
return err
}
key := "remote." + Remote + ".fetch"
out, stderr, code, err := runCode(ctx, repo, nil, "config", "--get-all", key)
if err != nil {
+35 -9
View File
@@ -29,11 +29,24 @@ func (s *Server) GetTaskDir(w http.ResponseWriter, r *http.Request, taskID strin
if !ok {
return
}
rel := ""
if params.Path != nil {
rel = *params.Path
serveDir(w, r, t.WorktreePath, params.Path)
}
func (s *Server) GetProjectDir(w http.ResponseWriter, r *http.Request, projectID string, params api.GetProjectDirParams) {
p, err := s.store.GetProject(r.Context(), projectID)
if err != nil {
writeStoreError(w, err)
return
}
name, full := taskPath(t.WorktreePath, rel)
serveDir(w, r, p.RepoPath, params.Path)
}
func serveDir(w http.ResponseWriter, r *http.Request, root string, p *string) {
rel := ""
if p != nil {
rel = *p
}
name, full := taskPath(root, rel)
f, err := os.Open(full)
if err != nil {
@@ -84,7 +97,7 @@ func (s *Server) GetTaskDir(w http.ResponseWriter, r *http.Request, taskID strin
listing.Truncated = true
}
real, err := realDir(t.WorktreePath, full)
real, err := realDir(root, full)
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
@@ -97,7 +110,7 @@ func (s *Server) GetTaskDir(w http.ResponseWriter, r *http.Request, taskID strin
for _, e := range listing.Entries {
paths = append(paths, path.Join(real, e.Name))
}
ignored, err := git.Ignored(r.Context(), t.WorktreePath, paths)
ignored, err := git.Ignored(r.Context(), root, paths)
if err != nil {
writeError(w, http.StatusInternalServerError, err)
return
@@ -154,11 +167,24 @@ func (s *Server) GetTaskFile(w http.ResponseWriter, r *http.Request, taskID stri
if !ok {
return
}
if params.Path == "" {
serveFile(w, t.WorktreePath, params.Path)
}
func (s *Server) GetProjectFile(w http.ResponseWriter, r *http.Request, projectID string, params api.GetProjectFileParams) {
p, err := s.store.GetProject(r.Context(), projectID)
if err != nil {
writeStoreError(w, err)
return
}
serveFile(w, p.RepoPath, params.Path)
}
func serveFile(w http.ResponseWriter, root, rel string) {
if rel == "" {
writeError(w, http.StatusBadRequest, errors.New("path is required"))
return
}
name, full := taskPath(t.WorktreePath, params.Path)
name, full := taskPath(root, rel)
fi, err := os.Stat(full)
if err != nil {
@@ -166,7 +192,7 @@ func (s *Server) GetTaskFile(w http.ResponseWriter, r *http.Request, taskID stri
return
}
if !fi.Mode().IsRegular() {
writeError(w, http.StatusBadRequest, fmt.Errorf("%s is not a regular file", params.Path))
writeError(w, http.StatusBadRequest, fmt.Errorf("%s is not a regular file", rel))
return
}
+17 -5
View File
@@ -26,14 +26,26 @@ func (s *Server) GetTaskImage(w http.ResponseWriter, r *http.Request, taskID str
if !ok {
return
}
serveImage(w, r, t.WorktreePath, params.Path)
}
path := params.Path
func (s *Server) GetProjectImage(w http.ResponseWriter, r *http.Request, projectID string, params api.GetProjectImageParams) {
p, err := s.store.GetProject(r.Context(), projectID)
if err != nil {
writeStoreError(w, err)
return
}
serveImage(w, r, p.RepoPath, params.Path)
}
func serveImage(w http.ResponseWriter, r *http.Request, root, rel string) {
path := rel
if !filepath.IsAbs(path) {
path = filepath.Join(t.WorktreePath, path)
path = filepath.Join(root, path)
}
real, err := filepath.EvalSymlinks(path)
if errors.Is(err, fs.ErrNotExist) {
writeError(w, http.StatusNotFound, fmt.Errorf("%s does not exist", params.Path))
writeError(w, http.StatusNotFound, fmt.Errorf("%s does not exist", rel))
return
}
if err != nil {
@@ -43,7 +55,7 @@ func (s *Server) GetTaskImage(w http.ResponseWriter, r *http.Request, taskID str
contentType := worktreeImageTypes[strings.ToLower(filepath.Ext(real))]
if contentType == "" {
writeError(w, http.StatusBadRequest, fmt.Errorf("%s is not a PNG, JPEG, GIF, WebP or SVG file", params.Path))
writeError(w, http.StatusBadRequest, fmt.Errorf("%s is not a PNG, JPEG, GIF, WebP or SVG file", rel))
return
}
@@ -60,7 +72,7 @@ func (s *Server) GetTaskImage(w http.ResponseWriter, r *http.Request, taskID str
return
}
if !info.Mode().IsRegular() {
writeError(w, http.StatusBadRequest, fmt.Errorf("%s is not a file", params.Path))
writeError(w, http.StatusBadRequest, fmt.Errorf("%s is not a file", rel))
return
}
+2
View File
@@ -24,6 +24,8 @@
<true/>
<key>SUAutomaticallyUpdate</key>
<true/>
<key>SUScheduledCheckInterval</key>
<integer>3600</integer>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoadsInWebContent</key>
+23
View File
@@ -26,6 +26,8 @@ private let leadWelcome = """
Start in [Settings](byre-app://tab?name=settings): \(leadSettings)
"""
private let leadSetup = "Walk me through setting up byre: the tools its agents need, signing in to their vendors, and the macOS permissions on the server's Mac and on my Macs."
struct AttentionGroup: Identifiable {
let id: String
let label: String
@@ -501,6 +503,20 @@ final class ChatModel {
}
}
func askSetup(_ overlays: Overlays) {
let sendID = newID("snd")
let cancel = builder.showPending(leadSetup, [], sendID)
Task {
do {
try await send(sessionId: liveSession?.id, text: leadSetup, images: nil, model: nil, firstModel: store.defaultModel, sendID: sendID)
} catch {
if cancel() {
overlays.showAlert(errorText(error))
}
}
}
}
func withdraw(_ p: PendingSend, _ overlays: Overlays) async {
guard let taskId, let inputId = p.inputId else {
return
@@ -802,6 +818,7 @@ private struct ScrollMetrics: Equatable {
private struct TranscriptScroll: View {
let model: ChatModel
@Environment(Store.self) private var store
@Environment(Overlays.self) private var overlays
@Environment(\.paneAccent) private var accent
@ViewState private var position = ScrollPosition(edge: .bottom)
@ViewState private var scrolling = false
@@ -835,6 +852,12 @@ private struct TranscriptScroll: View {
if model.welcome {
MarkdownView(blocks: parseMarkdown(leadWelcome))
.padding(.top, 12)
Button("Walk me through setup") {
model.askSetup(overlays)
}
.buttonStyle(WebButtonStyle())
.help("Ask your lead to check and set up the tools and permissions byre's agents need")
.padding(.top, 12)
}
}
}
+5 -1
View File
@@ -38,6 +38,10 @@ enum LeftTab: String {
case lead, status, notes, workflows, server, settings
}
enum ProjectTab {
case tasks, files, settings
}
@MainActor
@Observable
final class Overlays {
@@ -53,7 +57,7 @@ final class Overlays {
var leadFocus = 0
var tabFocus = 0
var projectFocus = false
var projectSettings = false
var projectTab = ProjectTab.tasks
var taskList = false
@ObservationIgnored var pickerFrame: CGRect = .zero
@ObservationIgnored var menuHover = false
-9
View File
@@ -139,15 +139,6 @@ final class Drafts {
func prune(_ store: Store) {
var changed = false
for key in Set(composerTexts.keys).union(composerImages.keys).union(lostImages.keys) {
let taskId = key.hasPrefix("message:") ? String(key.dropFirst("message:".count)) : key
if taskId != "lead" && store.findTask(taskId)?.status != .active {
composerTexts[key] = nil
composerImages[key] = nil
lostImages[key] = nil
changed = true
}
}
for taskId in comments.keys where store.findTask(taskId)?.status != .active {
comments[taskId] = nil
changed = true
+77 -32
View File
@@ -5,19 +5,31 @@ typealias DirListing = Components.Schemas.DirListing
typealias DirEntry = Components.Schemas.DirEntry
typealias FileContent = Components.Schemas.FileContent
enum FileRoot: Hashable {
case task(String)
case project(String)
var name: String {
if case .project = self {
return "repo"
}
return "worktree"
}
}
struct FilePlace: Equatable {
var dir = ""
var file: String?
}
private struct FilesKey: Equatable {
let taskId: String?
let root: FileRoot?
let place: FilePlace
}
extension Store {
var filePlace: FilePlace {
selectedTaskId.flatMap { filePlaces[$0] } ?? FilePlace()
func filePlace(_ root: FileRoot?) -> FilePlace {
root.flatMap { filePlaces[$0] } ?? FilePlace()
}
func openFile(_ taskId: String, _ path: String) {
@@ -34,13 +46,22 @@ extension Store {
}
func showInFiles(_ taskId: String, _ path: String) {
if path.isEmpty || path.hasSuffix("/") {
filePlaces[taskId] = FilePlace(dir: path == "/" ? path : String(path.dropLast()))
} else {
filePlaces[taskId] = FilePlace(dir: parent(path), file: path)
}
filePlaces[.task(taskId)] = filePlace(at: path)
route("#/task/\(taskId)/files")
}
func showInProjectFiles(_ projectId: String, _ path: String, _ overlays: Overlays) {
filePlaces[.project(projectId)] = filePlace(at: path)
overlays.projectTab = .files
route("#/project/\(projectId)")
}
private func filePlace(at path: String) -> FilePlace {
if path.isEmpty || path.hasSuffix("/") {
return FilePlace(dir: path == "/" ? path : String(path.dropLast()))
}
return FilePlace(dir: parent(path), file: path)
}
}
private func join(_ dir: String, _ name: String) -> String {
@@ -78,6 +99,7 @@ private func shown(_ c: FileContent) -> Int64 {
@Observable
private final class FilesModel {
let store: Store
var root: FileRoot?
private(set) var listing: DirListing?
private(set) var content: FileContent?
private(set) var error = ""
@@ -91,14 +113,14 @@ private final class FilesModel {
}
var place: FilePlace {
store.filePlace
store.filePlace(root)
}
func go(_ p: FilePlace) {
guard let taskId = store.selectedTaskId else {
guard let root else {
return
}
store.filePlaces[taskId] = p
store.filePlaces[root] = p
}
func reset() {
@@ -114,22 +136,29 @@ private final class FilesModel {
}
func load() async {
guard let taskId = store.selectedTaskId else {
guard let root else {
return
}
target += 1
let started = target
let p = place
let client = store.api.client
do {
if let file = p.file {
let fresh = try await store.api.client.getTaskFile(path: .init(taskId: taskId), query: .init(path: file)).ok.body.json
let fresh = switch root {
case .task(let id): try await client.getTaskFile(path: .init(taskId: id), query: .init(path: file)).ok.body.json
case .project(let id): try await client.getProjectFile(path: .init(projectId: id), query: .init(path: file)).ok.body.json
}
if started == target {
content = fresh
listing = nil
loads += 1
}
} else {
let fresh = try await store.api.client.getTaskDir(path: .init(taskId: taskId), query: .init(path: p.dir)).ok.body.json
let fresh = switch root {
case .task(let id): try await client.getTaskDir(path: .init(taskId: id), query: .init(path: p.dir)).ok.body.json
case .project(let id): try await client.getProjectDir(path: .init(projectId: id), query: .init(path: p.dir)).ok.body.json
}
if started == target {
listing = fresh
content = nil
@@ -147,10 +176,7 @@ private final class FilesModel {
}
}
func transcript(_ fresh: [ByreEvent]) {
if !fresh.contains(where: { $0.kind == "agent.tool_result" || $0.kind == "turn.ended" }) {
return
}
func changed() {
timer?.cancel()
timer = Task {
try? await Task.sleep(for: .milliseconds(1500))
@@ -176,11 +202,12 @@ private final class FilesModel {
if let l = listing {
return "\(l.entries.count)\(l.truncated ? "+" : "") \(l.entries.count == 1 ? "entry" : "entries")"
}
return store.selectedTaskId != nil ? "Loading…" : ""
return root != nil ? "Loading…" : ""
}
}
struct FilesView: View {
let root: FileRoot?
@Environment(Store.self) private var store
@ViewState private var model: FilesModel?
@@ -192,12 +219,26 @@ struct FilesView: View {
}
}
.frame(maxHeight: .infinity, alignment: .top)
.task(id: FilesKey(taskId: store.selectedTaskId, place: store.filePlace)) {
.task(id: FilesKey(root: root, place: store.filePlace(root))) {
let m = model ?? FilesModel(store)
model = m
m.root = root
m.reset()
let off = store.selectedFeed?.appended.add { [weak m] events in
m?.transcript(events)
let off: (() -> Void)? = switch root {
case .task:
store.selectedFeed?.appended.add { [weak m] events in
if events.contains(where: { $0.kind == "agent.tool_result" || $0.kind == "turn.ended" }) {
m?.changed()
}
}
case .project(let id):
store.events.add { [weak m] ev in
if (ev.kind == "task.merged" || ev.kind == "task.merged_to_main") && ev.projectId == id {
m?.changed()
}
}
case nil:
nil
}
await untilCancelled()
off?()
@@ -217,7 +258,7 @@ private struct FilesHead: View {
let base = absolute ? "/" : ""
HStack(alignment: .firstTextBaseline, spacing: 12) {
FlowLayout(spacing: 2) {
crumb(absolute ? "/" : "worktree", base, parts.isEmpty)
crumb(absolute ? "/" : model.root?.name ?? "worktree", base, parts.isEmpty)
ForEach(Array(parts.enumerated()), id: \.offset) { i, part in
if !absolute || i > 0 {
Text("/").foregroundStyle(Palette.muted)
@@ -255,18 +296,18 @@ private struct FilesBody: View {
@Environment(Store.self) private var store
var body: some View {
if store.selectedTaskId == nil {
if model.root == nil {
EmptyText(text: "No task selected.")
} else if !model.error.isEmpty {
EmptyText(text: model.error, error: true)
} else if let c = model.content, let taskId = store.selectedTaskId {
} else if let c = model.content, let root = model.root {
if let file = model.place.file, isImage(file) {
FileImage(taskId: taskId, path: file, load: model.loads)
FileImage(root: root, path: file, load: model.loads)
.id(model.generation)
} else if c.binary {
EmptyText(text: "Binary file.")
} else if model.place.file?.lowercased().hasSuffix(".md") == true {
FileMarkdown(content: c, taskId: taskId, dir: model.place.dir)
FileMarkdown(content: c, root: root, dir: model.place.dir)
.id(model.generation)
} else {
FileText(content: c)
@@ -285,7 +326,7 @@ private struct FilesBody: View {
EntryRow(label: e._type == .dir ? "\(e.name)/" : e.name, kind: e._type, ignored: e.ignored, meta: meta, symlink: e.symlink) {
if e._type == .dir {
model.go(FilePlace(dir: join(l.path, e.name)))
} else if e._type == .file && isPage(e.name), let taskId = store.selectedTaskId {
} else if e._type == .file && isPage(e.name), case .task(let taskId) = model.root {
store.openPage(.file(taskId: taskId, path: join(l.path, e.name)))
} else if e._type == .file {
model.go(FilePlace(dir: l.path, file: join(l.path, e.name)))
@@ -355,13 +396,17 @@ private struct EntryRow: View {
private struct FileMarkdown: View {
let content: FileContent
let taskId: String?
let root: FileRoot
let dir: String
var body: some View {
let ctx = switch root {
case .task(let id): MDContext(taskId: id, dir: dir)
case .project(let id): MDContext(project: id, dir: dir)
}
ScrollView {
VStack(alignment: .leading, spacing: 0) {
MarkdownView(blocks: parseMarkdown(content.text), ctx: MDContext(taskId: taskId, dir: dir))
MarkdownView(blocks: parseMarkdown(content.text), ctx: ctx)
if content.truncated {
Text("Showing the first \(bytes(shown(content))) of \(bytes(content.size)).")
.foregroundStyle(Palette.muted)
@@ -376,7 +421,7 @@ private struct FileMarkdown: View {
}
private struct FileImage: View {
let taskId: String
let root: FileRoot
let path: String
let load: Int
@Environment(Store.self) private var store
@@ -407,7 +452,7 @@ private struct FileImage: View {
}
.task(id: load) {
do {
let data = try await ImageCache.fetch(.worktree(taskId: taskId, path: path), store.api)
let data = try await ImageCache.fetch(.file(root, path: path), store.api)
guard let img = PlatformImage(data: data) else {
image = nil
error = "Not a readable image."
+10 -5
View File
@@ -3,7 +3,7 @@ import SwiftUI
enum ImageSource: Hashable {
case blob(String)
case worktree(taskId: String, path: String)
case file(FileRoot, path: String)
case data(Data)
}
@@ -11,7 +11,7 @@ private let dataImage = Re(#"^data:image/(png|jpeg|gif|webp);"#, caseless: true)
private let fileURL = Re(#"^file://"#, caseless: true)
private let anyScheme = Re(#"^[a-z][a-z0-9+.-]*:"#, caseless: true)
func worktreeImageSource(_ path: String, _ taskId: String?, dir: String = "") -> ImageSource? {
func worktreeImageSource(_ path: String, _ root: FileRoot?, dir: String = "") -> ImageSource? {
if dataImage.test(path) {
guard let comma = path.firstIndex(of: ","), path[..<comma].lowercased().hasSuffix(";base64"), let data = Data(base64Encoded: String(path[path.index(after: comma)...])) else {
return nil
@@ -29,10 +29,10 @@ func worktreeImageSource(_ path: String, _ taskId: String?, dir: String = "") ->
} else if !dir.isEmpty && !file.hasPrefix("/") {
file = "\(dir)/\(file)"
}
guard let taskId else {
guard let root else {
return nil
}
return .worktree(taskId: taskId, path: file)
return .file(root, path: file)
}
@MainActor
@@ -76,11 +76,16 @@ final class ImageCache {
case .png(let b), .jpeg(let b), .imageGif(let b), .imageWebp(let b): b
}
return try await Data(collecting: body, upTo: 32 << 20)
case .worktree(let taskId, let path):
case .file(.task(let taskId), let path):
let body = switch try await api.client.getTaskImage(path: .init(taskId: taskId), query: .init(path: path)).ok.body {
case .png(let b), .jpeg(let b), .imageGif(let b), .imageWebp(let b), .imageSvgXml(let b): b
}
return try await Data(collecting: body, upTo: 32 << 20)
case .file(.project(let projectId), let path):
let body = switch try await api.client.getProjectImage(path: .init(projectId: projectId), query: .init(path: path)).ok.body {
case .png(let b), .jpeg(let b), .imageGif(let b), .imageWebp(let b), .imageSvgXml(let b): b
}
return try await Data(collecting: body, upTo: 32 << 20)
}
}
}
+31 -13
View File
@@ -18,11 +18,14 @@ enum AppLink {
return c.url!
}
static func file(_ taskId: String, _ path: String) -> URL {
static func file(_ root: FileRoot, _ path: String) -> URL {
var c = URLComponents()
c.scheme = "byre-app"
c.host = "file"
c.queryItems = [URLQueryItem(name: "task", value: taskId), URLQueryItem(name: "path", value: path)]
c.queryItems = switch root {
case .task(let id): [URLQueryItem(name: "task", value: id), URLQueryItem(name: "path", value: path)]
case .project(let id): [URLQueryItem(name: "project", value: id), URLQueryItem(name: "path", value: path)]
}
return c.url!
}
@@ -61,7 +64,11 @@ func openAppLink(_ url: URL, _ store: Store, _ overlays: Overlays) -> OpenURLAct
case "route":
store.route(q["hash"] ?? "")
case "file":
store.openFile(q["task"] ?? "", q["path"] ?? "")
if let project = q["project"] {
store.showInProjectFiles(project, q["path"] ?? "", overlays)
} else {
store.openFile(q["task"] ?? "", q["path"] ?? "")
}
case "preview":
let taskId = q["task"] ?? ""
store.route("#/task/\(taskId)")
@@ -169,17 +176,23 @@ struct InlineRenderer {
let store: Store
let size: CGFloat
let taskId: String?
let project: String?
let weight: CGFloat
let dir: String
private let root: String
init(store: Store, size: CGFloat, taskId: String?, weight: CGFloat = 400, dir: String = "") {
init(store: Store, size: CGFloat, taskId: String?, project: String? = nil, weight: CGFloat = 400, dir: String = "") {
self.store = store
self.size = size
self.taskId = taskId
self.project = project
self.weight = weight
self.dir = dir
root = taskId.flatMap { store.findTask($0)?.worktreePath } ?? ""
if let taskId {
root = store.findTask(taskId)?.worktreePath ?? ""
} else {
root = project.flatMap { id in store.projects.first { $0.id == id }?.repoPath } ?? ""
}
}
private struct Style {
@@ -275,7 +288,7 @@ struct InlineRenderer {
s.italic = true
render(inner, s, &out)
case .image(let src, let alt):
if let source = worktreeImageSource(src, taskId, dir: dir) {
if let source = worktreeImageSource(src, files, dir: dir) {
out.append(.image(source, src: src))
} else {
link(src, [.text(alt.isEmpty ? src : alt)], style, &out)
@@ -327,11 +340,15 @@ struct InlineRenderer {
render(inner, style, &out)
}
private var files: FileRoot? {
taskId.map(FileRoot.task) ?? project.map(FileRoot.project)
}
private func fileLink(_ path: String) -> URL? {
guard let taskId, !root.isEmpty, let p = normalPath(path) else {
guard let files, !root.isEmpty, let p = normalPath(path) else {
return nil
}
return AppLink.file(taskId, p)
return AppLink.file(files, p)
}
private func codeFile(_ code: String) -> URL? {
@@ -451,6 +468,7 @@ struct MDContext {
var size: CGFloat = 13
var color: Color = Palette.text
var taskId: String?
var project: String?
var choice: ((MDItem, Bool) -> AnyView)?
var discuss: ((MDItem) -> AnyView)?
var gridColumns = 4
@@ -590,7 +608,7 @@ private struct FlowBuilder {
}
private func inline(_ inlines: [MDInline], _ size: CGFloat) -> NSAttributedString {
let a = InlineRenderer(store: store, size: size, taskId: ctx.taskId, weight: ctx.weight, dir: ctx.dir).text(inlines)
let a = InlineRenderer(store: store, size: size, taskId: ctx.taskId, project: ctx.project, weight: ctx.weight, dir: ctx.dir).text(inlines)
return nativeString(a)
}
@@ -902,7 +920,7 @@ struct InlineView: View {
@Environment(\.centerImages) private var centerImages
var body: some View {
let segs = InlineRenderer(store: store, size: ctx.size, taskId: ctx.taskId, weight: ctx.weight, dir: ctx.dir).segments(inlines)
let segs = InlineRenderer(store: store, size: ctx.size, taskId: ctx.taskId, project: ctx.project, weight: ctx.weight, dir: ctx.dir).segments(inlines)
VStack(alignment: .leading, spacing: 4) {
ForEach(Array(segs.enumerated()), id: \.offset) { _, seg in
switch seg {
@@ -948,7 +966,7 @@ private struct BlockView: View {
case .paragraph(let inlines):
InlineView(inlines: inlines, ctx: ctx)
case .heading(let level, let inlines):
InlineView(inlines: [.strong(inlines)], ctx: MDContext(size: level == 3 ? 14 : 13, color: ctx.color, taskId: ctx.taskId, dir: ctx.dir))
InlineView(inlines: [.strong(inlines)], ctx: MDContext(size: level == 3 ? 14 : 13, color: ctx.color, taskId: ctx.taskId, project: ctx.project, dir: ctx.dir))
.padding(.top, 4)
case .rule:
Rectangle()
@@ -956,7 +974,7 @@ private struct BlockView: View {
.frame(height: 1)
.padding(.vertical, 2)
case .quote(let blocks):
MarkdownView(blocks: blocks, ctx: MDContext(size: ctx.size, color: Palette.muted, taskId: ctx.taskId, dir: ctx.dir))
MarkdownView(blocks: blocks, ctx: MDContext(size: ctx.size, color: Palette.muted, taskId: ctx.taskId, project: ctx.project, dir: ctx.dir))
.padding(.leading, 13)
.overlay(alignment: .leading) {
Rectangle().fill(Palette.border).frame(width: 3)
@@ -1107,7 +1125,7 @@ private struct DetailsBlock: View {
HStack(alignment: .firstTextBaseline, spacing: 4) {
Text(open ? "▾" : "▸")
.foregroundStyle(Palette.accent)
InlineView(inlines: summary, ctx: MDContext(size: ctx.size, color: Palette.accent, taskId: ctx.taskId, dir: ctx.dir))
InlineView(inlines: summary, ctx: MDContext(size: ctx.size, color: Palette.accent, taskId: ctx.taskId, project: ctx.project, dir: ctx.dir))
}
.contentShape(Rectangle())
}
+1 -1
View File
@@ -244,7 +244,7 @@ struct TaskTabBody: View {
case .changes:
ChangesView()
case .files:
FilesView()
FilesView(root: store.selectedTaskId.map(FileRoot.task))
case .page:
if let shown = store.selectedTaskId.flatMap({ store.shownPages[$0] }) {
PageTabs(shown: shown)
+19 -12
View File
@@ -17,11 +17,14 @@ struct ProjectView: View {
VStack(spacing: 0) {
if store.focusedProject() != nil {
HStack(spacing: 4) {
PaneTabButton(label: "Tasks", active: !overlays.projectSettings) {
overlays.projectSettings = false
PaneTabButton(label: "Tasks", active: overlays.projectTab == .tasks) {
overlays.projectTab = .tasks
}
PaneTabButton(label: "Settings", active: overlays.projectSettings) {
overlays.projectSettings = true
PaneTabButton(label: "Files", active: overlays.projectTab == .files) {
overlays.projectTab = .files
}
PaneTabButton(label: "Settings", active: overlays.projectTab == .settings) {
overlays.projectTab = .settings
}
Spacer(minLength: 0)
}
@@ -30,14 +33,18 @@ struct ProjectView: View {
Rectangle().fill(Palette.border).frame(height: 1)
}
}
ScrollView {
if let p = store.focusedProject() {
ProjectBlock(project: p, todo: todos[p.id], settings: overlays.projectSettings)
.padding(.horizontal, 16)
.padding(.top, 8)
.padding(.bottom, 16)
} else {
Muted(text: "No project selected.")
if let p = store.focusedProject(), overlays.projectTab == .files {
FilesView(root: .project(p.id))
} else {
ScrollView {
if let p = store.focusedProject() {
ProjectBlock(project: p, todo: todos[p.id], settings: overlays.projectTab == .settings)
.padding(.horizontal, 16)
.padding(.top, 8)
.padding(.bottom, 16)
} else {
Muted(text: "No project selected.")
}
}
}
}
+1 -1
View File
@@ -525,7 +525,7 @@ private struct Attachments: View {
case .image:
let src = (a.image?.isEmpty == false ? a.image : a.path) ?? ""
let caption = a.caption ?? ""
if let source = worktreeImageSource(src, taskId) {
if let source = worktreeImageSource(src, .task(taskId)) {
ByreImage(source: source, src: src)
} else if !src.isEmpty {
InlineView(inlines: [.link(href: src, [.text(caption.isEmpty ? src : caption)])], ctx: MDContext(taskId: taskId))
+1 -1
View File
@@ -83,7 +83,7 @@ final class Store {
private(set) var leadMore = false
var todoBusy: [String: String] = [:]
var pickedMembers: [String: String] = [:]
var filePlaces: [String: FilePlace] = [:]
var filePlaces: [FileRoot: FilePlace] = [:]
private(set) var drafts: Drafts?
let images: ImageDrafts
let imageCache = ImageCache()
+1 -1
View File
@@ -49,7 +49,7 @@ ditto "$build/TailscaleKit.framework" "$app/Contents/Frameworks/TailscaleKit.fra
cp "$mac/Info.plist" "$app/Contents/Info.plist"
plutil -insert ByreCommit -string "$(git -C "$root" rev-parse HEAD)" "$app/Contents/Info.plist"
plutil -insert CFBundleShortVersionString -string "$version" "$app/Contents/Info.plist"
plutil -insert CFBundleVersion -string "$(git -C "$root" log -1 --format=%ct)" "$app/Contents/Info.plist"
plutil -insert CFBundleVersion -string "$(date +%s)" "$app/Contents/Info.plist"
cp "$mac"/fonts/*.woff2 "$app/Contents/Resources/fonts/"
cp "$icns" "$app/Contents/Resources/byre.icns"
cp "$mac/byre.provisionprofile" "$app/Contents/embedded.provisionprofile"
+1 -1
View File
@@ -45,7 +45,7 @@ plutil -insert CFBundleIcons -xml "$(plutil -extract CFBundleIcons xml1 -o - "$b
plutil -insert ByreCommit -string "$(git -C "$root" rev-parse HEAD)" "$plist"
version=$(git -C "$root" describe --tags --abbrev=0)
plutil -insert CFBundleShortVersionString -string "${version#v}" "$plist"
plutil -insert CFBundleVersion -string "$(git -C "$root" log -1 --format=%ct)" "$plist"
plutil -insert CFBundleVersion -string "$(date +%s)" "$plist"
platform=$(xcrun --sdk "$sdk" --show-sdk-platform-version)
sdkbuild=$(xcrun --sdk "$sdk" --show-sdk-build-version)
plutil -insert CFBundleSupportedPlatforms -xml "<array><string>$(if [ "$sdk" = iphoneos ]; then echo iPhoneOS; else echo iPhoneSimulator; fi)</string></array>" "$plist"
+1 -1
View File
@@ -32,7 +32,7 @@ put() {
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"
put appcast.xml appcast.xml application/xml "no-cache"
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