diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 11eb8c9..f1367d9 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -6,7 +6,7 @@ body: id: version attributes: label: Version / commit - placeholder: v0.0.1 or a short SHA + placeholder: v0.1.0 or a short SHA validations: required: true - type: input diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4797a95..84cfafe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,12 +44,12 @@ Or `make ci`. CI enforces Rust checks on macOS/Linux and the UI build on Ubuntu. Push a version tag to publish binaries via GitHub Actions: ```sh -git tag v0.0.1 -git push origin v0.0.1 +git tag v0.1.0 +git push origin v0.1.0 ``` Assets are attached to the GitHub Release; users can run -`./scripts/install-from-release.sh v0.0.1`. +`./scripts/install-from-release.sh v0.1.0`. ## Commits and PRs diff --git a/Cargo.lock b/Cargo.lock index ab0f332..aa78162 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,7 +189,7 @@ dependencies = [ [[package]] name = "mb-adapters" -version = "0.0.1" +version = "0.1.0" dependencies = [ "mb-protocol", "notify", @@ -201,7 +201,7 @@ dependencies = [ [[package]] name = "mb-device" -version = "0.0.1" +version = "0.1.0" dependencies = [ "mb-protocol", "tracing", @@ -209,7 +209,7 @@ dependencies = [ [[package]] name = "mb-protocol" -version = "0.0.1" +version = "0.1.0" dependencies = [ "serde", "serde_json", @@ -223,7 +223,7 @@ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "microbridgectl" -version = "0.0.1" +version = "0.1.0" dependencies = [ "mb-protocol", "serde_json", @@ -232,7 +232,7 @@ dependencies = [ [[package]] name = "microbridged" -version = "0.0.1" +version = "0.1.0" dependencies = [ "mb-adapters", "mb-device", diff --git a/Cargo.toml b/Cargo.toml index 76b7fe4..d89854a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ members = [ ] [workspace.package] -version = "0.0.1" +version = "0.1.0" edition = "2021" license = "MIT OR Apache-2.0" repository = "https://github.com/DevVig/microbridge" diff --git a/Formula/microbridge.rb b/Formula/microbridge.rb index 03edecd..c0e06db 100644 --- a/Formula/microbridge.rb +++ b/Formula/microbridge.rb @@ -2,6 +2,8 @@ # frozen_string_literal: true # Homebrew formula for Microbridge — menu bar app + daemon (not CLI-only). +# Installs prebuilt GitHub Release assets (fast). Source builds: use HEAD or +# clone + ./scripts/install.sh. # # brew tap DevVig/microbridge https://github.com/DevVig/microbridge # brew install microbridge @@ -14,29 +16,46 @@ class Microbridge < Formula desc "Open-source control plane for the Codex Micro (menu bar + daemon)" homepage "https://github.com/DevVig/microbridge" - url "https://github.com/DevVig/microbridge/archive/refs/tags/v0.0.1.tar.gz" - sha256 "f171c275890add016045b0bbde54330f104b6d5db3a9d16c8d366cd5fcdde599" + version "0.1.0" license any_of: ["MIT", "Apache-2.0"] head "https://github.com/DevVig/microbridge.git", branch: "main" - depends_on "rust" => :build - depends_on "node" => :build depends_on :macos - def install - system "cargo", "build", "--release", "--locked", "-p", "microbridged", "-p", "microbridgectl" - bin.install "target/release/microbridged" - bin.install "target/release/microbridgectl" + on_macos do + on_arm do + url "https://github.com/DevVig/microbridge/releases/download/v#{version}/microbridge-v#{version}-aarch64-apple-darwin.tar.gz" + # sha256 filled by scripts/bump-formula.sh after each release + sha256 "0000000000000000000000000000000000000000000000000000000000000000" + + resource "ui" do + url "https://github.com/DevVig/microbridge/releases/download/v#{version}/microbridge-ui-v#{version}-aarch64-apple-darwin.tar.gz" + sha256 "0000000000000000000000000000000000000000000000000000000000000000" + end + end + on_intel do + url "https://github.com/DevVig/microbridge/releases/download/v#{version}/microbridge-v#{version}-x86_64-apple-darwin.tar.gz" + sha256 "0000000000000000000000000000000000000000000000000000000000000000" - cd "apps/microbridge-ui" do - system "npm", "ci" - system "npm", "run", "tauri", "build", "--", "--bundles", "app" + resource "ui" do + url "https://github.com/DevVig/microbridge/releases/download/v#{version}/microbridge-ui-v#{version}-x86_64-apple-darwin.tar.gz" + sha256 "0000000000000000000000000000000000000000000000000000000000000000" + end end + end - app = Dir["apps/microbridge-ui/src-tauri/target/release/bundle/macos/*.app"].first - odie "Microbridge.app missing after Tauri build" if app.nil? - prefix.install app + def install + # Release tarball layout: microbridge-vX.Y.Z-/{microbridged,microbridgectl,…} + bin.install Dir["**/microbridged"].first + bin.install Dir["**/microbridgectl"].first + + resource("ui").stage do + app = Dir["**/Microbridge.app"].first + odie "Microbridge.app missing from UI release archive" if app.nil? + prefix.install app + end + # INSTALL.md ships inside the daemon archive when present. doc.install "INSTALL.md" if File.exist?("INSTALL.md") end @@ -44,7 +63,6 @@ def post_install apps = Pathname.new(Dir.home)/"Applications" apps.mkpath dest = apps/"Microbridge.app" - # Only replace if missing or previously installed by this formula. marker = dest/".microbridge-brew" if dest.exist? && !marker.exist? ohai "Leaving existing ~/Applications/Microbridge.app in place (not brew-managed)" @@ -75,6 +93,9 @@ def caveats Open the app once (or add Login Items) so the menu bar icon appears: open ~/Applications/Microbridge.app + Hardware LEDs/keys need a connected Codex Micro (HID packing landing + after device captures). Until then the UI shows Simulator / Detected. + Upgrade: brew update && brew upgrade microbridge EOS end diff --git a/INSTALL.md b/INSTALL.md index 89c7309..79b6271 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -89,13 +89,16 @@ arch-specific menu bar app). On macOS, releases also include a ```sh ./scripts/install-from-release.sh # latest (prefers DMG on macOS) -./scripts/install-from-release.sh v0.0.1 +./scripts/install-from-release.sh v0.1.0 ``` Or open the DMG from the GitHub Release page and drag Microbridge into Applications, then install/start the daemon via Homebrew or the daemon archive. +**Note:** Homebrew installs **prebuilt** release binaries (not a from-source +Tauri build). The formula checksums are refreshed by CI after each `v*` tag. + ## Layout after install | Path | Purpose | diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000..9b61740 --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,35 @@ +# Privacy + +Microbridge is a **local-only** control plane. It does not phone home. + +## What stays on your machine + +| Data | Where | Why | +|---|---|---| +| Agent session journals | Read from paths like `~/.codex/sessions` and Claude project folders | Derive session titles/state for the menu bar and LED mapping | +| Config | `~/.microbridge/config.toml` | Key source, lighting, appearance | +| Daemon log | `~/.microbridge/daemon.log` (or Homebrew service logs) | Debug | +| Unix socket | `~/.microbridge/microbridged.sock` (mode `0600`) | Local IPC for UI + adapters | + +## What we do **not** do + +- No telemetry, analytics, or crash upload +- No update pings or cloud accounts +- No network client in the daemon (auditable in `Cargo.lock`) +- No uploading of session text or source code + +## Adapters + +First-party adapters watch **local** session stores. Community adapters must +follow the same rule (see [docs/adapters.md](docs/adapters.md)): talk only to +local runtimes; no network I/O. + +## Hardware + +USB presence probing (macOS `system_profiler`) and future HID traffic stay on +the host. Device captures used for reverse-engineering are documented in +[docs/device-hid.md](docs/device-hid.md) and are not sent anywhere by Microbridge. + +## Questions + +Security-sensitive reports: [SECURITY.md](SECURITY.md). diff --git a/README.md b/README.md index 1513e3f..bc81cbd 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Microbridge is a tiny local daemon that bridges AI coding agents — Codex CLI, Claude Code, Cursor, T3 Code, and anything else with an adapter — to the [Work Louder Codex Micro](https://worklouder.cc/). Per-key RGB mirrors live agent state; the keys drive agent actions (approve, reject, interrupt, switch focus). No vendor desktop app required. -> **Status: early alpha.** Protocol v0 with UI/control, in-process Codex/Claude watchers, mock device, `microbridgectl`, and a Tauri companion shell. Real HID packing waits on device captures — see [ROADMAP.md](ROADMAP.md). +> **Status: early public alpha (`v0.1.x`).** Menu bar UI, local daemon, in-process Codex/Claude watchers, and signed macOS packages are shipping. **Real Micro HID packing lands after hardware arrives (target 2026-07-22)** — until then LEDs/keys use Simulator / Detected. See [ROADMAP.md](ROADMAP.md) and [PRIVACY.md](PRIVACY.md). ## Screenshots diff --git a/SECURITY.md b/SECURITY.md index 9cb1b75..fbea7eb 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,8 +1,20 @@ # Security policy -Microbridge runs unprivileged, listens only on a local Unix socket in your -home directory, and performs no network I/O. Still, it sits between your -coding agents and a hardware input device, so we take reports seriously. +Microbridge runs unprivileged on your Mac, listens only on a **local Unix +socket** (mode `0600` under `~/.microbridge/`), and performs **no network I/O**. +It sits between your coding agents (session journals / adapter IPC) and — +eventually — a USB input device (Codex Micro). + +## Scope we care about + +- Unauthorized clients attaching to the daemon socket +- Privilege escalation via the menu bar app or daemon +- Unexpected network egress from daemon / first-party adapters +- Leaking session contents beyond the local machine +- Malicious adapter PRs that scrape private Electron internals or add idle + footprint / network I/O (declined on sight — see CONTRIBUTING) + +## Reporting **Report vulnerabilities privately** via GitHub's [private vulnerability reporting](../../security/advisories/new) on this @@ -10,3 +22,8 @@ repository. Please do not open public issues for security problems. You can expect an acknowledgment within a week. Supported version: the latest release (pre-1.0, there are no backports). + +## Out of scope (for now) + +- Full HID claim / exclusive USB ownership (landing with device captures) +- Guarantees about third-party community adapters you choose to run diff --git a/apps/microbridge-ui/package-lock.json b/apps/microbridge-ui/package-lock.json index 221d6a7..0d60e4f 100644 --- a/apps/microbridge-ui/package-lock.json +++ b/apps/microbridge-ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "microbridge-ui", - "version": "0.0.1", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "microbridge-ui", - "version": "0.0.1", + "version": "0.1.0", "dependencies": { "@tauri-apps/api": "^2", "@tauri-apps/plugin-shell": "^2", diff --git a/apps/microbridge-ui/package.json b/apps/microbridge-ui/package.json index a56b86b..62bfabf 100644 --- a/apps/microbridge-ui/package.json +++ b/apps/microbridge-ui/package.json @@ -1,7 +1,7 @@ { "name": "microbridge-ui", "private": true, - "version": "0.0.1", + "version": "0.1.0", "type": "module", "scripts": { "dev": "vite", diff --git a/apps/microbridge-ui/src-tauri/Cargo.toml b/apps/microbridge-ui/src-tauri/Cargo.toml index caf4ed8..20e79f3 100644 --- a/apps/microbridge-ui/src-tauri/Cargo.toml +++ b/apps/microbridge-ui/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "microbridge-ui" -version = "0.0.1" +version = "0.1.0" description = "Microbridge menu bar app (primary UI)" authors = ["Microbridge contributors"] edition = "2021" diff --git a/apps/microbridge-ui/src-tauri/tauri.conf.json b/apps/microbridge-ui/src-tauri/tauri.conf.json index 1608a58..8411380 100644 --- a/apps/microbridge-ui/src-tauri/tauri.conf.json +++ b/apps/microbridge-ui/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Microbridge", - "version": "0.0.1", + "version": "0.1.0", "identifier": "ai.microbridge.ui", "build": { "beforeDevCommand": "npm run dev", diff --git a/apps/microbridge-ui/src/lib/bus.ts b/apps/microbridge-ui/src/lib/bus.ts index 9374236..db7ffa3 100644 --- a/apps/microbridge-ui/src/lib/bus.ts +++ b/apps/microbridge-ui/src/lib/bus.ts @@ -1,6 +1,6 @@ import type { DaemonConfig, Snapshot } from "./types"; -/** Talks to microbridged via Tauri when available; demo snapshot in browser. */ +/** Talks to microbridged via Tauri when available; demo snapshot in browser only. */ const DEMO: Snapshot = { sessions: [ @@ -28,8 +28,8 @@ const DEMO: Snapshot = { ], focused_session_id: "s1", agent_key_session_ids: ["s1", "s2", "s3", null, null, null], - device_connected: true, - device_name: "mock", + device_connected: false, + device_name: "demo-browser", config: { key_source: "most_recent", pinned_session_ids: [], @@ -61,6 +61,10 @@ export async function fetchSnapshot(): Promise { return snap ?? DEMO; } +export function isDemoSnapshot(snapshot: Snapshot): boolean { + return snapshot.device_name === "demo-browser"; +} + export async function setConfig(config: DaemonConfig): Promise { const next = await invoke("set_config", { config }); return next ?? config; @@ -96,7 +100,3 @@ export async function subscribeSnapshot( return () => window.clearInterval(id); } } - -export function isDemoSnapshot(snapshot: Snapshot): boolean { - return snapshot.device_name === "mock" && snapshot.sessions.some((s) => s.id === "s1"); -} diff --git a/apps/microbridge-ui/src/surfaces/Popover.tsx b/apps/microbridge-ui/src/surfaces/Popover.tsx index 246bab1..d0dda19 100644 --- a/apps/microbridge-ui/src/surfaces/Popover.tsx +++ b/apps/microbridge-ui/src/surfaces/Popover.tsx @@ -74,17 +74,28 @@ export function Popover({ onQuit: () => void; }) { const t = dark ? DARK : LIGHT; - const simulator = snapshot.device_name === "mock"; + const demo = snapshot.device_name === "demo-browser"; + const simulator = snapshot.device_name === "mock" || demo; const detected = !snapshot.device_connected && snapshot.device_name.includes("usb"); - const connected = snapshot.device_connected || simulator || detected; + // Show the live UI shell in simulator/detected modes; only "Connected" + // means claimed HID (not yet shipped for production hardware). + const showLiveShell = + snapshot.device_connected || simulator || detected; const chipLabel = snapshot.device_connected ? "Connected" : detected ? "Detected" - : simulator - ? "Simulator" - : "Disconnected"; + : demo + ? "Demo" + : simulator + ? "Simulator" + : "Disconnected"; + const chipTone = snapshot.device_connected + ? "ok" + : detected || simulator + ? "warn" + : "off"; const focused = snapshot.sessions.find( (s) => s.id === snapshot.focused_session_id, ); @@ -140,23 +151,48 @@ export function Popover({ {chipLabel} - {connected ? ( + {(simulator || detected) && ( +

