bairn
A personal photo archive for households whose nursery or preschool uses Famly. Pulls images and videos from the parent-side feed to disk, with full EXIF and XMP metadata embedded into each file (educator name, kids tagged, post body, timestamp, timezone). Optional Immich upload for households that already self-host one.
Single static Go 1.25 binary. Spec-first typed clients (genqlient against the Famly GraphQL surface, oapi-codegen against the Immich OpenAPI spec). Five-state typestate asset lifecycle (Discovered → Downloaded → Saved → Uploaded? → Recorded) tracked in a JSON state file under $XDG_STATE_HOME, locked with flock, written atomically. Polite retry primitive on cenkalti/backoff/v5 that honors Retry-After and treats 4xx auth errors as permanent. Discovery toolkit (shape probe, traffic-capture playbook, GraphQL introspection) under discovery/, with vendor-specific outputs gitignored.
Highlights
- Embeds full Famly-side context into each saved JPEG (educator, kids tagged, post body, timestamp, timezone) so the archive is usable independently of the platform.
- Best-resolution downloads: rewrites the CDN URL’s size segment to the originally-reported width and height before fetching.
- Hand-rolled XMP packet writer that splices the APP1 segment into the JPEG bytes at the correct pre-SOS position (dsoprea’s library appends after SOS, where readers ignore it).
- Sink interface separates disk (always-on canonical) from Immich (env-gated, optional). New sinks slot in without touching the orchestration loop.
- Seven architecture decisions documented at
docs/decisions/0001-0007. - LLM-augmented CI via
gitlab.com/dunn.dev/pipeline/claude-drift-triagefor shape-signature drift triage. - MIT licensed.
The story of why this exists, the methodology bairn implements, and the wider point about vendor stance is in A Progressive Vendor Stance for the Agent-Native Future.