slab #
A desk for running many coding agents at once. Slab is a macOS menu bar app (plus a handful of hooks and scripts) that gives every Claude Code, Codex or Easel session on the machine a colour, a name, a stone, and a place on the wall. Story page: https://aesthetic.computer/slab.
What the menu bar does today:
- Theme by status. Each Terminal window is re-skinned by the session
inside it: one tint while the agent works, another when it is waiting on
you, a third when the turn is complete. Profiles live in
seed/. - Title by task. The window title becomes what the session is doing.
- Prompt rocks. One 3D pixel stone per live prompt, parked top-right of
its terminal. Shape = identity, spin = status, pet name in bubble letters,
a seeded percussion voice on hover, the session summary on click. Easel
rocks double as a scannable code for the piece being made.
(
menubar-swift/Sources/SlabMenubar/PromptSigilOverlay.swift) - The wall. ⌘⌥T tiles the agent terminals, ⌘⌥S scatters them, ⌘⌥ arrows walk focus spatially (and across Macs over Deskflow), ⌘⌥X focuses the rocks, ⌃⌥⌘A flips dark mode fleet-wide.
- Status sounds. Three synthesized cues (complete / awaiting / disconnected) that share one voice and differ only in contour.
- Game Mode. GeForce NOW calls Tailscale a VPN on sight of the tunnel
interface, even with no exit node routing a single packet. One switch
pauses the tailnet and pins AirDrop's
awdl0radio down (it channel-hops the Wi-Fi card mid-stream), and puts both back on the way out — optionally by itself, the moment GeForce NOW opens. (menubar-swift/Sources/SlabMenubar/GameMode.swift) - Fleet. Every Mac runs the same menu bar; they share a ledger of live
sessions over the tailnet (
host:name),framephotographs any desk andpuppetacts on it. Rocks are never drawn for remote machines.
Slab is not packaged: no download, no installer script. Clone the monorepo,
run ./install.sh --prompt-host for hooks + markers, then
menubar-swift/install.sh to build and launch the bar. The rest of this
README is mostly about the lid daemon, which is where Slab began.
The lid (where it started) #
A lid-closed ambient + reactive audio system for the MacBook, gated on Claude Code activity, with a completion chime that sleeps the Mac when the last thread finishes.
Close the lid while Claude has active work in flight (with sleep suppressed) and the laptop becomes a "sleeping slab": an ambient C-major-pentatonic bed loops through the speakers while a mic-listener picks up claps, snaps, shushes, kisses, hums, and close-up sings — responding in real time with short pentatonic pluck-arpeggios that mirror each sound's pitch contour. Close the lid with nothing active and it's silent. When Claude finishes a prompt, it beeps once per other in-flight prompt or subagent; when the last piece of work completes, the "all-done" chime plays — and if the lid is closed, the Mac sleeps right after.
Active work is tracked as marker files in two directories:
$SLAB_HOME/state/active-prompts/<session_id>— created by theUserPromptSubmithook, removed byStop.$SLAB_HOME/state/active-subagents/<ts>-<pid>-<n>— created by thePreToolUsehook (filtered to the Task tool), removed (oldest-first) bySubagentStop.
The daemon treats the total file count as "active work". When the Stop hook runs with zero remaining markers and the lid is closed, ambient stops, the all-done chime plays, and the Mac sleeps.
What it does #
| Event | Sound |
|---|---|
Lid close (disablesleep=1) |
display sleep. Ambient only if a prompt or subagent is in flight. |
| Prompt or subagent starts while lid closed | ascending C-arp + ambient loop starts |
| Mic transient while ambient is playing (> 2 kHz) | 4-note pentatonic pluck arp, contour mirrors input |
| Lid open while ambient playing | descending ding + smooth descending arp stinger + ambient stops |
| Lid open with no ambient | silent |
| Subagent finishes (Task tool) | single ping |
| Claude Stop, other active work remaining | N ascending beeps (C6 D6 E6 G6 A6 C7 D7 E7) |
| Claude Stop, all work done, lid open | "all-done" chime |
| Claude Stop, all work done, lid closed | TTS "i'm tired" with cosine fade-out tail → pmset sleepnow |
| User submits new prompt | touches active-prompts marker, sets disablesleep=1 |
Install #
git clone git@github.com:whistlegraph/aesthetic-computer.git
cd aesthetic-computer/slab
./install.sh
The installer will:
- Symlink scripts into
~/.local/bin/ - Copy sound assets into
~/.local/share/slab/sounds/ - Build a Python venv at
~/.local/share/slab/venv/withnumpy,sounddevice, andrumps - Install two launchd agents:
~/Library/LaunchAgents/computer.slab.daemon.plist— the lid-ambient daemon~/Library/LaunchAgents/computer.slab.menubar.plist— a menu-bar status item + transparent full-screen HUD in the top-right corner of the screen
- Merge hooks into
~/.claude/settings.json(Stop, SubagentStop, PreToolUse(Task), UserPromptSubmit, SessionStart) - Install a passwordless-sudo rule at
/etc/sudoers.d/slab-pmsetforpmset(prompts for password once)
Opt-outs: --no-hooks, --no-sudoers.
For a Mac that should participate in the prompt-rock/prox fleet without the ambient audio, lid daemon, or sudoers changes, use:
./install.sh --prompt-host
This installs the agent wrappers, Claude hooks, and marker state only. Install
the Swift menu bar separately with menubar-swift/install.sh.
Two Slabs: the studio one and the shipped one #
install.sh builds the Slab that belongs to this studio. It symlinks scripts
back into the checkout so an edit takes effect immediately, merges hooks that
shell out to jq, and signs the bundle with a stable self-signed
certificate — chosen deliberately, because ad-hoc signing changes the code
hash every build and would revoke the Accessibility grant each time.
menubar-swift/release-solo.sh builds the other one: the Slab a stranger can
download.
cd menubar-swift
./release-solo.sh # build → sign → notarize → staple → DMG
./release-solo.sh --no-notarize # local smoke test; the result will NOT open elsewhere
It produces menubar-swift/dist/Slab-<version>.dmg, Developer ID-signed with
the hardened runtime, notarized and stapled so it opens offline on a Mac that
has never heard of us. Three things differ from the studio build:
- Hooks live in the binary. The shell hooks need
jqthirteen times andpython3three times, and macOS ships neither.slab-menubar hook <event>(seeSources/SlabMenubar/HookCLI.swift) reimplements the marker protocol withJSONSerializationandsysctl, so the only dependency left is the app itself. Both halves write the same markers; change one shape and the rocks stop reading. - It sets itself up. On first launch
FirstRun.swiftoffers to merge those hooks into~/.claude/settings.jsonand seed the Terminal profiles from a copy bundled inContents/Resources/solo. It refuses outright when~/.local/bin/claude-prompt-log.shis a symlink, which is how it knows a repo install owns the machine and must not be touched. - It is signed by Developer ID. A Mac holding both builds will be asked to grant Accessibility once more, because the designated requirement is part of what TCC remembers. Delete the old bundle when you switch, or the permission prompt returns on every launch.
bin/verify-solo-bundle.sh gates the signing step. It checks the parts are
present, that the binary references no path inside a home directory, that it
links only system libraries, and that the hook subcommand really writes a
marker. That check exists because Menu Band once shipped a DMG that ran on the
machine that built it and crashed everywhere else.
Requires a Mac holding the Developer ID key (blueberry or neo) and the vault's
apple/app-specific-password.env for the notary service.
To publish, follow the Menu Band pattern: upload the DMG to
assets.aesthetic.computer/slab/, then record version, size and sha256 in a
manifest the /slab page reads. Do not publish a build that has not been
launched on a Mac that never ran Slab — the first-run dialog and the TCC
prompts are the two things no check here can prove.
Usage #
claude-sleep awake # disable all sleep (stay awake with lid closed)
claude-sleep auto # restore normal sleep behavior
claude-sleep now # sleep the Mac immediately
claude-sleep status # show SleepDisabled state
Before using lid-closed ambient mode: claude-sleep awake. The first time the mic listener runs you'll get a macOS microphone permission prompt.
Uninstall #
./uninstall.sh # unload agent, remove symlinks + hooks + sudoers
./uninstall.sh --purge # also delete ~/.local/share/slab (sessions, logs, venv, sounds)
Layout #
slab/
├── bin/ # scripts (symlinked into ~/.local/bin/)
│ ├── lid-ambient.sh # launchd daemon, polls lid + active prompts
│ ├── lid-reactive.py # mic → pluck-arp synth + noise voice (Python)
│ ├── lid-ambient-synth.swift # live AVAudioEngine ambient drone + capture (Swift)
│ ├── lid-return-generate.py # generate lid-return.wav (smooth descending arp)
│ ├── slab-menubar.py # rumps menu bar status item (no Dock icon)
│ ├── claude-sleep # sleep-state toggle
│ ├── claude-stop.sh # Stop-hook entry (prompts + subagents aware)
│ ├── claude-prompt-log.sh # UserPromptSubmit hook (touches active-prompts marker)
│ ├── claude-tool-pre.sh # PreToolUse(Task) hook (touches active-subagents marker)
│ ├── claude-subagent-stop.sh # SubagentStop hook (pops oldest subagent marker + ping)
│ ├── claude-ping-repeat.sh # legacy 30s pings (no longer invoked)
│ ├── claude-sleep-schedule.sh # legacy delayed auto-sleep (no longer invoked)
│ ├── slab-monitor.sh # resource sampler
│ └── slab-zone # location-zone manager
├── sounds/ # WAV assets (lid chimes, pings, beeps)
├── launchd/
│ ├── computer.slab.daemon.plist.template
│ └── computer.slab.menubar.plist.template
├── sudoers.d/
│ └── slab-pmset.template
├── settings-fragment.json # Claude Code hooks, merged on install
├── install.sh
└── uninstall.sh
Runtime state lives under ~/.local/share/slab/:
sessions/<stamp>-<zone>.wav per-lid-close mix of Python listener output (noise + plucks)
sessions/ambient-<stamp>.wav per-lid-close recording of the live Swift ambient drone
sessions/<stamp>-<zone>.jsonl trigger events + location metadata
logs/lidalive.log daemon transitions
logs/reactive.log reactive listener triggers
logs/resources.jsonl CPU/RSS samples every 15s
logs/claude-stop.log Stop-hook activity
config/zones.json geofenced zones + per-zone scale/dynamics
state/active-prompts/<id> one file per Claude session with a prompt in flight
state/active-subagents/<id> one file per in-flight Task-tool subagent
state/last-location.json cached coords (if Location Services unreachable)
venv/ Python venv for the reactive listener
sounds/ installed sound assets (lid chimes, pings, beeps)
Zones (location-aware ambient) #
Each lid-close queries Core Location and picks the closest zone in
~/.local/share/slab/config/zones.json. The matching zone overrides the
reactive listener's scale, pitch mapping (div_factor), and amp. Coords +
zone name are written into the session JSONL and appended to the WAV
filename.
brew install corelocationcli # one-time (install.sh can run it)
# grant Location Services permission once
slab-zone where # show current coords + zone
slab-zone add home 150 # pin current spot as 'home', 150m radius
slab-zone add studio 60
slab-zone list # dump zones.json
slab-zone remove home
Available scales: major_pentatonic, minor_pentatonic, blues,
dorian, phrygian, lydian, whole_tone, chromatic. Edit
zones.json directly to tweak a zone's scale, div_factor, or
arp_amp.
Tuning #
Most knobs live at the top of each script:
- Ambient —
bin/lid-ambient-synth.swift: scale, note-gap range, duration/fade ranges, detuning, drone frequency, AU effects (reverb preset, delay time/feedback). Recompile viaswiftc -O -o ~/.local/bin/lid-ambient-synth bin/lid-ambient-synth.swift(install.sh does this automatically). - Reactive listener —
bin/lid-reactive.py:HIGH_BAND,TRIGGER_RATIO,MIN_GAP,DIV_FACTOR,NOTE_DUR,PLUCK_TAIL,ARP_NOTES,ARP_AMP. Scales live inSCALE_INTERVALS. - Lid-poll interval —
bin/lid-ambient.sh:POLL(default 0.5s). - Resource poll —
bin/slab-monitor.sh:INTERVAL(default 15s).
Edits to files in slab/bin/ take effect immediately because the installed copies are symlinks back into the repo.
Requirements #
- macOS (Apple Silicon tested). Intel should work but the display-sleep-on-lid-close path depends on
pmset displaysleepnow. - Homebrew, Python 3.11+,
jq,swiftc(Xcode Command Line Tools). - Microphone permission for the reactive listener (prompted on first run).
Notes #
pmset disablesleep 1suppresses all sleep, including explicitpmset sleepnow— the sudoers rule lets the scheduler toggle it back off before suspending.- The reactive listener writes 44.1 kHz mono int16 WAVs of only its own output (the pluck arps). The looping ambient
afplayand the chimes aren't captured — that would need a loopback audio device like BlackHole. - Session files accumulate forever by default. Purge periodically:
rm ~/.local/share/slab/sessions/*. - Apple Silicon MacBooks have no accelerometer, gyroscope, or accessible CPU temp via public APIs — so shake/gesture and temperature-modulation paths aren't implemented here yet.