+ {demo + ? "Browser demo data — start microbridged + the Tauri app for a live bus." + : simulator + ? "No Micro claimed — LED frames are simulated until HID packing lands." + : "USB Micro seen, but HID is not claimed yet (packing pending)."} +

+ )} + + {showLiveShell ? ( <>
{focused ? ( @@ -285,8 +321,9 @@ export function Popover({ className="mt-1 max-w-[240px] text-[12px] leading-relaxed" style={{ color: t.textSecondary }} > - Plug in over USB-C or pair over Bluetooth. Your Agent Keys light - up the moment a thread goes live. + Plug in over USB-C. Real Agent Key LEDs/input land with HID + packing (device captures). Until then use Simulator mode or start + the daemon to watch sessions.

)} diff --git a/apps/microbridge-ui/src/surfaces/Settings.tsx b/apps/microbridge-ui/src/surfaces/Settings.tsx index 869e685..82d7e87 100644 --- a/apps/microbridge-ui/src/surfaces/Settings.tsx +++ b/apps/microbridge-ui/src/surfaces/Settings.tsx @@ -406,12 +406,12 @@ export function Settings({ { name: "Cursor", kind: "Community", - note: "scaffold — adapters/cursor", + note: "scaffold only — not production (adapters/cursor)", }, { name: "T3 Code", kind: "Community", - note: "scaffold — adapters/t3code", + note: "scaffold only — not production (adapters/t3code)", }, ].map((a) => (
  • std::io::Result<()> { let path = socket_path(); if let Some(parent) = path.parent() { std::fs::create_dir_all(parent)?; + // Keep the config dir private; the socket itself is locked to 0600 below. + let _ = std::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o700)); } let _ = std::fs::remove_file(&path); let listener = UnixListener::bind(&path)?; + std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))?; info!( socket = %path.display(), protocol = PROTOCOL_VERSION, diff --git a/crates/microbridged/src/state.rs b/crates/microbridged/src/state.rs index 341498c..d415136 100644 --- a/crates/microbridged/src/state.rs +++ b/crates/microbridged/src/state.rs @@ -156,8 +156,14 @@ impl DaemonState { }; let Some(tx) = self.adapter_txs.get(&owner) else { // In-process adapters: owner id 0 is reserved for local handlers. + // Codex/Claude control-plane mapping lands in a follow-up (#24); + // until then actions are acknowledged but not forwarded to a CLI. if owner == 0 { - info!(session_id, ?action, "in-process action"); + info!( + session_id, + ?action, + "in-process action (no runtime bridge yet — use microbridgectl / await #24)" + ); return; } warn!(session_id, ?action, owner, "adapter connection gone"); diff --git a/docs/architecture.md b/docs/architecture.md index 2ea0500..6b1dedb 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -21,7 +21,7 @@ line; regressions are release blockers. | Metric | Budget | How | |---|---|---| -| Idle CPU | 0.0% (no wakeups between events) | no polling loops, no timers; blocking reads on socket + HID; FSEvents/inotify for session-file watching | +| Idle CPU | 0.0% (no wakeups between events) | no polling loops, no timers; blocking reads on socket + HID; FSEvents/inotify for session-file watching. **Exception:** macOS frontmost watcher polls `NSWorkspace` every 400ms until a notification observer replaces it ([`frontmost.rs`](../crates/microbridged/src/frontmost.rs)). | | Idle RSS (daemon) | < 15 MB, target single-digit | single static Rust binary, no runtime | | Network | **zero, by design** | no HTTP client linked; no telemetry, no update pings; auditable via `Cargo.lock` | | Device traffic | bytes per state *transition* | LED frames written only when resolved state changes; a 32–64 byte HID report each | @@ -41,7 +41,7 @@ The deck shows exactly one session at a time: deck (and the approve/reject keys) until resolved. 2. **Pinned beats auto.** The user can pin a session from Settings or a device key; pinning disables auto-follow until unpinned. -3. **Auto-follow (M3).** Otherwise the frontmost app's active session owns the +3. **Auto-follow.** Otherwise the frontmost app's active session owns the deck — driven by `NSWorkspace` frontmost-app notifications (event-driven, not polled). 4. **Fallback.** With no other signal, the most recently updated session wins. diff --git a/docs/governance.md b/docs/governance.md index 86de490..aa61231 100644 --- a/docs/governance.md +++ b/docs/governance.md @@ -39,8 +39,8 @@ on merge, auto-merge enabled. ## macOS install + auto-update (Homebrew) **This is the supported consumer path** — not cloning the git repo. -`brew install microbridge` builds/installs the **menu bar app** and the -daemon (not CLI-only). +`brew install microbridge` installs **prebuilt** release binaries for the +**menu bar app** and the daemon (not CLI-only; not a from-source Tauri build). ```sh brew tap DevVig/microbridge https://github.com/DevVig/microbridge diff --git a/docs/project-tracking.md b/docs/project-tracking.md new file mode 100644 index 0000000..a147edd --- /dev/null +++ b/docs/project-tracking.md @@ -0,0 +1,17 @@ +# Project tracking + +## Linear + +Intended team/project: **VIGDEV** / **Microbridge Production Launch** +(Phase A → 2026-07-21, Phase B from 2026-07-22). + +Linear MCP/API was not available in the agent environment that created this +release train. Until Linear is wired, use GitHub milestones on this repo: + +| Milestone | Scope | +|---|---| +| [Phase A](https://github.com/DevVig/microbridge/milestone/1) | Software productionization (public, brew, signed DMG, OSS hardening) | +| [Phase B](https://github.com/DevVig/microbridge/milestone/2) | Device HID — issues labeled `Blocked Dependency` until Micro arrives | + +Mirror issues into Linear when the MCP/API key is available; keep the same +titles and phase split. diff --git a/scripts/bump-formula.sh b/scripts/bump-formula.sh index ca67d13..964406c 100755 --- a/scripts/bump-formula.sh +++ b/scripts/bump-formula.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash -# Bump Formula/microbridge.rb to a new tag and GitHub archive sha256. -# Usage: ./scripts/bump-formula.sh v0.0.2 +# Bump Formula/microbridge.rb to a new release tag and fill sha256 for the +# prebuilt daemon + UI archives (same URLs Homebrew fetches). +# Usage: ./scripts/bump-formula.sh v0.1.0 set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" @@ -8,22 +9,89 @@ TAG="${1:?usage: $0 vX.Y.Z}" TAG="${TAG#v}" FULL="v${TAG}" FORMULA="$ROOT/Formula/microbridge.rb" -URL="https://github.com/DevVig/microbridge/archive/refs/tags/${FULL}.tar.gz" - -echo "==> Fetching ${URL}" -TMP="$(mktemp)" -if command -v gh >/dev/null; then - gh api "repos/DevVig/microbridge/tarball/${FULL}" >"$TMP" -else - curl -fsSL "$URL" -o "$TMP" -fi -SHA="$(shasum -a 256 "$TMP" | awk '{print $1}')" -rm -f "$TMP" - -echo "==> Updating formula → ${FULL} sha256=${SHA}" -# Portable in-place edit -perl -0pi -e "s#url \"https://github.com/DevVig/microbridge/archive/refs/tags/v[^\"]+\"#url \"${URL}\"#" "$FORMULA" -perl -0pi -e "s#sha256 \"[a-f0-9]+\"#sha256 \"${SHA}\"#" "$FORMULA" - -echo "Updated $FORMULA" -grep -E 'url |sha256 ' "$FORMULA" +REPO="${MICROBRIDGE_REPO:-DevVig/microbridge}" +BASE="https://github.com/${REPO}/releases/download/${FULL}" + +sha_of() { + local url="$1" + local tmp + tmp="$(mktemp)" + echo "==> Fetching ${url}" >&2 + # Prefer the public release asset URL (identical bytes to brew's download). + # Fall back to gh for private repos during the bump job. + if curl -fsSL -o "$tmp" "$url" 2>/dev/null; then + : + elif command -v gh >/dev/null; then + local name + name="$(basename "$url")" + gh release download "$FULL" --repo "$REPO" --pattern "$name" --dir "$(dirname "$tmp")" --clobber + mv "$(dirname "$tmp")/$name" "$tmp" + else + echo "error: cannot download $url" >&2 + exit 1 + fi + shasum -a 256 "$tmp" | awk '{print $1}' + rm -f "$tmp" +} + +DAEMON_ARM_URL="${BASE}/microbridge-${FULL}-aarch64-apple-darwin.tar.gz" +DAEMON_INTEL_URL="${BASE}/microbridge-${FULL}-x86_64-apple-darwin.tar.gz" +UI_ARM_URL="${BASE}/microbridge-ui-${FULL}-aarch64-apple-darwin.tar.gz" +UI_INTEL_URL="${BASE}/microbridge-ui-${FULL}-x86_64-apple-darwin.tar.gz" + +DAEMON_ARM_SHA="$(sha_of "$DAEMON_ARM_URL")" +DAEMON_INTEL_SHA="$(sha_of "$DAEMON_INTEL_URL")" +UI_ARM_SHA="$(sha_of "$UI_ARM_URL")" +UI_INTEL_SHA="$(sha_of "$UI_INTEL_URL")" + +echo "==> Rewriting ${FORMULA} → ${FULL}" + +python3 - "$FORMULA" "$TAG" \ + "$DAEMON_ARM_URL" "$DAEMON_ARM_SHA" \ + "$DAEMON_INTEL_URL" "$DAEMON_INTEL_SHA" \ + "$UI_ARM_URL" "$UI_ARM_SHA" \ + "$UI_INTEL_URL" "$UI_INTEL_SHA" <<'PY' +import pathlib, re, sys + +path = pathlib.Path(sys.argv[1]) +version = sys.argv[2] +daemon_arm_url, daemon_arm_sha = sys.argv[3], sys.argv[4] +daemon_intel_url, daemon_intel_sha = sys.argv[5], sys.argv[6] +ui_arm_url, ui_arm_sha = sys.argv[7], sys.argv[8] +ui_intel_url, ui_intel_sha = sys.argv[9], sys.argv[10] + +text = path.read_text() +text = re.sub(r'version "[^"]+"', f'version "{version}"', text, count=1) + +def replace_block(src: str, arch_marker: str, daemon_url: str, daemon_sha: str, ui_url: str, ui_sha: str) -> str: + # Replace the on_arm / on_intel block URLs + sha256 pairs in order. + pattern = rf'(on_{arch_marker} do\n)(.*?)(\n end\n)' + m = re.search(pattern, src, flags=re.S) + if not m: + raise SystemExit(f"missing on_{arch_marker} block") + body = m.group(2) + # First url/sha256 = daemon; next url/sha256 inside resource = ui + urls = list(re.finditer(r'url "[^"]+"', body)) + shas = list(re.finditer(r'sha256 "[^"]+"', body)) + if len(urls) < 2 or len(shas) < 2: + raise SystemExit(f"expected 2 url/sha256 pairs in on_{arch_marker}, found {len(urls)}/{len(shas)}") + # Rebuild body with substitutions from the end so offsets stay valid. + replacements = [ + (urls[0], f'url "{daemon_url}"'), + (shas[0], f'sha256 "{daemon_sha}"'), + (urls[1], f'url "{ui_url}"'), + (shas[1], f'sha256 "{ui_sha}"'), + ] + replacements.sort(key=lambda x: x[0].start(), reverse=True) + for match, new in replacements: + body = body[: match.start()] + new + body[match.end() :] + return src[: m.start(2)] + body + src[m.end(2) :] + +text = replace_block(text, "arm", daemon_arm_url, daemon_arm_sha, ui_arm_url, ui_arm_sha) +text = replace_block(text, "intel", daemon_intel_url, daemon_intel_sha, ui_intel_url, ui_intel_sha) +path.write_text(text) +print(path) +PY + +echo "Updated formula:" +grep -E 'version |url |sha256 ' "$FORMULA" | head -40 diff --git a/scripts/install-from-release.sh b/scripts/install-from-release.sh index 2ee3a5f..e11af22 100755 --- a/scripts/install-from-release.sh +++ b/scripts/install-from-release.sh @@ -25,7 +25,11 @@ case "$OS-$ARCH" in Darwin-arm64) TARGET="aarch64-apple-darwin" ;; Darwin-x86_64) TARGET="x86_64-apple-darwin" ;; Linux-x86_64) TARGET="x86_64-unknown-linux-gnu" ;; - Linux-aarch64) TARGET="aarch64-unknown-linux-gnu" ;; + Linux-aarch64) + echo "error: Linux aarch64 release binaries are not published yet" >&2 + echo "hint: build from source with ./scripts/install.sh --no-ui" >&2 + exit 1 + ;; *) echo "unsupported platform: $OS $ARCH" >&2 exit 1