Monorepo for Aesthetic.Computer aesthetic.computer
core macpal
4 folders · 12 files

README.md

MacPal #

A friendly little character who lives in the corner of your screen.

This is the first, deliberately-tiny MacPal: star — a star who floats in a corner, blinks and leans on his own, springs when you hover, squashes when you press, and rides along when you drag him (snapping to the nearest corner with a little clink on release). Click him and he escapes — collapses into a micro chip tucked into his corner with his name beside him; click again and he tinks back to full size. His corner, color, and collapsed state persist across launches.

It is the seed of a distributable MacPal (an eventual Mac App Store build).

What he can do #

  • Right-click menu — About MacPal, Gold Star / Silver Star (live toggle, checkmarked), TrackDrum, Quit MacPal. TrackDrum ships as a signed nested app and opens directly from the star.
  • Gold or silver — a full silver pose set ships alongside the gold one; the choice is remembered and even the About masthead follows it.
  • About window — modeled on the Menu Band About panel: star masthead, "MacPal", "a friendly little helper", and the dedication "This MacPal was made for Fía and is maintained by @jeffrey" (the í wears an acute accent and the live system-accent color), version, and copyright.
  • Menu Band compatibility — MacPal creates/polls ~/.local/share/desktop-badge/note, the existing signal file Menu Band writes <seq> <noteName> to when it plays. On each note the star opens his mouth and floats a pitch-colored ♪ — no Menu Band changes needed, they ship independently. New signal kinds can land in the same dir for future compatibility.

Build & run (local dev) #

./build.sh            # → build/MacPal.app (ad-hoc signed)
./build.sh --install  # build, copy to /Applications, and launch

On first launch MacPal adds itself to Login Items (via SMAppService) so the pal is always there. Remove it any time in System Settings → General → Login Items — it won't re-add itself.

Package to hand to someone (AirDrop / email) #

./package.sh              # sign (Developer ID) + notarize + staple
SKIP_NOTARIZE=1 ./package.sh   # signed only, faster (recipient right-click→Open)

Produces a notarized, stapled app that opens with no Gatekeeper block:

  • dist/MacPal.app — signed bundle
  • dist/MacPal.zip — ~900 KB, for email/download
  • ~/Desktop/MacPal.app — loose copy to AirDrop

The recipient just drags it to Applications and opens it (one normal "downloaded from the internet, are you sure?" prompt the first time). Notary credentials are read from aesthetic-computer-vault/apple/app-specific-password.env; signing uses the Developer ID Application: Jeffrey Scudder (FB5948YR3S) cert.

Icon #

make-icon.sh renders Resources/AppIcon.icns (gold star on a sky-blue squircle) from star-glyph.svg via rsvg-convert + iconutil. Re-run it after changing the star art. package.sh regenerates it automatically if missing.

Profiles & plugins #

One binary, two lives, chosen by launch profile:

  • (no args) → the star, for Fía, with the affirmations plugin (a caption @jeffrey can change remotely — see below). This is the App-Store build.
  • --profile fuser --name panda --corner TL [--repo …] → the fuser badge for the fleet (neo / panda / chicken / blueberry): git status, mission tasks, ⚡OVERTIME, and a live terminal pane, via the fuser plugin.

Add --loopboy <machine> to either profile to enter machine-addressed Loopboy mode. The target resolves from the canonical private fleet registry ($FLEET_MACHINES, otherwise ~/aesthetic-computer-vault/machines.json) using its registry key, hostname, tailnet name, or status key. The right-click Loopboy-machine submenu switches among registered identities. Unknown and offline targets remain visible as explicit states.

Loopboy mode adds a thin host heartbeat across the top of the Macpal. Ordinary Macpals never construct the heartbeat plugin and their presentation is unchanged. V1 is read-only: it observes the resolved host's cached Slab ledger and aggregates prompt-rock freshness/activity, plus an optional mission record at ~/.config/slab/loopboy-machines/<machine>.json. It does not manage or control the target machine.

Loopboy machine-heartbeat state contract #

state meaning
active fresh working/rendering/awaiting rocks; a traveling pulse, not percent-complete
healthy host/mission freshness is current without active work
quiet online or recently seen, gradually decaying
stalled mission explicitly blocked, or active work stopped heartbeating
offline resolved host ledger is absent/stale or registry liveness is offline
unknown address does not resolve in the canonical fleet registry

The bar shows a filled percentage only when the mission snapshot explicitly sets {"bounded":true,"progress":0…1}. Unbounded work never implies a completion percentage. Prompt rocks are one input to whole-host state, not the identity of the heartbeat itself. Panda/Iris is the first use; the resolver and aggregation contract are machine-generic.

Running mission todos have a green stripe and a subtle flowing green tint behind steady lettering. Reduce Motion keeps the stripe and tint still. Other rows have no panel background. An item's ISO startedAt drives a seconds-resolution elapsed timer; finishedAt freezes it when supplied. Heartbeats (at / updatedAt) never reset that timer. Rows without a start retain their activity age. The lettering follows the system appearance with a small contrasting halo for desktop readability. The provider mark sits to the avatar’s left, opposite the resident agent’s contact disc; mission.json replaces the retired nine-point tasks row.

