# toolchain/macos — SCORE Conventions for working on @jeffrey's macOS hosts — the fleet (neo, blueberry, panda, chicken). All run the same user `jas`, so the `/Users/jas/...` paths below are identical on every machine. Machine specifics (e.g. neo's drive size) are called out inline. --- ## The Desktop convention **On every fleet Mac, `~/Desktop/` should ONLY contain the `Shelf` alias** (plus macOS system dotfiles like `.DS_Store` / `.localized`). Everything else is media cruft and needs to be relocated. (Adopted on neo first, then blueberry 2026-07-03; same convention fleet-wide.) ### Why - `~/Desktop` is **auto-cleaned mid-render** by a background macOS housekeeper (observed dropping files during long ffmpeg/whisper jobs). Anything important that lives there can vanish mid-run. - A clean desktop is a stable visual surface for screen-recording, screenshots, and the wallpaper-tint daemon (see [[slab_desktop_tint]]). - Single alias = single click to drill into the actual working dir, no more "which copy is canonical." ### The single allowed alias ``` ~/Desktop/Shelf -> /Users/jas/Documents/Shelf/ ``` That's the only thing on the desktop. All real work lives behind it. ### Where things actually live (under `~/Documents/Shelf/`) - `gens//refs/` — gpt-image-2 reference image sets per generation run (e.g. `gens/trancepenta-zuck/refs/`, `gens/amazing-grace-sections/verse1/refs/`). Canonical home for `whistlegraph-butterfly.png`, `pals-logo.png`, jeffrey + zuck refs. - `-DISTROKID/` — released master.wav + 3000px cover.jpg + encoded mp3 for tracks that shipped (e.g. `hellsine-DISTROKID/`). - `-ultimate/` and `.-pre.assets/` — pre-master engine outputs + struct.json for the active rendering pipeline. - `desktop-cleanup-YYYY-MM-DD/` — dated bucket for ad-hoc relocation when the desktop accumulates cruft. Don't delete, just move. ### How to clean When the desktop has accumulated media cruft, move EVERYTHING except the `Shelf` alias into a dated cleanup folder under it: ```bash mkdir -p "/Users/jas/Documents/Shelf/desktop-cleanup-$(date +%Y-%m-%d)" # then move each item (see gotcha below for screenshots) ``` Route by category if you can — release-shaped material into `-DISTROKID/`, image refs into `gens//refs/`, working files into the dated cleanup bucket. Don't delete anything; the auto-cleaner already deletes plenty. ### Gotcha — Screenshot filenames macOS screenshot filenames like `Screenshot 2026-05-26 at 12.25.07 PM.png` contain a **unicode NARROW NO-BREAK SPACE** (U+202F) between the time and `PM`, NOT a regular space. Copy-pasting the filename from `ls` output substitutes a regular space, and bash `mv` then fails with `No such file or directory` even though `ls` clearly lists the file. **Workaround:** use Python's `os.listdir` + `shutil.move`, which reads the actual filename bytes: ```python import os, shutil src_dir = '/Users/jas/Desktop' dst_dir = '/Users/jas/Documents/Shelf/desktop-cleanup-2026-05-26' for f in os.listdir(src_dir): if f.startswith('Screenshot'): shutil.move(os.path.join(src_dir, f), os.path.join(dst_dir, f)) ``` Or in fish: `mv ~/Desktop/Screenshot*.png "$dst_dir/"` (glob expansion sees the real bytes — works fine, unlike the manually-quoted path). --- ## Reclaiming disk space Drive pressure varies by machine (neo is a 228 GB drive that lives between 85–99% full; blueberry is a 460 GB drive, ~60% as of 2026-07-03). When any of them gets tight, scan in this order — biggest wins first, safest first. ### Audit pass ```bash df -h /System/Volumes/Data # how bad is it du -sh /Users/jas/*/ 2>/dev/null | sort -rh # top-level home du -sh /Users/jas/.[^.]*/ 2>/dev/null | sort -rh # dotdirs du -sh /Applications/*.app 2>/dev/null | sort -rh # apps du -sh /Library/Developer/* 2>/dev/null | sort -rh # Xcode/simulators ``` `du -sh` without sudo undercounts some root-owned dirs (notably `/Library/Developer/CoreSimulator` — reports ~6 GB when actually 35 GB). Cross-check with `simctl runtime list`. ### When deleting frees NOTHING — APFS local snapshots If you `rm` gigabytes and `df` doesn't budge (or the volume is at literally **0 bytes** and even Claude Code's task-output writes fail with `ENOSPC`), the freed blocks are pinned by **APFS local Time Machine snapshots**. Deletes don't reclaim space until the snapshots release it. Fix without sudo: ```bash tmutil thinlocalsnapshots / 21474836480 4 # urgency 4 = aggressive; frees ~target bytes tmutil listlocalsnapshots / # confirm they're gone ``` (`tmutil deletelocalsnapshots /` may silently need sudo and appear to no-op; `thinlocalsnapshots ... 4` worked unprivileged 2026-06-15.) When the disk is so full Bash can't even capture output, redirect to a file and Read it: `cmd > /tmp/probe.txt 2>&1` then Read the file. After thinning, THEN clear the buckets below — and check `/System/Volumes/Data` (not `/`, the sealed system snapshot) for the real usage. ### Reusable cleanup command Fleet Macs can install [`ac-disk-clean.sh`](ac-disk-clean.sh) as a report-first cleanup utility: ```bash ac-disk-clean # inventory only ac-disk-clean --apply # known regenerable caches ac-disk-clean --apply --thin-snapshots # opt-in APFS snapshot thinning ``` `--install` places it in `~/.local/bin` and enables a weekly user LaunchAgent. Running applications keep ownership of their caches; repositories, downloads, models, agent state, and CoreSimulator runtimes are always report-only. ### Safe regenerable buckets Always clear first — fully recover with no judgment call: - `**/.build` — Swift Package Manager build caches (menuband, menubar-swift, wave-wizard typically 200 MB–1.5 GB each) - `~/.cache/mu` — mu email index. On a Mac this is now always a vestige (mail moved to jasellite), and an empty one is actively harmful: `mu find` against it succeeds with zero hits, so a search reports "no mail" instead of "wrong machine". Delete it on sight — see "Mail lives on jasellite" below - `~/.cache/puppeteer` — Chromium download cache (~325 MB) - `~/Library/Caches/Google` — Chrome cache (regrows continuously) - `~/Library/Caches/com.spotify.client` — Spotify offline cache - `~/Library/Caches/pip` — pip download cache - `~/Library/Messages/Caches` — iMessage media cache (~1 GB, regenerates from iCloud) - `npm cache clean --force` — npm content-addressed cache - `~/.cache/huggingface/hub/models--*` — model weights, redownloadable but check first: gemma-4-e2b is the ACTIVE local MLX model (keep); sdxl-turbo was a 13 GB dormant experiment (deleted 2026-06-11) - `brew cleanup --prune=all` — Homebrew download cache (~200 MB) ### Slab session recordings — trim by age `~/.local/share/slab/sessions/` accumulates ambient `.wav` + jsonl files per session. Keep last 7 days; delete the rest: ```bash find ~/.local/share/slab/sessions -maxdepth 1 -type f -mtime +7 -delete ``` ### CoreSimulator runtimes (the silent hog) iOS / xrOS / watchOS simulator runtimes live under `/Library/Developer/CoreSimulator/Volumes/`. Each runtime is ~7–8 GB. Remove ones you don't actively dev for: ```bash # Run simctl from Xcode.app directly — xcode-select may point at # CommandLineTools, where simctl doesn't exist SIMCTL=/Applications/Xcode.app/Contents/Developer/usr/bin/simctl $SIMCTL runtime list # find the runtime ID $SIMCTL runtime delete # async — status "Deleting" $SIMCTL delete unavailable # also clears orphan runtimes ``` Wait for "Deleting" to clear before re-checking disk: ```bash until ! $SIMCTL runtime list | grep -q "Deleting"; do sleep 5; done ``` ### Shelf bulk `~/Documents/Shelf/` regularly grows past 15 GB. Look specifically for: - `-DISTROKID/` of any track that already shipped (verify in [[project_pop_releases_tracking]]) - `-youtube/` of any video that already uploaded - `*-stems-*.zip` next to an identical `*-stems-*/` folder (zip is a duplicate after the stems have been sent) - `desktop-cleanup-YYYY-MM-DD/` from prior cleanup passes - `gens/` — image-gen cache, regenerable but slow + costs $ ### What NOT to blindly delete These look like caches but encode hard-to-rebuild state: - `~/.codex` — Codex CLI conversation history, not a cache - `~/.insightface` — face recognition model weights (used by jeffrey-platter face-match) - `~/.brainglobe` — brain atlas data (used by Carlos demos) - `~/.whisper-models` — whisper model weights (redownloadable but each model is slow) - `~/.mail-all/` — **no longer on the Macs.** The mail archive and its mu index live on jasellite now; see "Mail lives on jasellite" below. If you find a `~/.mail-all` on a Mac it is a pre-2026-07 leftover, not the source of truth — confirm against jasellite, then it is safe to reclaim (~27 GB) - `~/Library/Containers/com.apple.MobileSMS/` — iMessage attachments + conversation history - `~/Pictures/Photos Library.photoslibrary` — Apple Photos - `~/.ac-instagram-profile` / `~/.distrokid-profile` — Chromium profiles for IG / DK automation; deleting logs you out - `~/Developer/fuser-*` — fuser client git worktrees (~3.3 GB each, ~23 GB total as of 2026-06-11); may hold uncommitted client work — ask @jeffrey before pruning even merged-looking ones ### Gotcha — the auto-classifier blocks batched rm Claude Code's safety classifier will refuse a single shell command that contains multiple `rm -rf` calls on pre-existing dirs, even with prior user approval. Work around by running **one `rm` per Bash invocation** with the target path explicit in the description. Don't try to bypass — just split the commands. --- ## Mail lives on jasellite **No Mac holds mail anymore.** The maildir (`~/.mail-all`, ~30 GB across five accounts), the mu index, and the msmtp send credentials all live on **jasellite** — the DO box. A systemd user unit there (`mail-sync.service`) runs mbsync + `mu index` on a timer, so the archive stays current without a laptop being awake. ### Don't look locally first The failure this causes is quiet, which is what makes it worth a section. `mu find` against an absent or empty local index does not error — it returns zero hits. So a mail search on a Mac reports **"your inbox is empty"** when the truth is "you searched the wrong machine." If mail ever comes back empty, suspect the machine before you suspect the mailbox. ### How the MCP reaches it `ants/mail-mcp/server.mjs` runs `mu` / `mbsync` / `msmtp` on jasellite over ssh (`AC_MAIL_HOST`, default `jas@24.144.92.66`); only argv and stdin cross the wire. Set `AC_MAIL_HOST=""` to force local exec — that's how jasellite runs the server against its own disk. There is also an HTTP daemon on jasellite (`--http 7765`, bound to the **tailnet** address `100.72.36.78`, never the public interface, bearer-authed via `AC_MAIL_TOKEN`). It is faster, but ssh is the default on purpose: the daemon's token would have to be copied to every laptop to be useful, and that widens who can read the mail. Ssh keys are already per-machine and already scoped to @jeffrey. Use the daemon from a host that already has the token; reach for ssh everywhere else. **Never commit `AC_MAIL_TOKEN`** — this repo is public. It lives in `~/.config/ac-mail/env` on jasellite. ## sticky.mjs See `README.md` — `node toolchain/macos/sticky.mjs ` drops it into a translucent macOS Stickies note. ## chrome-shot.mjs See `README.md` — safe headless Chrome screenshots that actually exit + clean their orphans.