MacOn › Documentation
MacOn Documentation
Everything for MacOn: the iPhone & iPad companion, the Mac app, and the macon CLI.
Overview
MacOn puts your Mac in your pocket. Pair an iPhone or iPad and you can see and drive its screen, wake and unlock it even when it is asleep or locked, automate it with a deck and flows, code on it, and hand tasks to an AI that operates it, from anywhere. The same Mac also runs as a local iOS CI runner: it watches a Bitbucket or GitHub repository, runs the pipeline in your macon.yml, and ships to TestFlight, all on hardware you own.
It comes in three parts that share one engine:
- The iPhone & iPad companion, streams and controls the Mac's screen, runs Mac apps phone-sized (CompactOS), automates with a deck and flows, gives you a code workspace with real terminals, talks to AI that can drive the Mac, monitors builds, and can wake and unlock the Mac.
- The Mac app, a desktop control panel for screen sharing, flows, power & access, and, when you want CI, pipelines, runners and secrets.
- The
maconCLI, the same engine, headless, installable via Homebrew. Ideal for scripts, servers, and always-on machines.
Anything you set up in the app can be exported and run by the CLI, and vice-versa. The Mac app and companion share one soft 3D "clay world" look you can re-skin across seventeen themes.
Install
Companion (iPhone & iPad)
Join the free beta on TestFlight: testflight.apple.com/join/PgHKv8WC. Requires iOS or iPadOS 17 or later. On first launch, pair it with your Mac using a one-time code (see Pairing & devices).
Mac app
Download the latest build here. Requires macOS 14 or later. The app hosts the companion server the phone connects to, and runs your CI.
CLI (Homebrew)
Optional, for headless and scripted use, the same engine as the app:
First run: macon init / macon doctor
Before watching anything, confirm the machine has what an iOS build needs. macon init checks everything and installs the Homebrew-managed pieces automatically; macon doctor is the report-only health check.
It verifies Homebrew, Xcode + Command Line Tools, git, Ruby/Bundler, fastlane, SwiftLint, gitleaks, a JDK, cloudflared, simulator runtimes, disk space (warns under 20 GB free, DerivedData eats it), and the TCC permissions (Screen Recording, Accessibility) the current process would need to serve screen/control headless. Tools available via Homebrew are installed for you; Xcode and simulators print the exact command to run.
CLI overview
| Command | What it does |
|---|---|
macon doctor | init [--check] | Check toolchain, permissions, cloudflared, disk; init installs what's missing. |
macon sims … | List / install simulator runtimes & devices. |
macon lint [path] | Validate and summarize a macon.yml. |
macon run … | Run one workflow once, then exit. |
macon watch … | Watch a repo and build new commits (until Ctrl-C). |
macon status | logs | trigger | cancel | Remote-control a running runner from the terminal. |
macon pair --host H --code C | Pair this CLI with a remote runner (local pairs itself). |
macon metrics | Print the runner's Prometheus metrics. |
macon config <init|validate> | Scaffold / check the portable config. |
macon validate [path] | Validate a macon.yml or an export file (by extension). |
macon service … / install-service | Run a watch as a background launchd service. |
macon companion … | Manage companion-app (iPhone/iPad) pairings. |
macon pipelines [file] | List pipelines in an app export file. |
macon completions <zsh|bash> | Print shell completions. |
Simulators
Inspect and manage the simulators your test matrix targets, for iOS, watchOS, tvOS, and visionOS (macOS builds run natively, no simulator).
Use macon sims to find the exact device and os values for a test matrix.
Run once
Run a single workflow in a checkout and exit with its status code, perfect for a git hook, cron, or another CI.
Watch
Continuously build new commits (or pull requests). Runs until you stop it.
Options
| Flag | Meaning |
|---|---|
--provider bitbucket|github | Git host (default bitbucket). GitHub: workspace = owner/org. |
--branch B / --prs | Watch a branch, or open PRs (--pr-target to filter). |
--webhook [--port N] | Push mode, build the instant the host calls you. |
--webhook-secret S | Require a secret (GitHub HMAC, or in the URL path). |
--every SECS | Poll interval for polling mode (default 30). |
--timeout MINS | Cancel a build that runs longer than this. |
--workflow N / --file | Which workflow / which pipeline file. |
--no-status | Don't post build status back to the host. |
--companion [--companion-port N] | Serve the iPhone/iPad app (default port 8899). |
Remote control, status · logs · trigger · cancel
Drive a running runner from the terminal, the desktop app's companion server, or a headless macon watch --companion, local or across the network. Full control without the GUI.
Auth is the same device token a paired iPhone uses. Against a local runner there's nothing to do, the CLI pairs itself through the shared on-disk pairing store and keeps the token in your Keychain. Against a remote runner (tunnel, EC2 Mac), pair once with the code the runner prints:
Pipelines match by name, id, or unique prefix. --follow exits 0/1 by the build's result, so macon trigger app --follow drops straight into another CI as a gate. --json (on status, doctor, config validate) makes everything scriptable.
Prometheus metrics
Every runner serves GET /metrics, Prometheus text exposition, behind the same bearer token: macon_up, pipeline/watching/building gauges, per-pipeline build totals by result, and last-build duration. macon metrics prints it; point a scraper at it for dashboards:
Portable config & validate
Scaffold and sanity-check the macon-export.json the CLI runs from, without opening the app.
validate catches empty workspace/repo/branch, duplicate webhook ports, missing provider credentials, and secrets that are in neither the file nor the environment, warnings for the soft stuff, errors for what would break a watch.
Run as a service
Install a watch as a launchd agent so it starts at login and restarts on crash, the way to run MacOn unattended on a dedicated or cloud (EC2) Mac. macon install-service is the one-line alias.
Credentials present in your shell at install time (BITBUCKET_*, GITHUB_TOKEN, ASC_*, SLACK_URL) are copied into the agent. Logs go to ~/Library/Logs/macon/<label>.log.
macon.yml, structure
A pipeline lives in your repo as macon.yml. It's Bitrise-style: named workflows composed with before_run/after_run, triggers that route branches/PRs to workflows, environment variables, and conditional or always-run steps.
Workflows
Each workflow is a named list of steps. Compose them so shared setup runs once:
before_run: [_setup], run these workflows' steps first.after_run: [_notify], run these afterward (e.g. Slack).env:, variables scoped to the workflow.
Pick which workflow runs with --workflow, or leave it to the triggers.
Steps
A step is a name and a shell script. Each becomes its own timed section in the app's log.
| Field | Meaning |
|---|---|
name | Shown in logs. |
script | Shell to run (zsh, in the checkout). |
run_if | Only run if this condition exits 0. e.g. '[ -n "$MACON_PR_ID" ]' |
always_run | true to run even if an earlier step failed. |
matrix | Fan the step out over combinations, see below. |
Test matrix
Fan a step out across every combination of values, ideal for UI-testing on multiple devices and OS versions. Each run gets MACON_MATRIX_<KEY> in its environment. All combinations run; the step fails if any of them fails.
Combinations run sequentially so simulators don't contend. Match the values to installed runtimes (macon sims).
Triggers
Route branches and pull requests to workflows (like Bitrise's trigger map). When a pipeline's workflow field is blank, MacOn matches here.
Built-in variables
Every step runs with these set:
| Variable | Value |
|---|---|
MACON_COMMIT, MACON_COMMIT_SHORT | The commit being built. |
MACON_BRANCH | Branch (or PR source branch). |
MACON_REPO | workspace/repo. |
MACON_WORKFLOW | The running workflow's name. |
MACON_BUILD_NUMBER | Incrementing build count. |
MACON_PR_ID, MACON_PR_TITLE, … | Set on pull-request builds. |
MACON_MATRIX_* | Set inside a matrix step. |
Provider-native variables (BITBUCKET_* or GITHUB_*) are also injected so tools like Danger work.
The second mode: a Bitbucket runner pool
Prefer to keep your existing bitbucket-pipelines.yml and let Bitbucket stay the brain? MacOn's other CI mode hosts a pool of Bitbucket self-hosted runners on your Mac. Bitbucket parses the YAML, schedules jobs, shows logs and gates PRs, your Mac just does the building.
- Tag steps with
runs-on: [self.hosted, macos]to route them to your machine. - Run multiple runners side by side; each gets its own working directory so checkouts never collide.
- Per-runner live log and status, Start/Stop controls, and Start All / Stop All from Settings or the menu-bar icon.
- Self-cleaning: stopping a runner empties its working directory; shared cache cleanup (DerivedData, SwiftPM, Archives, stale simulators) runs only when every runner is idle.
- Runners poll outbound to Bitbucket, nothing inbound to expose, so it works behind any NAT.
Both modes run happily at once: macon.yml pipelines for your own repos, a runner pool for the team's Bitbucket workspace.
Bitbucket & GitHub
A pipeline watches one host. Set credentials once (in the app's Settings, or via environment for the CLI).
| Provider | Auth | Environment |
|---|---|---|
| Bitbucket | Email + API token | BITBUCKET_EMAIL, BITBUCKET_API_TOKEN |
| GitHub | Personal Access Token | GITHUB_TOKEN |
The GitHub token needs repo access, classic repo scope, or fine-grained with Contents + Commit statuses + Pull requests.
Secrets
Secrets your pipeline needs (ASC_KEY_ID, ASC_ISSUER_ID, ASC_KEY_CONTENT, SLACK_URL, …) are injected into every step. In the app they're stored in the macOS Keychain, never in the repo. For the CLI they come from the inherited shell environment, unless you exported the config with secrets.
Shipping to TestFlight
MacOn runs your fastlane lanes, so shipping is a normal beta lane with automatic signing and an App Store Connect API key.
- Create an API key in App Store Connect → Users and Access → Integrations. Download the
.p8once. - Provide it as three secrets:
ASC_KEY_ID,ASC_ISSUER_ID, andASC_KEY_CONTENT(the.p8, base64-encoded). - Route
mainto a workflow that runsfastlane beta.
App ↔ CLI export
Set everything up visually in the app, then run it headless. In the app: Settings → Export Configuration… writes a macon-export.json (optionally including secrets). Then:
Exported without secrets, the file is config-only and safe to commit; supply token/secret values via the environment. Exported with secrets, it's self-contained, keep it private. The app's Import… loads a file back, so it also moves a setup between machines.
Companion app (iPhone & iPad)
The MacOn companion app turns your phone or iPad into a remote for the runner: a home hub with your pipelines, live build logs, screen streaming, remote control, a Stream Deck-style shortcuts board, a flows canvas, a code workspace, and AI, handy when the runner is a Mac in another room or a headless cloud instance. Add --companion to any watch to serve it:
Builds show as a structured step timeline, each step with its own status, duration, and expandable output (parsed the same way the desktop app does it), with the raw log one tap away. Re-run and Cancel sit right in the build view and on a long-press of any run.
Prefer the desktop app? Flip it on in Settings → Companion app, then Pair a device shows the address, code, and a QR to scan. Pairings are shared with the CLI, so a device works with either.
Pairing & devices
On start, the companion server prints a one-time address and code. In the app, tap Add runner and enter them (or scan the QR), the code is exchanged for a long-lived device token stored in the iOS Keychain, so you only pair once. Codes are single-use, expire (15 min by default), and are rate-limited.
Headless or cloud (EC2) Macs have no screen to read, the code prints to the log, so grab it over SSH. Expose the port through a cloudflared tunnel and point the app at the tunnel host (the app talks HTTPS/WSS):
Manage paired devices from the CLI, file-based, so it works even with no server running:
Remote access, from anywhere
By default the companion talks to the Mac over your local network. To reach it from cellular or another network, the desktop app can open a free internet tunnel for you: Settings → Companion app → Remote access. It runs cloudflared (a one-time brew install cloudflared) and shows a public https://…trycloudflare.com address, paste that into the app instead of the LAN address.
- Security is unchanged. The URL alone grants nothing: pairing still needs a one-time code, every other request a device token, and TLS is terminated by Cloudflare.
- The address rotates each time the tunnel restarts. On the device, ⋯ → Change Address repoints it, your pairing stays valid, no re-pairing. Or turn on iCloud sync and it follows automatically.
- On a headless/EC2 Mac, run the tunnel yourself and point the app at it:
cloudflared tunnel --url http://localhost:8899.
Manage pipelines from the device
The Pipelines screen isn't just a viewer. Each pipeline card has a watch toggle (start/stop the runner's watcher) and a long-press menu with Run Now, Edit, and Delete. The + button adds a new pipeline with the full editor, name, provider, workspace, repository and branch picked from searchable dropdowns (listed through the Mac's saved credentials; tokens never leave the Mac), watch mode, polling/webhook trigger, pipeline file, and fallback build command.
- Against the desktop app's server, everything works, add, edit, delete, watch, run.
- Against a headless CLI runner, the config is fixed: watch toggles and Run Now work, editing hides itself.
Screen streaming
Beyond builds and logs, a paired device can watch the Mac's screen live, handy for eyeballing a simulator during UI tests, or driving a Mac in another room. Tap the display icon on the dashboard. The Mac captures with ScreenCaptureKit and hardware-encodes H.264 (VideoToolbox); the iPhone/iPad decodes in hardware and displays it. Capture runs only while someone is watching.
- Enabled by the “Let paired devices view this screen” toggle (Settings → Companion app). macOS asks for Screen Recording permission the first time.
- Adaptive quality: the stream measures what the link can carry and adjusts bitrate on the fly, smoother over Wi-Fi and tunnels, without a fixed ceiling.
- Pick a resolution (360p · 480p · 720p · 1080p · 1440p · Full) and frame rate (30 / 60 / 120 fps) from the Touch Bar, drop resolution on a weak or remote link, raise it on a fast LAN. 120 fps needs a ProMotion Mac and iPad.
- Best on the LAN; usable remotely through the tunnel (higher latency). A headless/cloud Mac has no display to capture unless a virtual display is configured.
Remote control
While viewing the screen, turn on Control the Mac from the Touch Bar to drive it from the iPad. Two ways to move the cursor:
- Touchscreen (default), tap the screen where you want to click; drag to move, long-press to right-click, two fingers to scroll.
- Virtual trackpad, a movable, translucent pad that moves the cursor relatively, like a laptop trackpad: drag to move, tap to click, two fingers to scroll or pinch-to-zoom. Adjustable opacity, sensitivity, and size.
A Sidecar-style Touch Bar sits under the screen; swipe it left/right between pages. Page one: esc, switch spaces (◂ ▸), Mission Control, right-click, trackpad, keyboard, plus the resolution and fps pickers. Page two: the sticky ⌘ ⌥ ⌃ ⇧ modifier keys plus Undo, Spotlight, App switch, and Dock. Page three: media keys and a volume slider. Tap a modifier to arm it, then tap or type, e.g. ⌘ then C sends ⌘C, or ⌘ then a tap sends a ⌘-click. A hardware keyboard passes straight through.
- Off by default and gated on the Mac by “Let paired devices control this Mac” (Settings → Companion app). macOS also requires Accessibility permission before MacOn can move the cursor or type.
- Only paired devices (holding a valid token) can connect, but treat control access like handing someone your keyboard.
CompactOS, Mac apps, phone-sized
CompactOS makes a Mac app usable from your phone as if it were a native one. Open the CompactOS tab and you get a grid of the Mac's installed apps and open windows. Tap one and the Mac launches or focuses it, resizes that window to your device's screen (a fullscreen app becomes windowed first), and streams just that window, so the text is big and readable instead of a shrunk-down whole desktop.
- Direct touch: a tap is a click where you touch; a one-finger drag scrolls; long-press is right-click. The keyboard types straight into the app.
- Fits your orientation: rotate to portrait and the Mac window refits to fill it; raise the keyboard and the window shrinks so nothing hides underneath, the whole window always stays visible.
- Window switcher: jump between any of the Mac's open windows without leaving the session.
- Privacy by default: starting a CompactOS session raises the Mac's privacy curtain automatically, and the opened window is forced to the front so your taps always land on it.
It needs “Let paired devices control this Mac” enabled on the Mac (Accessibility permission).
Shortcuts deck (Stream Deck)
The Shortcuts board turns the iPad into a Stream Deck for the Mac: a grid of Liquid Glass keys that launch Mac apps (with their real icons, fetched from the Mac), fire keyboard shortcuts (⌘⌥⌃⇧ + key), or run a sequence, a chain of steps like open Chrome → wait → type a URL → Return.
- Keys are fully customizable: label, SF Symbol or emoji, preset or custom color.
- Edit mode adds, edits, and drag-rearranges keys; a Fill screen option stretches them into an even grid (4 keys → a 2×2 board).
- Describe a key instead: the AI composer takes “open Xcode and run my tests”, typed or dictated, and generates the key (icon, label, color, action), validated against the Mac's real app list before it's added.
- Requires “Let paired devices control this Mac” on the Mac, the same gate as remote control.
Flows, visual automations
Flows are block-graph automations you draw on the device and run on the Mac. Blocks snap onto a pannable, zoomable canvas and wire together with ropes that physically sag and swing (a verlet simulation); data flows along the wires from block to block.
- Block catalog: triggers, shell scripts, text tools, files, JSON, HTTP requests, PDF text extraction, clipboard, screenshots, logic (if/else, loops, variables), and AI blocks that call your configured providers or local Ollama models.
- Execution on the Mac: blocks run in dependency order; branches skip untaken paths; loops iterate their downstream. Watch each block light up live, then inspect its output, error, and duration in the run history.
- AI composer: describe an automation, typed or dictated, like “every morning at 8, fetch Hacker News, summarize it, speak it”, and the composer wires a validated graph for you to preview and save.
- Gated on the Mac: flows execute real commands, so they're off until you enable Settings → Companion app → Allow Flows.
Code workspace, editor, terminals, Xcode
The workspace is a mini IDE pointed at a folder on your Mac: a collapsible file explorer, a tabbed editor (type locally with zero latency; Save syncs the file back), and a side-by-side code assistant that reads the open file as context, @-mention other files to fold them in. Assistant models: Claude with your key, or local Ollama.
- Real terminals: multiple tabs, each an independent zsh PTY at the workspace root, ANSI colors, a key row for Ctrl-C/Tab/arrows, and scrollback that survives closing the panel.
- Xcode integration: the workspace detects projects, lists schemes, and runs build / test / clean straight into a terminal tab.
- Fix-the-pipeline loop: open
macon.yml, edit, save, re-run the build, without touching the Mac.
AI features, chat, voice, and the agent
Every AI feature is optional and bring-your-own: Anthropic Claude, OpenAI, Google Gemini, any OpenAI-compatible endpoint, or fully local models via Ollama running on the Mac. Keys are stored in the Mac's Keychain and never sync to the phone.
- AI chat: talk to the Mac's local Ollama models, private, on-device inference. Attach images (vision models) or PDFs/text files; conversations persist across launches.
- Voice chat: speak, and the AI answers out loud, with a natural Piper voice if installed on the Mac, or the device voice otherwise. Hands-free mode re-opens the mic after each reply; talking over the AI cancels it and listens again.
- The agent: give it a task and it drives the Mac, reading the accessibility tree and screen snapshots, planning steps, clicking, typing, and launching apps. A supervised mode pauses for your approval before each step, shown over the live screen so you watch it work. With Playwright installed (
macon initoffers it), it automates a real browser too.
Fleet map
The Fleet screen draws your setup as a little world: the Mac at the center with every paired device hanging off it on swinging, draggable strings (the same verlet ropes as the flows canvas). Devices that talked to the Mac recently glow; quiet ones fade. Tap a card for its pairing details, device model, last-seen time, token prefix, and revoke from there or with macon companion devices. The Mac app has the same map, so you can audit pairings from either end.
Wake & unlock the Mac
The companion can keep the Mac reachable and bring it back to life remotely. Everything here is off by default and toggled on the Mac in Settings → Power & Access.
- Stay awake, while the companion server runs, the Mac holds a power assertion so it never idle-sleeps, so a paired device can always reach it.
- Wake, from the phone's Mac Power section, wake the display on a running Mac, or send a Wake-on-LAN magic packet to wake it from sleep (needs “Wake for network access” in macOS energy settings, same Wi-Fi).
- Unlock, the Mac types a login password you store in its Keychain to dismiss the lock screen. Best-effort: macOS Secure Keyboard Entry can block synthetic typing, and it can't defeat a FileVault preboot screen. Requires Accessibility.
- Lock page, when the Mac reports a locked screen, the companion shows a themed 3D padlock with an Unlock button; unlocking plays a lock-open animation. An optional toggle raises the Mac's privacy curtain the instant it unlocks.
No special entitlement is needed, Wake-on-LAN is a plain UDP broadcast, and the Mac app isn't sandboxed. On the phone, “Stay connected” keeps reconnecting (and fires a wake packet) whenever the Mac drops off.
Always connected, via iCloud
Optional iCloud (CloudKit) sync makes the pairing self-healing. Turn it on in both apps (same Apple ID) and the Mac publishes its current address, including a freshly rotated Cloudflare tunnel URL, to your private iCloud database. The phone reads that beacon and re-points itself automatically, reconciling against the Mac's real address on every poll. Pair once and you never re-type an address again, no matter how the tunnel URL changes.
- It also carries wake/unlock/privacy commands, so they work even when the tunnel or LAN is down.
- Requires a paid Apple Developer account, the iCloud capability on both apps, and both devices signed into the same Apple ID. Off (and dormant) otherwise, the app falls back to the manual address you paste.
- Live screen streaming and control still ride the tunnel/LAN; iCloud keeps the phone pointed at the right place so they reconnect on their own.
Privacy screen
When you're driving the Mac remotely, the privacy screen covers its physical display with a “please don't touch” wall, so a passerby can't see what you're doing, while the companion keeps viewing and controlling the real desktop underneath. Raise it from Settings → Companion app or the menu-bar extra; dismiss it with the ⌃⌥⌘U hot key.
- Optional passcode to dismiss (stored hashed in the Keychain), leave it off and the hot key drops the wall immediately.
- Customizable: pick a background (aurora, gradient, solid, pure black, stars, waves, orbs, rays, your own image, or the 3D world, the theme's clay model drifting across the screen), an accent color, a symbol with animation, and the message, all with a live preview.
- Burn-in aware: a Motion option (gentle drift or a DVD-style bounce) keeps the bright content moving so it can't burn into an OLED display.
- It's a privacy curtain, not a security lock, it deters onlookers but isn't a login boundary.
Themes, the clay world
Both the Mac app and the companion are dressed as a soft, tactile 3D clay world (rendered with SceneKit), and it's re-skinnable. Pick a world in Settings and the whole app repaints, backdrop, cards, and the floating 3D models on the home.
- Paint boxes recolor everything: Pastel, Candy, Ocean, Sunset, Forest, Terracotta, Graphite, Neon.
- Model worlds swap the 3D pieces themselves: Monster (googly-eyed blobs), Cosmos (planets & stars), Blocks (toy bricks), Balloon, Dessert, plus Holo (iridescent chrome), Puffy (candy clouds), Pop (pink prism), and Googly (a field of eyeballs), each with its own take on the 3D headline type.
- The picker shows a live-rendered 3D preview of each world, and always-dark worlds (Neon, Cosmos, Holo) render the whole UI dark.