slab #
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.
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.