notefeed

Oct 27

  1. How freundebuch releases reach this feed

    Until today freundebuch had no releases: every merge to main updated the :main image, and if you ran it you got whatever was newest. Now there are versioned releases, and each one shows up here on its own. The first, 0.1.0, is the note just below this one.

    What happens on a release

    I push a tag such as v0.2.0. GitHub Actions runs the same pipeline as on main: lint, typecheck, the unit tests, the Playwright tests and a smoke test that starts the container and waits for it to report healthy, while it builds the image for amd64 and arm64. Only when all of that passes:

    1. The image gets its version: ghcr.io/marceltov/freundebuch:0.2.0, :0.2 and :latest. A pre-release like v0.2.0-rc.1 only gets its own tag, so latest never points at one.
    2. A GitHub release is made, with GitHub's generated list of the pull requests merged since the previous tag and a docker pull line.
    3. The same notes are posted here, tagged release and with the version, plus a link to the GitHub release.
    flowchart LR
      T[tag v0.2.0] --> C[tests and smoke test]
      T --> B[image build, amd64 + arm64]
      C --> I[image tags 0.2.0, 0.2, latest]
      B --> I
      I --> R[GitHub release]
      R --> N[note on this feed]
    

    For 0.1.0 the whole run took about 11 minutes from the tag to the note. A re-run doesn't post twice: it first asks notefeed whether a note with that version tag is already there.

    Pinning a version

    To upgrade only when you choose, pin a version in your compose.yaml:

    services:
      freundebuch:
        image: ghcr.io/marceltov/freundebuch:0.1
    

    :main stays as it is, for everyone who wants every merge.

    Good to know

    The first release has no earlier tag to count from, so its notes list every pull request since the start (26 of them). From 0.2.0 on, each release lists only what's new. The notes are PR titles, which are written for me more than for you; I'll post a note in my own words alongside the bigger releases, like the two earlier ones on this feed.

    The workflow is in .github/workflows/ci.yml. This feed has RSS at https://notefeed.me/r/freundebuch/feed.xml. If something about the release notes doesn't work for you, tell me.

  2. freundebuch: a personal CRM you talk to

    You meet a friend and can't remember whether her new job has started, what her kid is called, or that you promised to send her a contact. I wanted something for that moment that doesn't ask me to fill in forms. So I built freundebuch, a personal CRM for one household, where Claude does all the typing.

    How it works

    After a dinner I ramble about it to the Claude app, by voice or text. Claude files it through freundebuch's built-in MCP endpoint: who was there, what's new, what I promised, gift ideas, birthdays, who is related to whom. The full transcript is kept. Before I see someone again I ask "I'm seeing Anna tomorrow, what's new?" and get a briefing: the last meetings, what's going on, open promises, upcoming dates.

    MCP is the main interface. Every feature works there first. A REST API (with OpenAPI) and a German web app sit on the same domain layer. The web app is mostly for looking things up on my phone:

    • The orbit: me in the middle, friends on rings by how long it's been. Anyone overdue for their check-in interval drifts outward.
    • A page per person: what's going on, what's good to know, promises, dates, and the people around them.
    • Erzählen: a chat with Claude inside the app, using the same tools, for when I'm not in the Claude app.
    • Reminders once a day to a webhook such as ntfy: birthdays, promises, overdue friends.

    Shared, with private parts

    Two people in a household share one freundebuch. Anything said in confidence can be private to whoever recorded it, down to a single fact. Claude asks before filing when something sounds sensitive (health, money, relationship trouble). In the code, privacy is one function that every read goes through, so a private item can't leak through a count or a summary. Tests check that by seeding a household.

    What's in it so far

    I started on 23 October, and 44 PRs are merged as of today. Besides the core:

    • Groups with other names ("Kollegen" is "Arbeit").
    • Screenshots from social media: share a friend's Instagram or LinkedIn post and it lands on their page ("Urlaub in Lissabon · Instagram, 12. Oktober"). If Claude isn't sure who it is, or it looks sensitive, it waits for me to check.
    • A calendar feed of birthdays and dates that Google Calendar can subscribe to.
    • Roles and situations: "our landlady" is a relationship to us, not a fact. "Renovating the bathroom" is a situation that nudges me to ask about it later.

    What it isn't

    It isn't a contact book (no phone numbers or addresses) and it isn't multi-tenant: one instance per household, signed in through your own identity provider (authentik). It's one container with one SQLite file, and it's licensed AGPL-3.0.

    The code is on GitHub, and the docs explain self-hosting. This feed is where I'll post what changes. If you try it, I'd like to hear how the rambling feels.