diff --git a/README.md b/README.md index 77903da..2f7b19e 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,9 @@ A server and macOS app for running many coding agents (Claude Code, Codex and An ## Requirements -macOS, a [Tailscale](https://tailscale.com) tailnet with MagicDNS and HTTPS certificates turned on (byre and the app each join it as their own node, so no Mac needs the Tailscale app), and on the Mac that runs the server, `git`, the `claude` and `codex` CLIs (and for Gemini models the Antigravity CLI, `brew install --cask antigravity-cli`, signed in once by running `agy`), Node.js (agents' Playwright browser tools run through `npx`) and Google Chrome (`brew install git node --cask google-chrome`). Identity comes from Tailscale: byre serves only the tailnet's users, as its node identifies them. +macOS and a [Tailscale](https://tailscale.com) tailnet with MagicDNS and HTTPS certificates turned on. byre and the app each join it as their own node, so no Mac needs the Tailscale app. Identity comes from Tailscale: byre serves only the tailnet's users, as its node identifies them. + +The Mac that runs the server needs `git` and at least one agent CLI: Claude Code (`claude`), Codex (`codex`) or Antigravity (`agy`, for Gemini models; `brew install --cask antigravity-cli`, signed in once by running `agy`). Browser tools also need Node.js (agents' Playwright tools run through `npx`) and Google Chrome (`brew install git node --cask google-chrome`). ## Installing diff --git a/docs/operations.md b/docs/operations.md index acfa0f2..bf792dc 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -82,11 +82,9 @@ A model's name picks its vendor: `gpt-*` runs on Codex, `gemini-*` on Antigravit - A turn's final message is its summary. There is no report tool: models write a closing recap whatever they are told, so byre uses it rather than asking for a second one. - A review covers the whole task against the default branch, so a newer one makes older ones moot. Findings reach the task's agent together, once the whole review workflow finishes; an individual reviewer finishing is not a completed review. - An audit checks only the newest commit of the default branch, one at a time per project, so it doesn't run on every revision but the newest one is always audited eventually. byre never fetches for an audit, so a commit pushed outside byre is audited only once a byre merge has fetched it. -- A workflow definition is never changed once written: a save writes a new one, so freezing a workflow for a run is keeping the definition's id. - The lead's `show` is refused for a few seconds after the human moves the pane, and only a refusal arranges the "hold is over" signal, so the lead's prompt tells it to call `show` at once whenever it wants to show something, never holding back in expectation of a refusal. - A team task that comes up with some members failing to start isn't repaired; discard it and create it again. - There is no closed-task list in the UI; the human asks the lead. -- Nothing sets an inbox item's `superseded` state; it stays in the spec because old items hold it. ## git @@ -94,7 +92,6 @@ A model's name picks its vendor: `gpt-*` runs on Codex, `gemini-*` on Antigravit - `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. - The project's checkout must have the default branch checked out: a merge fast-forwards it. -- The Changes view builds its tree from a copy of the worktree's real index rather than `read-tree HEAD`, which keeps git's stat cache so unchanged files aren't rehashed. - byre's `grep` doesn't read the global `core.excludesFile`. ## Signing merges @@ -109,8 +106,6 @@ Task agents use the Macs the human has byre's app open on, through the app's Web - The app pings every 10s and reconnects after 25s with no pong. That catches a socket byre dropped while the Mac slept: the close never reached the Mac, and without something sent on it the app would wait on it indefinitely. - Messages are allowed up to 256 MiB each way, so file contents fit in one message; `client_read` refuses files over 64 MiB so the base64 reply stays well under it, past which byre would drop the connection. -- `client_edit` is a read then a write; nothing locks the file between the two, so a write in between is lost. -- An app too old to know the `upload` op can't be updated through the client tools; update it by hand. - `log show --info --last 10m --predicate 'subsystem == "run.fc.byre.mac"'` on a client shows `client request : ` 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, kept on disk, so `log show --start` reaches much further back for them. byre's log shows `client connected` and `client disconnected`. ## Screen and app control diff --git a/mac/Sources/Byre/Palette.swift b/mac/Sources/Byre/Palette.swift index 7fd53c3..0237d7c 100644 --- a/mac/Sources/Byre/Palette.swift +++ b/mac/Sources/Byre/Palette.swift @@ -4,7 +4,7 @@ import SwiftUI enum Palette { static let bg = dynamic(0xfbfbfa, 0x15171a) static let surface = dynamic(0xf2f3f5, 0x1c1f23) - static let raised = dynamic(0xffffff, 0x23272c) + static let raised = dynamic(0xf8f9fa, 0x23272c) static let border = dynamic(0xd6d9de, 0x353b43) static let text = dynamic(0x16181b, 0xeceff3) static let muted = dynamic(0x4a5260, 0xa9b1bc) diff --git a/mac/Sources/Byre/Workflow.swift b/mac/Sources/Byre/Workflow.swift index ab895a1..abd5a96 100644 --- a/mac/Sources/Byre/Workflow.swift +++ b/mac/Sources/Byre/Workflow.swift @@ -8,7 +8,7 @@ typealias StageAttempt = Components.Schemas.StageAttempt private let stateColors: [RunStage.StatePayload: NodeColors] = [ .waiting: NodeColors(fill: Palette.surface, stroke: Palette.border, text: Palette.muted), .running: NodeColors(fill: Palette.accentTint, stroke: Palette.accent, text: Palette.text), - .idle: NodeColors(fill: Palette.warnTint, stroke: Palette.warn, text: Palette.text), + .idle: NodeColors(fill: Palette.surface, stroke: Palette.border, text: Palette.muted), .finished: NodeColors(fill: Palette.okTint, stroke: Palette.ok, text: Palette.text), .merged: NodeColors(fill: Palette.okTint, stroke: Palette.ok, text: Palette.text), .cleanedUp: NodeColors(fill: Palette.okTint, stroke: Palette.ok, text: Palette.text), @@ -57,7 +57,7 @@ private func stageLabel(_ s: RunStage) -> String { private func stateColor(_ state: String) -> Color { switch state { case "running": Palette.accent - case "idle", "discarding": Palette.warn + case "idle", "discarding": Palette.muted case "finished", "merged": Palette.ok default: Palette.muted } @@ -239,7 +239,7 @@ private struct AttemptRow: View { .font(Fonts.font(11)) .foregroundStyle(stateColor(a.state.rawValue)) if a.discarding { - Text("discarding…").font(Fonts.font(11)).foregroundStyle(Palette.warn) + Text("discarding…").font(Fonts.font(11)).foregroundStyle(Palette.muted) } } if let from = a.forkedFrom {