Case Calendar is a Python CLI. You’ll need:
- Python 3.13 or newer.
- uv for
dependency management.
pip install uvworks in a pinch, butuvitself has its own installer that’s faster on most systems. - A CourtListener API token — sign up for a free account at courtlistener.com and copy the token from the user-profile page.
- An LLM — either a hosted API key or a local model:
- A hosted API key for one of: Anthropic, Google (Gemini), or
OpenAI. Pick whichever you already use. The extractor pipeline uses
the cheap / small-model tier of each provider; expect cents per case
per day on a busy docket. The recommended default is a split:
Gemini (
gemini-3.1-flash-lite) for extraction and Anthropic (claude-sonnet-4-6) for case summaries — and zero-config picks this for you when you provide keys for both. For why the default splits the two tracks across providers, see Architecture → Why the default is a split. - A local model via Ollama — no API key, no
per-token cost, and docket text never leaves your machine. The
trade-offs are real: it needs a GPU (16 GB of VRAM for the recommended
gpt-oss:20b), and while local extraction is benchmark-competitive, no local model is good enough for production case summaries — so the recommended setup is a hybrid (local extraction, hosted summaries). See Local LLMs for hardware requirements, model choices, and the provenance caveats that matter for adversary-nation cases.
- A hosted API key for one of: Anthropic, Google (Gemini), or
OpenAI. Pick whichever you already use. The extractor pipeline uses
the cheap / small-model tier of each provider; expect cents per case
per day on a busy docket. The recommended default is a split:
Gemini (
Install Case Calendar
git clone https://github.com/seanthegeek/case-calendar
cd case-calendar
uv sync
That’s the whole installation. uv sync reads pyproject.toml and installs
everything into a project-local virtual environment. Prefix every command
with uv run (e.g. uv run case-calendar sync) and uv handles the activation
for you.
Configure secrets
Case Calendar reads secrets from a .env file in the project root:
cp .env.example .env
Open .env and fill in:
COURTLISTENER_TOKEN=your_token_here
# Provide Gemini + Anthropic for the recommended split (Gemini extraction,
# Anthropic summaries); one key alone also works for both tracks.
GEMINI_API_KEY=...
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-... # optional third provider
CASE_CALENDAR_WEBHOOK_SECRET=... # only needed for `case-calendar serve`
You can set keys for one provider or for all three. The tool auto-detects
which provider to use from whichever *_API_KEY is set, and the priority is
per track: the extraction track prefers gemini > anthropic > openai,
and the case-summary track prefers anthropic > gemini > openai. So a fresh
operator who provisions all three keys without setting any LLM_* variable
lands on the project’s recommended split — Gemini for extraction, Anthropic
for summaries (see
Architecture → Why the default is a split).
If you provide only one provider’s key, both tracks use that provider.
To force one provider for BOTH tracks, set LLM_PROVIDER=anthropic (or
gemini / openai) — it’s the global default that overrides the per-track
auto-detect on both sides.
To pin each track independently — keeping the default split, or building a different one — use the per-track override env vars:
LLM_EXTRACTION_PROVIDERbeatsLLM_PROVIDERfor extraction + verify + dedupe calls.LLM_SUMMARY_PROVIDERbeatsLLM_PROVIDERfor the case-summary track.
Either or both can be set with or without LLM_PROVIDER — when an
override is set, that track uses it; otherwise the track falls back to
LLM_PROVIDER or, last, the per-track key auto-detect.
⚠️ The
.envfile should never be committed to source control — the repository’s.gitignorealready lists it.
Highly recommended: local OCR tools
CourtListener stores RECAP PDFs unedited — it does not re-OCR documents
contributed to RECAP. The plain_text CourtListener returns is whatever the
uploader’s PDF carried natively, which on a non-trivial fraction of court
PDFs is either:
- Empty — an image-only scan from a photocopier, no embedded text.
- Garbled — custom font subsets with no
/ToUnicodemap produce multi-KB strings ofÿ/ glyph-index tokens that the summary LLM can’t read.
Installing the local OCR fallback lets Case Calendar re-process those PDFs itself, so the AI summary pipeline sees usable text instead of an explicit “insufficient documents” refusal:
# Debian / Ubuntu
sudo apt install poppler-utils tesseract-ocr
# RHEL / CentOS / Rocky (tesseract lives in EPEL)
sudo dnf install epel-release
sudo dnf install poppler-utils tesseract
# macOS
brew install poppler tesseract
Without these, the tool still works — it just skips un-OCR’d or garbled PDFs and retries on each sync (no cache poisoning). But on those dockets the AI summary will fall back to:
Documents available for this docket are insufficient to generate a reliable summary.
Install them. The extra disk footprint is small and the OCR runs only when the primary text extraction has already failed.
Optional: calendar push backends
By default Case Calendar writes an ICS file you can subscribe to. If you’d rather have events show up directly in Google Calendar or Microsoft 365 / Outlook, see the calendar backends page for the one-time OAuth setup. Both are opt-in and require no per-command flag once authorized.
Verify it works
Add at least one case to config.yaml (see configuration)
and run:
uv run case-calendar sync
A successful first sync prints one line per case, e.g.:
[us-v-wang] dockets_skipped=0 entries_seen=42 processed=11 actions=8
[cybercrime] wrote 14 events -> out/cybercrime.ics
The ICS file is now ready to subscribe to from any calendar app. See calendars for what to do with it.
Next steps
- Configuration — what to put in
config.yaml. - CLI reference — every subcommand and flag.
- Real-time webhooks — replace polling with push.