Successor to the macOS Logger desktop app. Receives log events and
storage snapshots from a mobile client SDK over WebSocket, persists them
to SQLite, and renders a virtualized live feed plus a storages inspector.
Internal name. The source tree, Xcode project, and target are all named
Beaver(renamed from the codenameLoggerNextin D38). Two things are intentionally kept asLoggerNextfor backwards compatibility:
- The bundle identifier
com.applicaster.LoggerNext— changing it would break Sparkle in-place updates for existing installs.- The on-disk Application Support directory
~/Library/Application Support/LoggerNext/— changing it would orphan existing users' sessions, bookmarks, and storage snapshots.See DECISIONS.md D21 (the original "don't rename" decision) and D38 (the source-tree rename done while keeping the two stable identifiers).
Architecture and feature set are stable (see DECISIONS.md); the
source tree lives under Beaver/.
| File | Purpose |
|---|---|
DECISIONS.md |
Numbered, dated decision log (D1–D20). Source of truth. |
ARCHITECTURE.md |
Layered design synthesizing the decisions. |
PROTOCOL.md |
Wire-protocol contract with the mobile SDK. |
SESSION_FILE_FORMAT.md |
Export/import file shared with zapp-support (normative). |
COMMAND_CATALOG.md |
Forward-looking spec for the SDK cmdspec command (D17). |
Beaver/Resources/MCP.md |
Agent Access (MCP): tools, recipes, testing without Xcode. |
SCAFFOLD.md |
One-time Xcode project setup steps. |
Beaver and zapp-support (the web logger) write and read the same file, so a customer can send logs from either tool and we open them in either.
- The format is
SESSION_FILE_FORMAT.md— the only copy; zapp-support links here. - Export (log feed and Storages screen): filtered or all — the filter narrows the events; the session's storage and network requests are always included. The Storages screen also has Export storage only, with Keychain values redacted by default.
- Import opens Beaver's files, zapp-support's files (current and older), and HAR. Every file that opens today keeps opening; old Beaver versions open new files too.
- Changing the format takes a pair of PRs, one here and one in
zapp-support, cross-linked; neither merges alone. See the spec's §1 and
CLAUDE.md.
Beaver runs an MCP server so an AI agent on the same Mac can read everything
Beaver collected from the connected app — sessions, logs, network requests,
storage and the app's commands. It listens on http://127.0.0.1:9081/mcp
(loopback only) while Beaver is open and Agent Access (MCP) is on in
Beaver → Settings… → Agents (⌘,, or the gear at the bottom of the
sidebar). Everything the agent does is listed behind the Agent toolbar
button, which also has Connect agent with these steps for your port.
Set a client up once; after that just ask it to use beaver.
Run once in Terminal — it works in every project:
claude mcp add --scope user --transport http beaver http://127.0.0.1:9081/mcpRemove later with claude mcp remove beaver.
Add to ~/.cursor/mcp.json (merge into mcpServers if the file already
exists), then enable beaver in Cursor Settings → MCP:
{
"mcpServers": {
"beaver": { "url": "http://127.0.0.1:9081/mcp" }
}
}Settings → Connectors → install the PerplexityXPC helper → Add
Connector → Simple. Name it beaver and use this command (needs
Node.js; it bridges Perplexity's local connectors to Beaver):
npx -y mcp-remote http://127.0.0.1:9081/mcpAdd a Streamable HTTP server at http://127.0.0.1:9081/mcp. A client that
only runs commands can use the Perplexity command instead.
curl -s -X POST http://127.0.0.1:9081/mcp -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'First prompt to try: "Use beaver: look at the app's logs from the last 10 minutes and tell me about errors and failing network requests."
Port taken? Settings → Agents → Port, then Apply (and set your agents
up again with the new port). Tools, recipes and the tester checklist:
Beaver/Resources/MCP.md. What it can do, with full
flows: plans/2026-09-23-mcp-capabilities.md.
open Beaver.xcodeproj
# ⌘R to build and run.The app starts a WebSocket listener on ws://<your-ip>:9080. Use
App ▸ Copy WebSocket URL (⇧⌘C) to copy the address, then point your
mobile client at it.
- macOS 26 (Tahoe) or later
- Xcode 17 or later
- Swift 6 toolchain
One-click install URL — always the newest signed + notarized build:
Or skip the landing page and go straight to the download:
https://github.com/applicaster/Beaver/releases/latest/download/Beaver.zip
- Click the link. You'll get
Beaver.zip(universal binary, signed- notarized by Apple — Gatekeeper accepts it on first launch).
- Unzip → drag
Beaver.appinto/Applications. - Launch it. The toolbar's Copy IP button gives you the
<your-mac-ip>:9080address to point your mobile SDK at.
From the second launch onward, Sparkle picks up new releases automatically. You can also pick a specific version from the releases page if you ever need to pin one.
There are two paths: automated via CircleCI (preferred — every
merge to main that bumps the version produces a signed release),
and manual from your Mac (fallback when CI is unavailable).
.circleci/config.yml runs the full release pipeline on every push
to main. CI decides the next version from the commit messages
since the last tag (Conventional Commits–style), bumps the pbxproj,
builds, signs, notarizes, tags, publishes, and updates the Sparkle
appcast. No local version bump needed.
Commit-message convention (D23):
| Prefix on any commit since last tag | Bump | 1.0.3 becomes |
|---|---|---|
release: |
major | 2.0.0 |
feat: |
minor | 1.1.0 |
(anything else, e.g. fix:, chore:) |
patch | 1.0.4 |
Highest-impact prefix in the range wins. A PR with one feat: and
three fix: commits → minor bump.
Per-release workflow:
# 1. Make your changes. Commit with the right prefix.
git commit -m "feat: saved filter presets"
# or: fix: typo in level menu
# or: release: drop macOS 14 support
# 2. Add a CHANGELOG note under [Unreleased]. The release commit moves
# it into "## [X.Y.Z] - date", the GitHub Release body (D90).
# 3. Push.
git pushCircleCI does the rest in ~5-7 min. The release appears at
https://github.com/applicaster/Beaver/releases/latest, and the
Sparkle appcast picks it up so installed Beaver instances see the
update on their next launch.
Merging several PRs in a row is fine: only the newest commit on main
releases, and it ships everything since the last tag. Jobs for older
commits halt green with "main has moved on" and publish nothing (D93).
Required CircleCI environment variables (set once under Project
Settings → Environment Variables): APPLE_ID, APPLE_APP_PASSWORD,
APPLE_TEAM_ID, DEVELOPER_ID_APPLICATION, DEVELOPER_ID_P12_BASE64,
DEVELOPER_ID_P12_PASSWORD, GITHUB_TOKEN. See the comments at the
top of .circleci/config.yml for how to derive each value.
Use this when CI is down, you need to test a release locally, or
you're cutting a one-off build that shouldn't go through main.
One-time setup
cp scripts/.envrc.example .envrc.local
$EDITOR .envrc.local # fill in the four secrets below
chmod +x scripts/release.sh # if you cloned and the bit was stripped.envrc.local is gitignored — your secrets stay local.
What you need to provide in .envrc.local:
| Env var | Where to get it |
|---|---|
APPLE_ID |
The Apple ID on the Developer Program account. |
APPLE_APP_PASSWORD |
App-specific password generated at https://appleid.apple.com → Sign-In and Security → App-Specific Passwords. |
APPLE_TEAM_ID |
10-char team ID from https://developer.apple.com/account → Membership Details. |
DEVELOPER_ID_APPLICATION |
Exact certificate name from security find-identity -v -p codesigning (the one starting with "Developer ID Application:"). |
No installer profile is needed — D13 ships a notarized .app.zip,
not a .pkg. The Developer ID Application certificate is the only
signing identity required.
Cutting a release manually
# 1. Bump + date the CHANGELOG + build + sign + notarize + zip.
make ship VERSION=1.1.0 # ≈ 3–5 min including notarization
# → build/Beaver-1.1.0.zip
# 2. Push the commit + tag so CI sees consistent state.
git push && git push --tags
# 3. Publish the GitHub Release with the zip attached.
gh release create 1.1.0 build/Beaver-1.1.0.zip \
--title "Beaver 1.1.0" \
--notes-file CHANGELOG.mdmake ship is make bump VERSION=X.Y.Z (bumps MARKETING_VERSION +
CURRENT_PROJECT_VERSION, moves [Unreleased] into ## [X.Y.Z] - date
with scripts/changelog-release.sh, commits, tags) followed by make release
(builds, signs, notarizes, zips). Each step can also be run on its
own — see below.
Useful sub-commands
make version # print current MARKETING_VERSION + build
make bump VERSION=1.1.0 # bump + commit + tag, no build
make release # build + notarize the current version
make tag # tag HEAD at the current MARKETING_VERSION
make build # plain Debug build, no signing
make test # run the unit tests
make clean # wipe build/ + DerivedDataTroubleshooting
| Symptom | Fix |
|---|---|
make: ./scripts/release.sh: Permission denied |
chmod +x scripts/release.sh |
No signing certificate "Developer ID Application" found |
Verify DEVELOPER_ID_APPLICATION in .envrc.local matches security find-identity -v -p codesigning output exactly |
notarytool 401 Unauthorized |
App-specific password is wrong or expired. Regenerate at appleid.apple.com |
| Notarization stuck on "in progress" | xcrun notarytool log <submission-id> --apple-id … --password … --team-id … to see the rejection reason |
spctl assess warning at the end |
Re-run make release — stapling didn't complete cleanly |
make ship fails because tree is dirty |
Commit/stash your changes first; make bump refuses to run on a dirty tree to avoid mixing version bumps with other edits |
Same as the original Logger repository.