Monorepo for Aesthetic.Computer aesthetic.computer
core toolchain macos SCORE.md
12 kB

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/<project>/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.
  • <project>-DISTROKID/ — released master.wav + 3000px cover.jpg + encoded mp3 for tracks that shipped (e.g. hellsine-DISTROKID/).
  • <project>-ultimate/ and .<project>-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:

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 <project>-DISTROKID/, image refs into gens/<project>/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:

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 #

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:

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 as a report-first cleanup utility:

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:

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:

# 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 <UUID>       # async — status "Deleting"
$SIMCTL delete unavailable          # also clears orphan runtimes

Wait for "Deleting" to clear before re-checking disk:

until ! $SIMCTL runtime list | grep -q "Deleting"; do sleep 5; done

Shelf bulk #

~/Documents/Shelf/ regularly grows past 15 GB. Look specifically for:

  • <project>-DISTROKID/ of any track that already shipped (verify in [[project_pop_releases_tracking]])
  • <project>-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 <thing> drops it into a translucent macOS Stickies note.

chrome-shot.mjs #

See README.md — safe headless Chrome screenshots that actually exit + clean their orphans.