jonaprieto/granpa

Book several Gran Pared climbing slots from the shell, or ask Claude

★ 0Forks 0PythonGitHub ↗Compare
bogotabookingboulderingclaude-codeclaude-skillcliclimbingcolombiaoauthpkcepythonstdlibsupabase

README

granpa

python dependencies tests style claude code license Homebrew

Book several Gran Pared climbing slots from the shell in one command, or just ask Claude.

$ granpa slots 10-01
2026-10-01
  06:20  5 left
  14:00  7 left
  ...
$ granpa book --dry-run 10-01@14 10-01@23
2026-10-01 14:00  open (7 left)
2026-10-01 23:00  FAILED: no such slot
$ granpa book 10-01@14 10-03@18:20
2026-10-01 14:00  booked
2026-10-03 18:20  booked

Install

With Homebrew:

brew tap jonaprieto/granpa https://github.com/jonaprieto/granpa
brew trust --formula jonaprieto/granpa/granpa   # third-party taps need it
brew install granpa

Or use the single file, standard library only, Python 3.10 or newer.

git clone https://github.com/jonaprieto/granpa ~/granpa
ln -s ~/granpa/granpa.py ~/.local/bin/granpa

Any directory on your PATH works in place of ~/.local/bin.

Log in

Sign-in goes through Google, in two steps:

granpa login          # opens the Google sign-in page
granpa login '<URL>'  # paste the URL you land on

After signing in, the browser lands on https://app.granpared.com/users?code=.... Pass that URL, or just the code, to the second command. The session is stored in ~/.config/granpa/session.json (mode 600) and refreshes itself, so you only do this once.

Usage

granpa slots [DATE...]            # default: today
granpa book SLOT...
granpa book --dry-run SLOT...     # check, book nothing
Input Format Example
DATE MM-DD, YYYY-MM-DD 10-01
SLOT MM-DD@HH, MM-DD@HH:MM 10-01@18:20
SLOT YYYY-MM-DDTHH:MM 2026-10-01T18:20

Times are Bogota time. Slots last 20 minutes and start at :00, :20 or :40. A month and day earlier than today means next year. Every slot is validated before anything is booked, duplicates are booked once, and one failed slot does not stop the rest.

Exit codes: 0 all ok, 1 a slot failed or an auth or network error, 2 bad input.

Claude Code skill

The repo ships a skill so Claude can book for you from any session ("book me Thursday at 2pm", "what's open on Friday?"):

ln -s ~/granpa/skill ~/.claude/skills/granpa

Claude resolves the dates, books without asking unless the request is ambiguous, and reports each slot. If you are logged out it runs the first login step and asks you to paste back the URL; it never signs in to Google for you. Inside the Claude Code sandbox, Supabase responses can get truncated, so Claude runs granpa outside it.

How it works

The web app sits behind a bot challenge, so granpa calls the Supabase backend at auth.granpared.com directly:

  • Sign-in: Google OAuth with PKCE (/auth/v1/authorize, /auth/v1/token).
  • Availability: rpc/get_availability.
  • Booking: your id from profiles, then rpc/create_reservations_batch once per slot.

The embedded sb_publishable_... key is the public key the web app ships to every browser. Your own session is the only secret, and it stays in ~/.config/granpa.

Development

python3 -m unittest -v    # tests
ruff format && ruff check # 70-column style

Limitations

  • No cancel command yet; cancel on the website.
  • Books for yourself only, not dependents.
  • --dry-run does not know about slots you already booked.

License

MIT

Contributors

jonaprieto

Issues