Wenlan
Wenlan is a source-backed AI knowledge base and LLM wiki for AI work: organize documents and decisions into pages you can find, inspect, and review.
翻訳待ち
No updates detected yet. GitHub releases will appear here.
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Aggregate calendar availability from many accounts — Google, Outlook/M365, iCloud/Fastmail, plus device-bound work accounts behind Conditional Access — into a single private free/busy ICS feed you subscribe to for personal planning. Read-only, private, never shared externally.
Privacy by construction, with source attribution. AvailCal ingests only busy intervals —
start/end/statusplus a single owner-assigned one-word source label. Titles, descriptions, attendees and locations are stripped at the earliest point. The label becomes each event'sSUMMARY(andCATEGORIES) so you can still tell which calendar a block came from — a coarse tag likeWork,Perso,iCloud, never event content.
Cloud-reachable accounts ─(secret ICS URL / CalDAV)─┐
├─► Container Apps Job (cron, scale-to-zero)
Device-bound accounts ─(local agent → /raw JSON)─────┘ pull → normalize → merge → emit
▼
Blob: /raw/*.json + /merged/availability.ics + /raw/*.ics
▼
Apple Calendar / Thunderbird / Fantastical (ICS subscription, hourly)
A scheduled, scale-to-zero job (hourly cron, no always-on server) pulls every
source, normalizes all times to UTC, merges per-source busy blocks, and writes
one merged availability.ics to object storage. Your calendar clients subscribe
to that object. See docs/ARCHITECTURE.md.
It runs on Cloudflare Workers + Containers + R2 (primary — a Worker cron
drives the merge Container and serves the feed from R2) or an Azure Container
Apps Job + Blob (alternative). The same merge/ image runs on both.
Storage is pluggable. The same image writes to Cloudflare R2 (S3-compatible,
zero egress), Azure Blob, or a local directory — selected by env vars
(AVAILCAL_OUTPUT_DIR > R2 > Azure). For R2, see
infra/cloudflare/README.md.
The only bespoke code is the device-bound local reads (agents) and the
free/busy merge. Everything else reuses existing tools: recurring-ical-events
(recurrence), vdirsyncer (CalDAV), icalendar (parse/emit), native OS calendar
stores (device reads), OS schedulers (agents), and Container Apps (cloud timer).
| Path | What |
|---|---|
merge/ | The Python merge job (pull → normalize → merge → emit) + Dockerfile |
agents/windows/ | Outlook COM exporter + Scheduled Task installer |
agents/macos/ | EventKit exporter + launchd agent installer |
infra/ | Bicep for Storage, Key Vault, and the scheduled Container Apps Job |
.github/workflows/ | CI (lint/test/build) and OIDC deploy |
docs/ | Architecture, getting ICS URLs, subscribing, and the runbook |
sources.example.toml | The source registry (rawname → one-word label) |
az) with the Bicep extension.cd merge
pip install -e '.[dev]'
ruff check .
pytest -q
Copy sources.example.toml to sources.toml and map each raw input (ICS feed,
CalDAV account, device calendar name) to a single-word label. Labels must be
unique single tokens — the job validates this at startup and fails fast.
Follow docs/GET-ICS-URLS.md to obtain each account's
secret ICS URL (Google secret-iCal, Outlook published ICS) or set up CalDAV
(iCloud/Fastmail via vdirsyncer). Work accounts that block published ICS use
the device agents instead.
cd merge
AVAILCAL_SOURCES_TOML=../sources.toml \
AVAILCAL_ICS_FEEDS='GoogPersonal=https://…/basic.ics' \
AVAILCAL_OUTPUT_DIR=./out \
python -m availcal.main
# -> ./out/merged/availability.ics (and ./out/raw/<Label>.ics overlays)
Two supported targets — the same merge/ image runs on both:
Cloudflare (Workers + Containers + R2) — primary. A Worker runs the hourly cron, drives the merge Container, serves the feed from R2, and accepts agent uploads. See worker/README.md and infra/cloudflare/README.md.
wrangler r2 bucket create availcal # one-time
cd worker
npm ci
npm run typecheck # tsc --noEmit
npx wrangler types # validate wrangler.jsonc + bindings
# set secrets once (FEED_TOKEN, AGENT_TOKEN, RUN_TOKEN, AVAILCAL_R2_*, AVAILCAL_ICS_FEEDS):
wrangler secret put FEED_TOKEN # …repeat per secret (see worker/README.md)
npx wrangler deploy # builds+pushes the Container, binds R2, registers cron
# local dev: cp .dev.vars.example .dev.vars && npx wrangler dev
CI runs wrangler types + tsc on every PR; npx wrangler deploy runs from
.github/workflows/deploy-cloudflare.yml (manual/tag).
Azure (Container Apps Job) — alternative.
az acr login --name <ACR_NAME>
docker build -t <ACR_LOGIN_SERVER>/availcal:v1 ./merge
docker push <ACR_LOGIN_SERVER>/availcal:v1
./infra/deploy.sh availcal-rg eastus containerImage=<ACR_LOGIN_SERVER>/availcal:v1
Then load feed secrets into Key Vault / Workers Secrets and trigger a run — see docs/RUNBOOK.md.
This is the beginning of the README. Read the rest at the original. Read the full README ↗