NestBox in the menu bar
A goose in the menu bar. It runs the companion for you, finds your pet, puts a new pet on Wi-Fi over its USB cable, and shows what the pet is doing and exactly what is sent to it. Everything it needs is inside the app, so a Mac needs nothing else.
On a new Mac
- Plug in the pet. A drive called NESTBOX appears, holding
NestBox.zip. - Unzip it, drag NestBox into Applications, and open it.
- The first-run window shows: - A new pet (plugged in, not on Wi-Fi yet): the Mac's Wi-Fi name is filled in when macOS allows it, otherwise type it. Type the password and click Put It on Wi-Fi. - The app types
wifi <name> <password>andtz <your time zone>on the pet's USB console. - When the pet shows up on the network, the window says "Your pet is on Wi-Fi at …". - The password goes to the pet only. It is never saved, logged, or shown in "What It Sends". - A pet already on Wi-Fi: "Found Goose at nestbox-1a2b.local". - Nothing found: "Plug your pet into this Mac with its USB cable". It keeps looking. - Start at login, and Teach a Coding Agent…. - macOS asks whether NestBox may find devices on your local network. Click Allow: that is how it finds the pet.
Your pet: pairing
Each pet announces a unique id over Bonjour (TXT id=, host=, pet=). The first real pet the app sees becomes yours when it is the only one on the network, or when you put it on Wi-Fi from this Mac. From then on, the companion finds it by its id, so it is still found after a rename or a new address.
If several pets are on the network and none is yours yet, the menu asks you to pick one under Pets on Your Network. The pairing is kept in two places:
- the app's preferences;
~/.nestbox/device. Line 1 is the URL, which the Claude Code hooks read. Line 2 isid=…, whichnestboxuses when you run it by hand.
The menu
| Item | Does |
|---|---|
| status lines | the pet and what it is doing, why, which pet it talks to (device or simulator) and its address, whether the companion is connected, and which signals are off. The goose turns grey when no pet answers. |
| Show / Hide Pet Window | the on-screen simulator (build/nestbox-sim). This is a developer's extra, shown only when that build is there. |
| Pets on Your Network ▸ | every pet the Mac can see. Yours is ticked (half-ticked: in use, not paired yet). Choose one to pair with it. |
| Pets ▸ | the pets your NestBox holds. Choose one to bring it out. |
| Signals ▸ | what the companion sends: typing rate, idle time, CPU, screen lock. Untick one and the companion restarts without it (typing rate off is sent as -1, the others are left out). The Mac's clock (time, utc_offset) is always sent. |
| What It Sends… | every message exactly as the companion sends it, and the pet's reply, live. |
| Quick Test ▸ | makes the pet react now: done, needs you (15 s), error, say hello. |
| Teach a Coding Agent… | the pet's own page for coding agents (GET /agents). Copy it into Claude Code, Codex, Cursor or any agent and ask it to connect itself to your pet, or use Copy Link for agents that can fetch it. The app changes no agent's settings itself. |
| Start at Login | registers the app with macOS (System Settings › General › Login Items). See below. |
| Set Up Pet's Wi-Fi… | the Wi-Fi part of the first-run window, for example to move the pet to another network. |
| Setup… | the first-run window again. |
| Quit NestBox | stops the companion and the pet window it started (SIGTERM, then SIGKILL after 1.5 s). |
Build
scripts/build.sh # everything, ending with build/NestBox.app and build/NestBox.zip
scripts/build_app.sh # just the app (swiftc; no Xcode needed)
scripts/package_app.sh # just the zip, the file the pet's USB drive carries
scripts/build_app_selftest.sh # the app's self-test (see below)
All of them write to build/, or to $NESTBOX_BUILD_DIR when it is set.
The app and the helper inside it are built for Apple Silicon and Intel, macOS 13 and later, and signed ad hoc. The Swift code builds in the Swift 6 language mode, with complete concurrency checking. build_app.sh compiles its own copy of nestbox, because the CMake build targets only this Mac's macOS version, and takes the version from CMakeLists.txt.
From build/ the app also finds the developer's pet window there. Installed anywhere else, it looks in the folder set by defaults write pet.nestbox.menubar home /path/to/nestbox (or --home, or $NESTBOX_HOME), then in ~/nestbox/build, for a checkout in the usual place.
Source layout
| Files | |
|---|---|
main.swift, AppLauncher.swift | command-line options, one instance at a time, signal handling |
Model.swift | the app's state; drives the two runners below and talks to the pet |
CompanionSupervisor.swift, PetWindowRunner.swift, ChildProcess.swift, ChildRegistry.swift, ProcessTable.swift | the child processes |
MachineLine.swift, HelperEvent.swift, SimulatorEvent.swift | the tagged @nestbox {json} lines the helper and the pet window print with --machine; the app reads only these, never their log wording |
PetClient.swift, NetworkPet.swift, Preferences.swift, AppPaths.swift | HTTP, nestbox find --machine, settings, paths |
MenuBar.swift, StatusText.swift, SendsWindow.swift, TeachWindow.swift, SetupWindow.swift | the UI |
WiFiSetup.swift, ConsoleReply.swift, SerialConsole.swift, USBPet.swift, MacWiFi.swift, PosixTimeZone.swift | Wi-Fi over USB; ConsoleReply.swift holds the console replies it reads, as firmware/esp32/src/console_reply.h does |
../cli/ | the nestbox helper (C++): activity loop, Bonjour, HTTP, hook, find |
../selftest/ | the self-test, built as a separate executable |
Start at login
Start at Login uses SMAppService. If macOS asks for approval, the menu says so and opens the settings page. Install the app in /Applications first. macOS knows an ad-hoc signed app by its signature, so after a rebuild you may need to switch the item off and on again.
If macOS refuses, use a LaunchAgent instead. Save this as ~/Library/LaunchAgents/pet.nestbox.menubar.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>pet.nestbox.menubar</string>
<key>ProgramArguments</key>
<array><string>/usr/bin/open</string><string>-a</string><string>/Applications/NestBox.app</string></array>
<key>RunAtLoad</key><true/>
</dict>
</plist>
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/pet.nestbox.menubar.plist # on
launchctl bootout gui/$(id -u)/pet.nestbox.menubar && rm ~/Library/LaunchAgents/pet.nestbox.menubar.plist # off
For testing
APP="build/NestBox.app/Contents/MacOS/NestBox"
"$APP" --device http://127.0.0.1:8081 # only this pet (also: --name NAME, --home DIR, --serial /dev/ttysN)
"$APP" --render-icon icon.png
The self-test is a separate executable, built from the app's sources and companion/mac/selftest/. It tests a built app against a simulator started for it, and exits 1 if anything failed. It is run by hand, not by CTest or CI: it needs a live simulator, and it announces stand-in devices on the local network while it runs.
scripts/build_app_selftest.sh
build/nestbox-sim --headless --port 8099 --name test-simulator &
build/nestbox-menubar-selftest --app build/NestBox.app --device http://127.0.0.1:8099 --name test-simulator \
--sim-args "--headless --port 8098 --name selftest-simulator" --shots /tmp/shots
It covers:
- the helper inside the app (its signature, and both CPU types);
- the device's state and the pets it holds, switching pets;
- the companion: start, signals off, restart after a crash;
- quick tests, and the page for coding agents;
- Bonjour: finding the pet by name, and pairing by id.
dns-sdannounces stand-in pets with TXT ids: the only real pet pairs itself, choosing another pairs that one, and a renamed pet is still found; - Wi-Fi over USB, against a fake pet console on a pty that answers like the firmware: - the name and password reach the pet exactly; - the time zone is sent; - it waits for that pet by its id; - a wrong password, and a port that never answers; - the password appears in no log or preference;
- the pet window, and quitting.
It uses its own preferences and a temporary HOME for every helper it starts. The stand-in pets are announced on the local network, so other NestBox apps there can see them while it runs. --shots DIR also draws the windows into PNGs.