Iris fleet cards can supply lanes instead of a flat checklist. Each section has id, host, label, state, items, and queued (id/title pairs). MacPal keeps their order, draws a colored background for each lane, and shows its current task and up to two queued tasks. Empty and waiting lanes remain visible. Both Panda and Chicken receive all four sections. Blocked tasks retain elapsed time but have an amber warning instead of green activity. Older flat cards keep their existing status sorting. Run the built binary with --test-mission-lanes or --test-mission-timing to check these contracts without opening a window.

Named lanes also carry name, color (CSS #rrggbb), and tag, e.g. {"id":"blue","name":"Lane Blue","color":"#0000ff","tag":"lane:blue"}. Each lane gets a flat tinted background and its registry emoji, with square edges and no outline or left rail. The compact board hides the mission heading and Git bar, leaving clearance beneath the animated machine name. Lane backgrounds touch, with 12-point task titles and separate 11-point lines for each subitem. Long lines scroll independently instead of wrapping. Native-resolution text moves in whole display pixels, pauses at the start, and clips within its row. Clock updates preserve the scroll; Reduce Motion keeps text stationary. Lane headings have six points of space below them, with the bundled Panda or Chicken portrait beside the host name.

Items may supply subitems (strings) and an ISO deadlineAt alongside startedAt. A stationary clock and thin bar show elapsed/allowed time and time remaining. The bar measures time used, not work completed; it turns amber at 80% and red at the deadline. Finished tasks freeze at finishedAt. Missing deadlines show elapsed time only. Iris projects the enforced task/sweep deadline, capped by the applicable authorization expiry, without changing any limit. Text colors adapt to maintain at least 4.5:1 contrast against each card in either appearance. The host sits beside the permanent name. Iris owns one flat registry; MacPal never assigns colors by machine or array order. Initial identities are Blue (Panda general), Teal (Panda light), Coral (Chicken general), and Snow (Chicken light), with 🔵 / 🌊 / 🪸 / ❄️ respectively. Retired IDs remain reserved and new slots need explicit IDs. Cards keep all rows from a lane together and leave clicks available to the desktop beneath them.

The avatar (float, poses, name wiggle, hover, drag-to-corner, collapse, Menu Band sing) lives in PalCore.swift and is shared. Everything that stacks beneath the name is a PalPlugin.

Affirmations (remote status under the star) #

The star polls GET /api/macpal-status?to=fia every ~45s and shows the message under herself; a new one slides in with a hop and a soft chime, and the last is cached so it persists offline. @jeffrey pushes one (admin-only) with:

node macpal/affirm.mjs "proud of you 💛"        # → to=fia, production
npm run affirm "great work today ✨" -- --to fia
node macpal/affirm.mjs "testing" --local         # → https://localhost:8888
node macpal/affirm.mjs --clear                   # blank the caption

A playlist rotates through several messages server-side — the GET computes the current entry from elapsed time, so it keeps turning with every machine asleep (min 30s, the star polls every ~45s):

node macpal/affirm.mjs --playlist "it's good to be calm!" "i love you!" --every 60
node macpal/affirm.mjs "one message"             # a plain push replaces the playlist

(affirm-loop.mjs is the client-side ancestor of the same trick — it pushes singles on a timer from your machine and dies when it sleeps; prefer --playlist.)

Auth uses ~/.ac-token (run node tezos/ac-login.mjs once). The endpoint is system/netlify/functions/macpal-status.mjs (Redis-backed, served by lith).

Layout #

macpal/
├── build.sh                  swiftc Sources/*.swift → .app, sign, optional install
├── package.sh                Developer ID sign + notarize → AirDrop-ready
├── make-icon.sh              render Resources/AppIcon.icns from the star
├── affirm.mjs                CLI: push an affirmation to a star (admin-only)
├── Sources/
│   ├── main.swift            entry: parse profile → PalConfig → attach plugins
│   ├── PalCore.swift         shared avatar + PalPlugin protocol + About
│   ├── AffirmationsPlugin.swift   Fía: remote affirmation caption
│   └── FuserPlugin.swift     fleet: git · Asana · OVERTIME · terminal pane
├── Resources/
│   ├── Info.plist            bundle metadata (LSUIElement, min macOS 13)
│   ├── star-glyph*.svg       gold poses: home · blink · lean · sing  (bundled)
│   ├── star-silver*.svg      silver poses                            (bundled)
│   └── {neo,panda,chicken}-glyph*.svg   fleet glyphs (installer source, not bundled)
└── fuser/                    symlinks into the vault (client-sensitive deploy bits)
    ├── install.sh, deploy.sh        build the badge from Sources, flip the fleet
    └── badge-asana-sync.sh, …       (kept in aesthetic-computer-vault)

Per-user star state (corner, color, collapsed flag, last affirmation) lives in ~/Library/Application Support/MacPal/; the fuser badge uses ~/.local/share/desktop-badge/. Both share the Menu Band note signal at ~/.local/share/desktop-badge/note.

Swap the avatar #

Replace the star-glyph*.svg / star-silver*.svg files (128×128 viewBox, transparent background — the app draws the accent drop-shadow) and rebuild. The loader cycles through the home/-2/-3 poses for the active color and uses -sing for Menu Band note pings; one SVG is enough, the rest give it life.

Lineage #

MacPal began as the charm carved out of the fleet desktop-badge. The two have now merged the other way: the badge's features (git status, Asana, OVERTIME, the terminal pane) returned as the FuserPlugin, the shared avatar became PalCore, and the star gained the AffirmationsPlugin. The old aesthetic-computer-vault/fuser/bin/desktop-badge/desktop-badge.swift is superseded by macpal --profile fuser.