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 bundledist/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.