Quickstart: macOS
Install the commercial release on macOS, activate it, connect Telegram, and run your first agent. Every command is copy-pastable.
The resident relay is managed for you on macOS: install.sh registers it as a
login-time background service automatically. Step 4 only wires up the Telegram
credentials the relay needs — there is no service file to write by hand.
1. Prerequisites (Homebrew) — bash first
Section titled “1. Prerequisites (Homebrew) — bash first”The headline macOS gotcha: stock macOS ships bash 3.2 as /bin/bash, but
the framework requires bash ≥ 4.3. Fix this before anything else —
./install.sh itself only runs under bash ≥ 4.3. Run it under stock bash 3.2
and it fails fast with mat: requires bash >= 4.3; found <v> and a
brew install bash hint, changing nothing on disk.
Install Homebrew if you don’t have it, then install a modern bash and make it your interpreter:
brew install bashsudo sh -c "echo $(brew --prefix)/bin/bash >> /etc/shells"chsh -s "$(brew --prefix)/bin/bash"brew --prefix resolves to /opt/homebrew on Apple Silicon and /usr/local
on Intel, so the commands above are correct on both. chsh makes the new bash
your login shell (and therefore the shell tmux panes run); open a new terminal
tab afterwards and confirm:
echo "$BASH_VERSION" # expect 5.x, not 3.2Then install the rest of the runtime set:
brew install git tmux jq ghThese tools cover installation and the end-to-end path. install.sh hard-checks
tar with gzip support, jq, and cmp, and soft-warns for missing tmux
(check_dependencies in install.sh); the other tools are used by the install
or runtime but not guarded, so they are listed here regardless —
mat doctor (step 2) reports on every one
after install:
| Tool | Needed for | On macOS |
|---|---|---|
bash ≥ 4.3 |
the interpreter (namerefs, mapfile) |
brew — system bash is 3.2 |
tar + gzip |
extracting the release tarball | preinstalled — install.sh fails fast if either is missing |
jq |
JSON config the runtime reads | brew — install.sh fails fast if missing |
cmp |
guarded byte-for-byte configuration and prompt reconciliation | macOS /usr/bin/cmp; install.sh fails fast if missing |
tmux |
the whole framework runs in tmux panes | brew; install.sh warns if missing |
git |
repo operations (reported by the install preflight, not enforced) | preinstalled with Xcode Command Line Tools; brew installs a newer one |
curl |
downloading the tarball and later mat upgrade |
preinstalled |
Before installing you can audit all of this read-only with
./install.sh --check-dependencies (add --backend <kind> to include a
specific backend CLI). It reports the hard prerequisites, the runtime tools,
and the account steps that no package manager can install — backend login,
GitHub authentication, license activation, Telegram credentials — and it never
invokes mat doctor, which stays the post-install diagnostic. See the
Linux quickstart for the
full contract.
You also need:
gh(GitHub CLI) — required for the issue/PR workflow (brew install gh, included above).- A backend CLI — one of
claude(Claude Code),codex,copilot,pi,opencode,freebuff, orcmd(Command Code). This quickstart assumes the defaultclaudebackend; see First session for auth.
2. Obtain and install
Section titled “2. Obtain and install”Your LemonSqueezy purchase grants a downloadable product file — the release
tarball — plus a license key. Download the tarball, extract it, and run the
bundled installer (substitute your actual downloaded filename for
mat.tar.gz):
tar -xzf mat.tar.gzcd my-ai-team./install.shThe tarball is .git-less with a baked VERSION, so install.sh
auto-detects a local-payload install — no flag required. It copies the runtime
into ~/.local/share/my-ai-team and links commands into ~/.local/bin. If
~/.local/bin is not on your PATH, the installer prints the export needed to
make mat discoverable and leaves your shell configuration unchanged.
When ~/.config/mat/backends.json does not exist, the installer also writes a
minimal, human-readable Claude registry with default_effort set to high.
Existing or migrated backend configuration is preserved.
Like Linux, install.sh brings up the resident relay automatically — on macOS
it renders the bundled launchd LaunchAgent and launchctl bootstraps it. If the
install runs with no reachable gui/login session (e.g. over SSH), it skips
cleanly and the first adhoc/team/duo launch in your gui session self-heals
it. Either way there is no manual launchctl step; step 4 only supplies the
Telegram credentials.
The tarball is self-contained: it carries the runtime, the product skills, and
the relay service templates, and install.sh needs no network access.
Verify the install with mat doctor
Section titled “Verify the install with mat doctor”mat doctorOne read-only pass that names every unmet prerequisite at once — the tools from
step 1 (including the bash ≥ 4.3 floor, so a shell that is still stock 3.2 is
named here), gh authentication, ~/.local/bin on PATH with the linked
commands resolving to this install, the resolved backend token, Telegram
credentials, and the launchd relay agent. It exits non-zero if anything FAILs,
and each FAIL line carries the fix to run. It changes nothing and prints no
secret value.
Right after install the backend-token, Telegram-credential, and relay-agent
checks are expected to FAIL — steps 3–5 below are what satisfy them. Re-run
mat doctor at the end; a clean All checks passed. means the machine is fully
set up. On a host with no reachable gui/login session the relay-agent check is a
warn, not a FAIL, matching the install’s own skip behaviour. For the full check
list and the ok / warn / FAIL vocabulary, see
mat doctor in the user guide.
The gh gateway line confirms gh and HTTPS Git resolve your GitHub account
through mat’s credential gateway; if it warns, run install-gh-gateway.
The one file mat manages
Section titled “The one file mat manages”On every claude-kind launch, mat empties your personal ~/.claude/CLAUDE.md.
Claude Code loads it into every session regardless of CLAUDE_CONFIG_DIR, so it
would otherwise be added to every agent’s constitution:
- Nothing is deleted. The content moves to
~/.claude/CLAUDE.md.mat-backup-<UTC>, with a-Ncounter appended rather than clobbering an existing backup, and the launching terminal prints the backup path. - It runs on every launch, not just at install — if the file re-populates (a dotfiles run, a manual edit), it is moved aside again.
- Per-project
CLAUDE.mdfiles are untouched; only the one super-global file is. - Your own non-mat
claudesessions see the emptied file too; mat cannot keep it out of agent prompts and in yours at the same time. - Use
~/.config/my-ai-team/<mode>.mdor a project-local.my-ai-team/<mode>.mdfor agent prompt customisation; see Prompt overrides.
3. Activate your license
Section titled “3. Activate your license”mat activate <LICENSE_KEY>mat runs immediately after install without activation, but only an activated
machine can mat upgrade later (step 6) — so activate now. mat activate
records the key and instance id in ~/.config/mat/license.conf.
4. Bring up the Telegram loop — resident relay via launchd
Section titled “4. Bring up the Telegram loop — resident relay via launchd”The Telegram relay is the remote-control channel: your replies route back into the running agent’s tmux pane, so you drive sessions from your phone.
install.sh already rendered and launchctl bootstraped the LaunchAgent
~/Library/LaunchAgents/com.my-ai-team.tg-relay.plist — the Darwin analog of
the Linux relay service. All that remains is to hand it your Telegram
credentials; it self-recovers onto them within seconds. (If the
install ran with no gui session — e.g. over SSH — the first adhoc/team/duo
launch in your desktop session materializes and bootstraps it instead.)
4a. Create the bot and set credentials
Section titled “4a. Create the bot and set credentials”-
Create the bot. In Telegram, open a chat with @BotFather, send
/newbot, and give it a display name plus a username ending inbot. BotFather replies with an HTTP API token in the form123456:abc...— copy it and treat it as a password. -
Wake the bot. From the Telegram account you want the relay to message, open a chat with your new bot and send anything (
/startis fine). Telegram only delivers updates from accounts that have contacted the bot first. -
Get your numeric chat id. Open @GetIDs Bot, send
/start, and read theYour IDfield — that value is yourTG_CHAT_ID. Do not try to read it fromgetUpdates: once the relay is running it long-polls that endpoint continuously, so a manual call returns an empty result or409 Conflict. -
Derive
<HOST_KEY>. Runhostnameon the mat host and replace every character outside[A-Za-z0-9_]with a single_—XE-5CD423DJ98becomesXE_5CD423DJ98,my-laptopbecomesmy_laptop. The per-host token is named after it, so one shared secret file can hold bots for several hosts. -
Append the credentials to
~/.bashrc.secret(append — do not overwrite):Terminal window export TG_CHAT_ID="123456789"export TG_CHAT_TOKEN_XE_5CD423DJ98="123456:abc..." -
Verify the exports load.
source ~/.bashrc.secret, then echo both variables. An empty line means the file path is wrong or the export is malformed — fix it before continuing.
The relay sources ~/.bashrc.secret on every (re)start and refuses to start
without a per-host token, so once the exports are in place, restart it to pick
them up:
launchctl kickstart -k gui/$(id -u)/com.my-ai-team.tg-relaylaunchd’s KeepAlive also respawns the relay automatically within ~10s, so it
recovers onto the new credentials even without the explicit kickstart.
4b. Verify and manage it
Section titled “4b. Verify and manage it”Prefer mat relay over the raw launchctl calls below — it works the same
way on every platform and verifies the process is actually tg-relay before
acting on it:
mat relay status # detected manager, verified process, singleton-lock statemat relay stop # stop via launchd; the LaunchAgent stays registeredmat relay start # start it (fails closed if the plist is missing/out of date)mat relay restart # stop then start under one lockKnown launchd limitation: the singleton-lock field in mat relay status is
best-effort on macOS. Linux verifies process identity down to sub-second
/proc timing; launchd exposes no equivalent, so a same-second PID
coincidence is a known, narrow gap here — it never affects whether stop
refuses to report success, only the diagnostic detail shown.
The raw launchctl equivalents, if you need them directly:
# Inspect state (look for "state = running" and a 0 last exit status):launchctl print gui/$(id -u)/com.my-ai-team.tg-relay
# Force a restart after editing the plist or upgrading:launchctl kickstart -k gui/$(id -u)/com.my-ai-team.tg-relay
# Stop + unload (do this before re-bootstrapping an edited plist):launchctl bootout gui/$(id -u)/com.my-ai-team.tg-relayWithin a few seconds the bot messages you active under a <host> · tg-relay header. If it
doesn’t, tail the log:
tail -f ~/.local/state/my-ai-team/tg-relay.logThe log is capped at 50 MiB: when it grows past that, the oldest lines are
truncated and a truncated to newest … bytes line marks the cut.
A no token for host or TG_CHAT_ID unset line means step 4a’s exports aren’t
reaching the relay — confirm they are in ~/.bashrc.secret and that the plist’s
source "$HOME/.bashrc.secret" line is intact.
4c. Optional: keep the tmux server alive with no sessions
Section titled “4c. Optional: keep the tmux server alive with no sessions”If a named mat server should survive a drop to zero sessions, opt in manually:
tmux -L mat set-option -g exit-empty offPane and window IDs then grow until an operator runs
tmux -L mat kill-server; the next server starts with reset counters.
Three things to know about the managed relay
Section titled “Three things to know about the managed relay”- Restarts never give up. A relay that fails on every start is restarted indefinitely, roughly ten seconds apart. If the bot never announces itself, read the relay log rather than waiting it out.
- No memory cap. The service runs without an RSS ceiling, so a runaway relay is not stopped for you — check it if the machine starts swapping.
- Sleep pauses it. Idle sleep is held off so the Telegram long-poll keeps running, but closing the lid or a scheduled sleep still suspends the machine. On a laptop the relay pauses until wake; this is expected.
5. First session + verify the Telegram round-trip
Section titled “5. First session + verify the Telegram round-trip”Authenticate your backend once — two steps for the default claude backend:
claude setup-token # prints a long-lived tokenecho 'export CLAUDE_CODE_OAUTH_TOKEN=<paste-token>' >> ~/.bashrc.secretThe launcher sources ~/.bashrc.secret and reads CLAUDE_CODE_OAUTH_TOKEN; a
tokenless Claude backend fails fast at launch. The installer has already written
the inspectable default backends.json; use it for multi-account or
third-party-endpoint changes, as described in
Backend and user config.
Then launch an agent in any git repo:
cd ~/path/to/your/repoadhocA new tmux session opens with the agent. Now verify the loop end-to-end:
- From the Telegram account you wired up in step 4, send the bot a message.
- Confirm it appears in the agent’s tmux pane (prefixed
[relay]). - Confirm the agent’s reply comes back to you on Telegram.
A completed round-trip means install, activation, and the resident relay are all
live. Re-run mat doctor now: with the backend token, Telegram credentials, and
launchd agent all in place, it should report All checks passed.
6. Upgrades
Section titled “6. Upgrades”See whether a newer release is on offer, without installing anything:
mat upgrade --checkThat prints your installed version alongside the offered one and stops. To actually install it:
mat upgrademat upgrade reuses your stored key and installs only when the offered release
is not already installed (--force reinstalls the current one); the
license-gating Worker it contacts ships its endpoint inside the release, so there
is nothing for you to configure. The install re-renders the LaunchAgent and
restarts the relay onto the new code for you (a long-running bash daemon does
not hot-reload), so no manual restart is needed. If you ever need to force one:
launchctl kickstart -k gui/$(id -u)/com.my-ai-team.tg-relay