Deeper notes for working on rt itself. The README covers the short version
(clone, bun install, run from source); this file covers the parts you only
need once you are changing the install path, the editor extension, the menu bar
app, or the release.
A machine runs one of two apps at a time:
- mattstack.app (prod): its daemon, deck and CLI are the compiled rt inside the bundle.
- mattstack-dev.app (dev,
rt-tray/build.sh dev): its daemon and CLI run your source checkout through bun.
To switch, open the other app, by hand or from Settings > General >
Developer. Opening an app by hand takes the Mac over: the other app's tray
gives up its daemon agent and login item and quits, the other app's launchd
jobs are booted out, and ~/.local/bin/rt is pointed at the opened app (the
dev app writes a wrapper that runs bun run <checkout>/cli.ts; the prod app
links its bundled rt). Whatever sat at ~/.local/bin/rt before is replaced.
A login-item launch never takes over: if the other app is running, it quits
quietly instead. Nothing records a mode; which app is active is simply the
one installed and running.
Each process knows its own flavor from whoever launched it:
MATTSTACK_FLAVOR=dev|prod in each app's launchd plists, exported by the dev
wrapper, and otherwise the build (a compiled rt is prod, a source run is dev).
rt version names the app the CLI belongs to.
The checkout the dev app runs is stored in ~/.mattstack/rt/state.db (the
dev-mode kv row, which the dev daemon launcher also reads):
rt settings source-path # show it
rt settings source-path ~/code/repo-tools # set it (rewrites the dev wrapper if it owns ~/.local/bin/rt)rt --post-installThis is the same entry point mattstack.app spawns for its Install button. It runs in three phases:
- Refuse to proceed if rt is running from a transient root, meaning a mounted
DMG or a Gatekeeper-translocated copy. Drag mattstack.app to
/Applicationsand run it again. - Sweep legacy state: migrate an old
~/.rttree into~/.mattstack/rt, and delete a stale phase-1 copy of the app under~/Applications. - Run
rt setup apply --non-interactive --team-of-one, which is the real install: a registry of steps covering the home repo, team join, secrets, PATH links, intercepts, settings seeding, repo clones, service registration, the proxy, skills, cron triage, plugins, the browser helper, herdr integration, the editor extension, service start, a snapshot push, and a final verify.
rt setup plan shows the checklist before anything runs; rt setup status
shows the same checklist as a post-install health view.
Note that rt does not auto-run the installer on first invocation. An rt
with no ~/.mattstack/rt/daemon.json prints a one-line hint pointing at
mattstack.app or rt setup, and nothing else. That is deliberate:
auto-running would make rt setup plan unreachable before an install.
rt verify # human output, exits 1 on critical failures
rt verify --ci # same output, no ANSI colors, and a CI-appropriate severity set
rt verify --json # structured JSON for toolingrt verify is a read-only render of the same plan rt setup plan computes. It
does not install anything. Rows marked required are the critical ones: macOS
version, Command Line Tools, arm64 architecture, the rt binary, absence of
split legacy state dirs, the app bundle, the daemon, the install flavor,
herdr, Claude, the browser helper, plus credential and forge-reachability
groups. The editor extension, the vsix, shell integration, the ~/.local/bin
link, and intercepts are reported but not critical.
--ci additionally downgrades the permission, account, and access groups (and
a few tool rows) to non-critical, since a runner has no keychain or logged-in
browser.
Use this to test how the release binary behaves (compiled mode, no bun dependency at runtime):
bun build --compile ./cli.ts --outfile /tmp/rt-local
/tmp/rt-local --versionA compiled rt reads the real ~/.mattstack tree and will act on it. Run it
under an isolated HOME (env -i HOME=$(mktemp -d) /tmp/rt-local ...) unless you
specifically intend to touch your live install.
cd extensions/vscode/rt-context
bun install
bun run watch # live rebuild during development
bun run package # outputs rt-context-x.x.x.vsix
bun run install-local # packages + installs into CursorOr install a packaged build into every detected editor with
rt settings extension.
cd rt-tray
./build.sh debug # build and open in Xcode
./build.sh release # build release mattstack.app (prod)
./build.sh dev # build release mattstack-dev.app (dev, runs the daemon from source)
./build.sh install # build + copy mattstack.app into placeThe tray app reads its version from Info.plist
(CFBundleShortVersionString), which the CI build injects via git describe.
Local builds report whatever is in the plist at build time.
Never rebuild, re-sign, or reinstall an app bundle macOS has already blessed. Re-signing invalidates Login Items and TCC grants, and the failure is silent. Build into a scratch directory instead.
Push a version tag; CI does the rest.
git tag v1.2.3
git push --tags.github/workflows/release.yml then:
- Compiles
rtforbun-darwin-arm64and builds the Gort-uihelper for arm64. - Fetches the pinned helper binaries listed in
rt-tray/deps.lock. - Builds
mattstack.appwith the version baked intoInfo.plist, packagesrt-context.vsixintoContents/Resources/, and signs and notarizes. - Runs a clean-room install from the built
.zipon a fresh macOS runner:rt --post-install, a PATH check,rt daemon install, thenrt verify --ci. This gates publication rather than following it. - Publishes the release assets:
mattstack-<version>.dmg,mattstack-<version>.zip, Sparkle.deltafiles,appcast.xml, andSHA256SUMS.
rt ships an Apple silicon (arm64) build only. Intel Macs are not supported, and
rt verify fails the architecture row on one.
Updates reach users through Sparkle, driven by mattstack.app. rt update only
asks the app to check; it never downloads or installs anything itself.
docs/release-and-distribution.md carries the deeper release rules: the bundle
layout, signing, the marketplace publish, and the clean-room VM.