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

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.

Cleaner #

"Call the cleaner" means run the local fleet Mac's canonical safe disk cleanup utility, cleaner.sh. Agents should resolve that phrase to cleaner --apply; use report-only cleaner when cleanup was not explicitly requested. It protects repositories, project dependencies, Downloads, models, agent state, and active-application caches.

cleaner                            # inventory only
cleaner --audit                    # inventory + dust tree of ~, ~/Library, the checkout
cleaner --apply                    # known regenerable caches, then mac-cleanup-py's curated modules
cleaner --apply --remote-backed    # verify Spaces/CDN, then prune backed AC media
cleaner --apply --thin-snapshots   # opt-in APFS snapshot thinning
cleaner --apply --messages-cache   # iMessage attachments older than a year → Trash (iCloud-gated)
cleaner --uninstall LibreOffice    # app + leftovers via Pearcleaner; repeatable

--install places it in ~/.local/bin, enables a weekly user LaunchAgent, and brew-installs the three open-source helpers it leans on: dust (the audit tree), mac-cleanup-py (a curated module set — tool caches only, no browser or system caches, no logs, no backups — written to ~/.mac_cleanup_py so it never opens its interactive picker), and Pearcleaner (app removal that also takes Application Support, Caches, Preferences, and launch agents). Each helper is optional; the cleaner degrades to its own bash lanes. Never automate Pearcleaner's remove-orphaned: its orphan list includes extensions of installed apps. The Messages lane only runs when Messages in iCloud is on (attachments redownload on view), Messages is closed, and the terminal has Full Disk Access to read chat.db; it keeps the last year by default. The legacy ac-disk-clean name remains a compatibility alias. Running applications keep ownership of their caches; repositories, downloads, models, agent state, and CoreSimulator runtimes are always report-only. The default report also maps AC build trees, ignored generations, local asset mirrors, Whistlegraph downloads, Pop workspaces, worktrees, vault Git/storage, Final Cut/Xcode data, Docker, and local model stores. --remote-backed is interactive/explicit only: each surface is kept unless its own remote verifier passes, and it is never included in the weekly LaunchAgent.

Neo headroom #

Use Neo cleanup for the verified worktree and local Photos procedures, recovery records, and the host's daily schedule.

The AC performance guard checks disk headroom every 30 seconds and gates new shell-launched Git worktrees and Swift builds. Install it on every fleet Mac; deployment verifies the PATH shims as well as launchd.

Keep at least 20 GiB free on Neo for swap and system updates. Its hourly disk-space-watch checks only filesystem free space and warns at most once per day; it never deletes data. Install it with bash toolchain/macos/disk-space-watch.sh --install. Its current reading is ~/.local/share/slab/disk-space/latest.json.

Neo's Cleaner runs daily at 03:15 at background priority. Inaccessible caches are skipped rather than aborting the remaining cleanup. Keep voice evaluation and its intermediate audio on Poorslice; keep video/browser rendering and its bulk outputs on Panda. Neo holds the controlling sessions and source edits. Copy back only the results needed locally. Preserve source, transcripts, models, and personal media; move generated bulk only after verifying its copy.

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.

Fleet Chrome ownership #

The default browser on every fleet Mac is ordinary Chrome using the host's persistent signed-in Profile 1. Open it through fleet-browser open (the installed form of fleet_browser.py).

CDP is an explicit capability, not a second browser. After the user enables remote debugging in ordinary Chrome, a tool that needs CDP must acquire the shared fleet-browser access lease, use Chrome's DevToolsActivePort, touch its owner while active, and release it when finished. Never invent a /tmp/*-chrome profile or another debugging port. The manager refuses competing debug instances and the launchd reaper expires abandoned access.

Reuse the resident Puppet/CDP connection across client requests. A lease alone does not share a WebSocket, and Chrome asks again for each new connection. Avoid restarting the browser core when only an MCP client needs refreshing. Ordinary Chrome also needs a profile window open to show its consent dialog; a windowless process can reject debugging with HTTP 403.

The operator-approved fleet consent handler is Captutor Modalpolice: node slab/captutor/bin/install-nag-fighter.mjs --allow-remote-debugging installs its login watcher and explicit persistent policy. See slab/captutor/README.md. It handles only recognized native Chrome dialogs, never page content or unknown prompts. Verify its live status and handled events; installation alone is not proof. A locked desktop still needs unlocking. Do not turn off Chrome's consent checks.