GoForge — Build | Test | Ship | Collaborate GoForge — Build | Test | Ship | Collaborate
v0.4.3

Anvil

Meeting times across all your calendars

$ go install goforge.dev/anvil/cmd/anvil@latest

Meeting times, hammered into shape (module goforge.dev/anvil). Anvil finds slots that work across every calendar each person lives by — personal, work, startup, volunteer — for any mix of required and optional attendees, with drive-time padding for in-person meetings.

v0.4.3 — Free scheduling and typed calendar transport

Anvil is fully free and MIT-licensed with unlimited scheduling links. The current release uses the replayability-safe typed Resty rewrite for calendar feeds and explicit response-body ownership, while retaining demo mode, the agenda PWA, CalDAV and Google Calendar integration, Meet links, and deployable self-hosting. The v0.4.3 patch consumes the released Go+ stdlib v0.207.0.

Existing schedulers assume one person ≈ one calendar, maybe two. The real shape of the problem is people × calendars: you have four calendars, your co-founder has three, and “find a time for Mike and Melissa and Stan” means none of the ten may conflict. Anvil’s model is exactly that — a person is a name plus the union of all their busy time, and the finder never needs to know how many calendars fed it.

Install

go install goforge.dev/anvil/cmd/anvil@latest

Use it

anvil find -d 45m -from 2026-06-15 -to 2026-06-19 -tz America/New_York \
    -who mike=work.ics,personal.ics \
    -who melissa=https://calendar.google.com/calendar/ical/…/basic.ics \
    -opt stan=stan.ics -opt david=david.ics \
    -travel 30m
Wed Jun 17  13:30–14:15 EDT  4/4
Thu Jun 18  10:30–11:15 EDT  4/4
Thu Jun 18  11:00–11:45 EDT  3/4  missing: stan

Every -who is required; every -opt is scored — slots with more optional attendees rank first, earliest wins ties. -travel 30m demands a clear half hour on both sides of the meeting (drive time) without widening the meeting itself. Calendars are iCal files or URLs; every major provider (Google, Fastmail, iCloud, Proton) exports a private iCal address — no OAuth needed.

Flags: -d duration, -from/-to date window, -hours 09:00-17:00, -days mon,tue,… (default weekdays), -step start granularity, -n max results.

Self-hostable Calendly-for-many-people. One JSON config wires calendars (iCal URLs, CalDAV, Google) to people, people to links, and each link to the calendar the booked invite lands in. Hand out https://your.host/l/intro and J-random person picks from times that clear every calendar of every required attendee:

"links": [
  {"slug": "intro", "title": "Intro with Mike & Melissa", "duration_m": 45,
   "required": ["mike", "melissa"], "optional": ["stan"],
   "google_meet": true, "book_into": "melissa"},
  {"slug": "onsite", "title": "Coffee with Mike", "duration_m": 60,
   "required": ["mike"], "in_person": true, "travel_m": 30,
   "address": "300 Webster St, Oakland", "book_into": "mike-personal"}
]

Booking re-verifies against live calendars (409 if the slot just vanished), then creates the invitation with the right conferencing on it: a Google Meet link minted via the API, a static room (video_url), or an in-person address with drive-time padding on both sides. Invitations reach attendees through the target calendar (Google sendUpdates, CalDAV server scheduling).

Agenda: desktop, mobile, terminal

anvil serve also hosts an installable agenda app (PWA — add to a phone home screen or install from a desktop browser): everything coming across all configured calendars, each entry with its Join link (Meet, Zoom, Teams, Webex, Jitsi, Whereby auto-detected) or Directions button. Optional basic auth keeps the agenda private while scheduling links stay public. The same view lives in the terminal:

anvil agenda -cal mike=work.ics -cal mike=personal.ics -days 3

Setup helpers: anvil gcal-login (one-time OAuth, prints the refresh token), anvil caldav-calendars (lists collection URLs for the config).

Library

Six packages, nothing outside the Go standard library:

  • interval — half-open span algebra: Normalize, Union, Intersect, Subtract, Complement, Shrink.
  • schedule — the kernel. Find(Request) intersects required attendees’ free time with a working-hours mask, applies travel padding by shrinking free intervals (so a padded meeting fits iff the slot survives), and ranks candidates by optional attendance. Pure and deterministic: busy sets in, slots out.
  • ics — a small, honest iCalendar reader and writer: VEVENT, IANA TZID, all-day events, DURATION, EXDATE, attendees/organizer, and DAILY/WEEKLY recurrence; Encode emits RFC 5545 for invites and CalDAV PUTs.
  • caldav — minimal CalDAV client: discovery, calendar-query REPORT for busy time, and create-only event PUT (If-None-Match: *). Fastmail, iCloud, Nextcloud, Radicale.
  • gcal — minimal Google Calendar client: refresh-token OAuth, free/busy, event listing, insertion with Meet conference creation, and the loopback login flow.
  • agenda — merges occurrences across calendars and attaches the join URL or a directions link per item.
  • serve — the scheduling-link server and agenda app, built on all of the above.
cal, _ := ics.ParseIn(feed, tz)
mike := schedule.Merge("mike", cal.Busy(window), otherBusy)
slots, _ := schedule.Find(schedule.Request{
    Duration: 45 * time.Minute,
    Window:   window,
    Travel:   30 * time.Minute,
    Required: []schedule.Person{mike, melissa},
    Optional: []schedule.Person{stan, david},
})

Free and MIT-licensed

Every command, an unlimited number of scheduling links, all calendar sources, and the agenda PWA — free, with no tiers and nothing to activate. The source is MIT-licensed; self-host it on a $5 VPS and run as many links as you like.

Roadmap

  1. Library: interval algebra, scheduling kernel, iCal ingestion — v0.1.0
  2. CLI: anvil find over iCal files/URLs — v0.1.0
  3. CalDAV and Google Calendar API adapters (read and write — create the invite, not just find the slot) — v0.2.0
  4. anvil serve — self-hostable scheduling links with conferencing links on the invite and travel-time-aware in-person options — v0.2.0
  5. UI: at-a-glance agenda across all calendars, join links and directions one tap away (installable PWA + terminal) — v0.2.0

Next up: ATTENDEE free/busy via iTIP, recurring link round-robin, and native wrappers if the PWA ever feels insufficient.

Non-goals: Exchange, Outlook, being a calendar.