Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Default owners for review routing (optional reviews; PRs still required on main).
* @DevVig

/crates/ @DevVig
/apps/microbridge-ui/ @DevVig
/adapters/ @DevVig
/Formula/ @DevVig
/.github/ @DevVig
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ jobs:
cache-dependency-path: apps/microbridge-ui/package-lock.json
- run: npm ci
- run: npm run build

# Keep required-check names stable for the main branch ruleset.
# Job names above are what GitHub shows as status contexts.
34 changes: 34 additions & 0 deletions .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: PR title

on:
pull_request_target:
types: [opened, edited, synchronize, reopened]

permissions:
pull-requests: read

jobs:
lint:
name: conventional title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
feat
fix
docs
style
refactor
perf
test
build
ci
chore
adapter
revert
requireScope: false
subjectPattern: .+
wip: true
48 changes: 42 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:

permissions:
contents: write
pull-requests: write

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Scope pull-request write permission to bump-formula.

The workflow-level permission exposes PR write access to build and publish jobs that do not need it. A compromised build step could otherwise create or modify pull requests.

🧰 Tools
🪛 zizmor (1.26.1)

[error] 10-10: overly broad permissions (excessive-permissions): pull-requests: write is overly broad at the workflow level

(excessive-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 10, Restrict pull-request write access
from the workflow-wide permissions to the bump-formula job only. Remove the
global pull-requests write permission and add it under the bump-formula job’s
permissions, preserving all other job permissions and workflow behavior.

Source: Linters/SAST tools


jobs:
build:
Expand All @@ -18,7 +19,7 @@ jobs:
include:
- os: macos-latest
target: aarch64-apple-darwin
- os: macos-13
- os: macos-latest
target: x86_64-apple-darwin
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
Expand All @@ -29,8 +30,7 @@ jobs:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
- name: Build
run: |
cargo build --release -p microbridged -p microbridgectl --target ${{ matrix.target }}
run: cargo build --release -p microbridged -p microbridgectl --target ${{ matrix.target }}
- name: Package
run: |
STAGE=microbridge-${{ github.ref_name }}-${{ matrix.target }}
Expand Down Expand Up @@ -64,10 +64,46 @@ jobs:
generate_release_notes: true
files: release-assets/*
body: |
## Install
## Install (macOS)

```sh
./scripts/install-from-release.sh ${{ github.ref_name }}
brew tap DevVig/microbridge https://github.com/DevVig/microbridge
brew install microbridge
brew services start microbridge
```

Or from source: see [INSTALL.md](INSTALL.md).
Upgrade later: `brew update && brew upgrade microbridge`

Binary archive: `./scripts/install-from-release.sh ${{ github.ref_name }}`

Full guide: [INSTALL.md](INSTALL.md).

bump-formula:
name: bump Homebrew formula
needs: publish
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
ref: main
Comment on lines +86 to +88

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Disable persisted checkout credentials.

actions/checkout persists the workflow token in local Git configuration by default, while the pull-request action already receives the token explicitly. Set persist-credentials: false to reduce exposure to commands executed in this job.

🔐 Proposed fix
       - uses: actions/checkout@v5
         with:
           ref: main
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: actions/checkout@v5
with:
ref: main
- uses: actions/checkout@v5
with:
ref: main
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 86-88: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 86 - 88, Update the
actions/checkout@v5 step in the release workflow to set persist-credentials to
false alongside the existing ref: main configuration, while leaving the explicit
pull-request token handling unchanged.

Source: Linters/SAST tools

- name: Bump Formula url + sha256
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
chmod +x scripts/bump-formula.sh
./scripts/bump-formula.sh "${{ github.ref_name }}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not interpolate the release tag directly into shell code.

${{ github.ref_name }} is expanded before Bash parses the script. Pass it through env, invoke the environment variable, and validate the tag in scripts/bump-formula.sh before using it in URLs or embedded Perl code.

🛡️ Proposed fix
         env:
           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          RELEASE_TAG: ${{ github.ref_name }}
         run: |
           chmod +x scripts/bump-formula.sh
-          ./scripts/bump-formula.sh "${{ github.ref_name }}"
+          ./scripts/bump-formula.sh "$RELEASE_TAG"
🧰 Tools
🪛 zizmor (1.26.1)

[error] 94-94: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 94, Update the release workflow
invocation of bump-formula.sh to pass github.ref_name through an environment
variable instead of interpolating it into shell code. In
scripts/bump-formula.sh, validate the received tag before using it in URLs or
embedded Perl code, and reject invalid values.

Source: Linters/SAST tools

- name: Open PR
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "chore(brew): bump formula to ${{ github.ref_name }}"
title: "chore(brew): bump formula to ${{ github.ref_name }}"
body: |
Auto-bump `Formula/microbridge.rb` after release `${{ github.ref_name }}`.

After merge, users get the new version via:
```sh
brew update && brew upgrade microbridge
```
branch: chore/brew-${{ github.ref_name }}
delete-branch: true
10 changes: 8 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,18 @@ Assets are attached to the GitHub Release; users can run

## Commits and PRs

- Conventional commits (`feat:`, `fix:`, `docs:`, `adapter:` for adapter
work).
`main` is protected — **no direct pushes**. Open a PR; squash-merge only.

- PR titles must be Conventional Commits (`feat:`, `fix:`, `docs:`,
`adapter:`, …) — enforced by CI (`PR title` workflow).
- Required checks: `rust (ubuntu-latest)`, `rust (macos-latest)`, `ui`.
- Resolve review threads before merge.
- One logical change per PR; small PRs merge fast here.
- PRs must say how they were tested — "ran the reference adapter against the
daemon and watched the frames" is a fine answer at this stage.

See [docs/governance.md](docs/governance.md).

## Reporting security issues

Privately, please — see [SECURITY.md](SECURITY.md).
21 changes: 16 additions & 5 deletions Formula/microbridge.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
# Homebrew formula (build-from-source until a tap/bottle is published).
# typed: false
# frozen_string_literal: true

# Homebrew formula for Microbridge (source build — fast enough for alpha).
#
# brew install --build-from-source ./Formula/microbridge.rb
# brew tap DevVig/microbridge https://github.com/DevVig/microbridge
# brew install microbridge
# brew services start microbridge
#
# Upgrade (auto-update path):
# brew update && brew upgrade microbridge
# brew autoupdate start --upgrade --cleanup # optional background updates
#
class Microbridge < Formula
desc "Open-source control plane for the Codex Micro"
homepage "https://github.com/DevVig/microbridge"
url "https://github.com/DevVig/microbridge/archive/refs/tags/v0.0.1.tar.gz"
sha256 "f171c275890add016045b0bbde54330f104b6d5db3a9d16c8d366cd5fcdde599"
license any_of: ["MIT", "Apache-2.0"]
head "https://github.com/DevVig/microbridge.git", branch: "main"

Expand All @@ -28,9 +38,10 @@ def install

def caveats
<<~EOS
Config and socket live in ~/.microbridge/
Check the bus with: microbridgectl status
Full install notes: #{doc}/INSTALL.md (or INSTALL.md in the repo)
Config and socket: ~/.microbridge/
Status: microbridgectl status
Upgrade: brew update && brew upgrade microbridge
Background updates: brew autoupdate start --upgrade --cleanup
EOS
end

Expand Down
135 changes: 73 additions & 62 deletions INSTALL.md
Original file line number Diff line number Diff line change
@@ -1,111 +1,122 @@
# Installing Microbridge

Microbridge is a local daemon plus an optional companion UI. There is **no
network** and **no cloud account** — install puts binaries on your machine and
(on macOS) a per-user launchd agent.
cloud account** — install puts binaries on your machine and runs a user-level
service.

## Requirements

| Piece | Need |
|---|---|
| Daemon | Rust stable (`rustup`), macOS 13+ or Linux |
| Companion UI (optional) | Node ≥ 20; full `.app` also needs Xcode CLT |
| Hardware LEDs | Codex Micro over USB (HID packing still landing — mock works without hardware) |
## Recommended on macOS: Homebrew (with updates)

## Quick install (macOS, from source)
This is the easy path. You do **not** need to clone the repo. Homebrew owns
install, upgrades, and the launchd service.

```sh
git clone https://github.com/DevVig/microbridge.git
cd microbridge
./scripts/install.sh
brew tap DevVig/microbridge https://github.com/DevVig/microbridge
brew install microbridge
brew services start microbridge
microbridgectl status
```

This will:

1. `cargo build --release` for `microbridged` and `microbridgectl`
2. Install them to `~/.local/bin` (override with `MICROBRIDGE_BIN=…`)
3. Ensure `~/.local/bin` is on your PATH (prints a hint if not)
4. Install and start the launchd agent `ai.microbridge.daemon`
5. Write config defaults under `~/.microbridge/`

Verify:
### Updates

```sh
microbridgectl status
# or:
tail -f ~/.microbridge/daemon.log
brew update && brew upgrade microbridge
brew services restart microbridge
```

### Optional companion UI
Optional **background** upgrades (Homebrew’s autoupdate):

```sh
./scripts/install.sh --with-ui
# web preview during development:
cd apps/microbridge-ui && npm install && npm run dev
brew autoupdate start --upgrade --cleanup --immediate
# later: brew autoupdate status / brew autoupdate stop
```

`--with-ui` installs frontend deps and, when Tauri/Xcode tooling is available,
attempts `npm run tauri build`. You can always run the Vite UI against a live
daemon without bundling an `.app`.
Private tap note: if the GitHub repo is private, authenticate once
(`gh auth login` or a `HOMEBREW_GITHUB_API_TOKEN`) so `brew` can fetch the
tarball.
Comment on lines +33 to +35

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail

printf '\n## INSTALL.md excerpt\n'
nl -ba INSTALL.md | sed -n '1,160p'

printf '\n## Search for Homebrew/GitHub token references\n'
rg -n "HOMEBREW_GITHUB_API_TOKEN|repo-scoped|fine-grained|Contents: read|gh auth login|private repo|tarball|brew" -S .

Repository: DevVig/microbridge

Length of output: 219


🌐 Web query:

GitHub fine-grained personal access token repository archives Contents: read HOMEBREW_GITHUB_API_TOKEN Homebrew private tap tarball

💡 Result:

GitHub fine-grained personal access tokens (PATs) can be used to download private repository archives (tarballs) via the GitHub REST API, but they require specific configurations and permissions. Permissions: To download a repository tarball, a fine-grained PAT requires "Contents: read" repository permissions [1][2][3]. API Endpoint: While classic PATs could often access archives directly via the github.com web URL, fine-grained PATs are generally required to use the official GitHub REST API endpoint: GET /repos/{owner}/{repo}/tarball/{ref} [4][5]. Authentication Scheme: A known quirk exists where some tools using hard-coded "Bearer" authentication schemes with fine-grained PATs may fail with a 401 Unauthorized error when accessing the tarball endpoint [3]. Using the "token" authentication scheme (e.g., Authorization: token ) instead of "Bearer" is often necessary to successfully authorize the request [3]. Homebrew Context: Homebrew supports using fine-grained PATs via the HOMEBREW_GITHUB_API_TOKEN environment variable. Historically, Homebrew's internal regex for validating tokens did not recognize the github_pat_ prefix used by fine-grained PATs, but this has been updated [6][7]. Additionally, recent changes in Homebrew (as of mid-2026) have improved how the application handles environment variable scrubbing during formula/cask evaluation, ensuring that HOMEBREW_GITHUB_API_TOKEN remains available to private taps so they can resolve authenticated assets [8][9]. Users experiencing issues with private taps downloading artifacts should ensure their Homebrew installation is up to date, as these updates were specifically designed to preserve token availability during the evaluation process [9].

Citations:


🏁 Script executed:

set -euo pipefail

printf '\n## INSTALL.md lines 1-170\n'
nl -ba INSTALL.md | sed -n '1,170p'

printf '\n## Token-related references in repository\n'
rg -n "HOMEBREW_GITHUB_API_TOKEN|fine-grained|repo-scoped|Contents: read|private repo|tarball" -S .

Repository: DevVig/microbridge

Length of output: 223


🏁 Script executed:

set -euo pipefail

printf '\n## INSTALL.md excerpt\n'
sed -n '1,170p' INSTALL.md | cat -n

printf '\n## Token-related references in repository\n'
rg -n "HOMEBREW_GITHUB_API_TOKEN|fine-grained|repo-scoped|Contents: read|private repo|tarball|gh auth login" -S .

Repository: DevVig/microbridge

Length of output: 4601


Prefer a fine-grained PAT here
Use a fine-grained token limited to this repository with Contents: read instead of a classic repo PAT; HOMEBREW_GITHUB_API_TOKEN can be used for private tap tarball downloads. Also update the troubleshooting note at lines 121-122.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@INSTALL.md` around lines 33 - 35, Update the private tap authentication
guidance in INSTALL.md to recommend a fine-grained personal access token scoped
to this repository with Contents: read, rather than a classic repo-scoped PAT,
while retaining HOMEBREW_GITHUB_API_TOKEN for private tap tarball downloads.
Apply the same corrected guidance to the troubleshooting note near the later
authentication instructions.


## Linux (from source)
Uninstall:

```sh
./scripts/install.sh --no-launchd
# run in the foreground, or add your own systemd --user unit:
microbridged
brew services stop microbridge
brew uninstall microbridge
# optional: brew untap DevVig/microbridge
```

A sample user unit is in [`scripts/microbridge.service`](scripts/microbridge.service).
Governance / why this path: [docs/governance.md](docs/governance.md).

## Homebrew (skeleton)
---

## Requirements

| Piece | Need |
|---|---|
| Daemon (Homebrew) | Homebrew; Rust pulled in as a build dependency |
| Daemon (from source) | Rust stable (`rustup`), macOS 13+ or Linux |
| Companion UI (optional) | Node ≥ 20; full `.app` also needs Xcode CLT |
| Hardware LEDs | Codex Micro over USB (HID packing still landing — mock works without hardware) |

## From source (developers)

```sh
brew install --build-from-source ./Formula/microbridge.rb
brew services start microbridge # when using the formula's service block
git clone https://github.com/DevVig/microbridge.git
cd microbridge
./scripts/install.sh # macOS: binaries + launchd
# ./scripts/install.sh --with-ui
# ./scripts/install-linux-systemd.sh
microbridgectl status
```

A published tap/bottle is not available yet — use `./scripts/install.sh` for
day-to-day installs.

## Install from a GitHub Release
Uninstall: `./scripts/uninstall.sh` (add `--purge` to remove `~/.microbridge`).

When a `v*` tag is pushed, CI attaches platform archives. Then:
### Optional companion UI

```sh
./scripts/install-from-release.sh v0.0.1
# or latest:
./scripts/install-from-release.sh
./scripts/install.sh --with-ui
# or during development:
cd apps/microbridge-ui && npm install && npm run dev
```

## Uninstall
## Linux

```sh
./scripts/uninstall.sh
./scripts/install-linux-systemd.sh
# or:
./scripts/install.sh --no-launchd && microbridged
```
Comment on lines +82 to 85

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the installed binary path in the Linux foreground example.

The layout documents the source binary at ~/.local/bin/microbridged, but a child install script cannot modify the parent shell’s PATH. The chained microbridged command can therefore fail with “command not found.”

🛠️ Proposed fix
-./scripts/install.sh --no-launchd && microbridged
+./scripts/install.sh --no-launchd
+"$HOME/.local/bin/microbridged"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
./scripts/install-linux-systemd.sh
# or:
./scripts/install.sh --no-launchd && microbridged
```
./scripts/install-linux-systemd.sh
# or:
./scripts/install.sh --no-launchd
"$HOME/.local/bin/microbridged"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@INSTALL.md` around lines 82 - 85, Update the Linux foreground installation
example in INSTALL.md to invoke microbridged using its documented installed path
under ~/.local/bin, rather than relying on the parent shell’s PATH after running
install.sh.


Removes the launchd agent, binaries from `MICROBRIDGE_BIN` / `~/.local/bin`,
and optionally (`--purge`) `~/.microbridge/` (config, socket, logs).
Sample unit: [`scripts/microbridge.service`](scripts/microbridge.service).

## Install from a GitHub Release (binaries)

When a `v*` tag is published, CI attaches platform archives:

```sh
./scripts/install-from-release.sh # latest
./scripts/install-from-release.sh v0.0.1
```

## Layout after install

| Path | Purpose |
|---|---|
| `~/.local/bin/microbridged` | Daemon |
| `~/.local/bin/microbridgectl` | CLI |
| `~/Library/LaunchAgents/ai.microbridge.daemon.plist` | macOS autostart |
| `$(brew --prefix)/bin/microbridged` | Daemon (Homebrew) |
| `~/.local/bin/microbridged` | Daemon (source install script) |
Comment on lines +102 to +103
| `~/.microbridge/microbridged.sock` | Local NDJSON socket |
| `~/.microbridge/config.toml` | Key source, lighting, appearance |
| `~/.microbridge/daemon.log` | launchd stdout/stderr |
| `~/.microbridge/daemon.log` | launchd / service logs |
Comment on lines 98 to +106

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Document the Homebrew log location separately.

The formula writes service logs to var/log/microbridge.log, not ~/.microbridge/daemon.log. Qualify the latter as source-install-only and add the Homebrew-prefix path so troubleshooting points users to a real file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@INSTALL.md` around lines 98 - 106, Update the “Layout after install” table in
INSTALL.md to distinguish service log locations: qualify
~/.microbridge/daemon.log as source-install-only and add the Homebrew-prefix
var/log/microbridge.log path for Homebrew installations.


## Troubleshooting

**`microbridgectl: connect …`** — daemon not running. On macOS:
`launchctl kickstart -k gui/$(id -u)/ai.microbridge.daemon`.
**`microbridgectl: connect …`** — daemon not running.

```sh
brew services restart microbridge
# or:
launchctl kickstart -k "gui/$(id -u)/ai.microbridge.daemon"
```

**LEDs stay dark** — HID packing is still best-effort; ChatGPT desktop may
also own the device. Pause that app or use Settings → Pause LEDs while testing
the mock path (`microbridgectl status` still works).
also own the device.

**PATH** — add `export PATH="$HOME/.local/bin:$PATH"` to your shell rc if
`microbridgectl` is not found.
**Homebrew can’t fetch (private repo)** — `gh auth login`, or set
`HOMEBREW_GITHUB_API_TOKEN` to a PAT with `repo` scope.
Loading
Loading