Google Calendar CLI in Rust — multi-profile auth, range queries, JSON/TSV/CSV/raw/conky output, ICS import, hybrid grid/agenda rendering.
Note
Binary is gcal. Crate / repo name is gcalpod. Derived from
rust-dd/google-calendar-cli
(Apache 2.0). Substantial modifications listed in
NOTICE.md.
- 🔐 Multi-profile OAuth —
~/.gcal/profiles/<name>/per account; share one OAuth client across profiles viagcal init --shared. - 📅 Range queries with natural-language input —
today,+7d,+2w, weekday names, RFC3339,YYYY-MM-DD. - 🧾 Six output formats —
table/json/tsv/csv/raw/conky. Auto-pretty JSON on tty, compact when piped. - 🪟 Hybrid renderer — week grid for short ranges on wide terminals, day-grouped agenda for long ranges or narrow terms.
- 📥 ICS import with
--dry-runand--skip-duplicates. - ⏰
gcal remind— exec a command N minutes before next event with{{summary}}/{{start}}/{{html_link}}interpolation. - 🧯 Failure-first errors — RTK-style one-line summary + recovery-metadata path to the full log.
- 🧭
--verbosefor newcomers / init agents. - 📜 Man page via
gcal --gen-man.
git clone git@github.com:podarok/gcalpod.git
cd gcalpod
cargo build --release && cargo install --path . --lockedThe binary lands at ~/.cargo/bin/gcal.
Important
gcal requires your own Google Cloud OAuth client. There is no
shared / built-in fallback — every user creates their own OAuth
project (~5 minutes, free for personal use).
gcal init # interactive wizard
gcal auth status # confirm authentication
gcal list # current week (table)Or, for multi-account workflows:
gcal init --shared # one OAuth client at ~/.gcal/secret.json
gcal auth login --profile work
gcal auth login --profile personal
gcal auth switch work
gcal list --from today --to +7dConfiguring your Google Cloud OAuth client manually
- Visit https://console.cloud.google.com/projectcreate and create a project.
- Enable Calendar API at https://console.cloud.google.com/apis/library/calendar-json.googleapis.com
- Set up the OAuth consent screen (External / Testing) — add your email as a test user.
- Create OAuth client ID (Application type: Desktop app) and download the JSON.
- Move it into place:
mkdir -p ~/.gcal mv ~/Downloads/client_secret_*.json ~/.gcal/secret.json
Detailed walkthrough: docs/custom_auth.md.
gcal quick parses natural-language date/time via Google's quick-add
endpoint, then optionally patches in fields the NL parser drops on the
floor:
gcal quick "team sync May 11 2026 3pm-4pm"
gcal quick "client visit May 11 2026 3pm-4pm" \
--location "Kyiv, Khreschatyk 1" \
--description "Bring access badge. Confirm 1h before."
gcal quick "design review tomorrow 2pm-3pm" --conference # attaches MeetFlags:
-l/--location <text>— set place (post-create patch).-d/--description <text>— set notes (post-create patch).-c/--conference— attach Google Meet (post-create patch).--transparency opaque|transparent— Free/Busy flag (post-create patch).opaque(default) blocks the time;transparentmarks the event Free so it does not clash with overlapping slots.--calendar <id>— target a non-primary calendar.
Location, description, and transparency share a single PATCH call when
multiple are supplied. For arbitrary field edits on an existing event use
gcal edit <id> --field key=value — supported keys: summary,
description, location, start, end, transparency.
gcal add <title> <date> accepts --transparency directly at insert time
(no patch round-trip). gcal import honours RFC 5545 TRANSP:TRANSPARENT
on ICS input.
| Command | Purpose |
|---|---|
gcal init [--shared] | Interactive setup wizard (per-profile or shared secret). |
gcal auth login | OAuth flow for a profile. Flags: --scopes, --no-browser, --reauth. |
gcal auth status [--all] [--check] | Per-profile state. --check pings live API. |
gcal auth switch <profile> | Change active profile in ~/.gcal/config.toml. |
gcal auth logout [--all] [--purge] | Remove cached token (and secret with --purge). |
gcal auth refresh | Force token refresh. |
gcal list [--from --to --calendar --format --style --lineart] | Range query. Hybrid grid/agenda by default. |
gcal agenda / gcal search <q> | Flat list / full-text search. |
gcal calendars list | List accessible calendars. |
gcal add / gcal quick <text> | Create events; --conference attaches Google Meet, --location/--description set place/notes. |
gcal edit <id> --field key=value | Mutate fields: summary, description, location, start, end. |
gcal delete <id> [-y] | Delete with confirmation gate. |
gcal import <path> [--dry-run] [--skip-duplicates] | Bulk-insert ICS / VCAL. |
gcal remind <mins> -- <cmd>... | Exec command N min before next event. |
gcal config get/set/unset/list/path | Manage ~/.gcal/config.toml. |
gcal --gen-man | Print man page (clap_mangen) to stdout. |
Add --verbose to any command for extra context — useful for new
users and AI init agents.
gcal list # table (default)
gcal list --format json | jq '.[] | select(.summary | test("standup"; "i"))'
gcal list --from today --to +30d --format tsv > planning.tsv
gcal list --format csv | column -t -s,
gcal list --format raw | jq '.[] | .recurrence' # full upstream Event JSON
gcal list --format conky # ${color cyan}…${color}Stable v1 schema for --format json|tsv|csv:
{
"id": "...",
"calendar_id": "primary",
"summary": "...",
"description": "...",
"start": "2026-05-04T12:00:00+03:00",
"end": "2026-05-04T13:00:00+03:00",
"all_day": false,
"status": "confirmed",
"creator": "...",
"attendees_count": 2,
"html_link": "...",
"updated": "..."
}Bumping any field name / removing one is a breaking change → major version.
gcal resolves OAuth credentials in this order:
GCAL_CLIENT_ID+GCAL_CLIENT_SECRETenv vars (optionalGCAL_PROJECT_ID).GCAL_SECRET_FILE=<path>env var.~/.gcal/profiles/<active>/secret.json.~/.gcal/secret.json(shared / legacy — one client across profiles).
Active-profile resolution: --profile flag → GCAL_PROFILE env →
~/.gcal/config.toml active_profile → "default".
gcal config set tz Europe/Kyiv
gcal config set default_format json
gcal config list
gcal config pathGCAL_VERBOSE=1 (or --verbose) prints which source supplied the
secret + profile in use.
$ gcal auth switch nonexistent
gcal: auth switch failed: Profile 'nonexistent' does not exist. Create
it via `gcal auth login --profile nonexistent` first.; see /Users/.../
Library/Application Support/gcal/tee/1777882749_auth_switch.log
Pass -v / --verbose to print the full error inline (no log path).
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo test --all # 47+ tests
cargo run --release -- --helpTip
gcal --gen-man > /usr/local/share/man/man1/gcal.1 then man gcal.
Sponsor button on the repo page activates these channels (configured
in .github/FUNDING.yml):
Starting v1.0.0, all changes to main flow through open
issues + pull requests (PR/Issue gate). Direct pushes to main
are blocked. See CONTRIBUTING.md for the full
workflow:
- Open an issue describing intent.
- Wait for maintainer ack.
- Fork or branch → push commits.
- Open a PR; CI must pass (
fmt,clippy,test, smoke). - Maintainer review + merge.
The full v1.0.0 work plan and decision history were transferred
upstream to ITCare-Company/template_for_agents/process-knowledge-base/gcalpod-queue/
as a worked Anatomy reference.
Built on top of rust-dd/google-calendar-cli.
Substantial modifications + fork-point commit hash: NOTICE.md.
This project is licensed under the PolyForm Noncommercial License 1.0.0 plus the gcalpod Sustainable License Addendum v1 (gSL-v1).
Quick guide:
| If you are... | What applies | What you owe |
|---|---|---|
| Hobbyist / student / employee on personal time | core license (Noncommercial) | nothing |
| Solo or 2-person micro-business, < $20k revenue, ≤ $20k raised | Addendum B | self-assess; nothing |
| Contributor with merged commits | Addendum E | nothing; commercial use granted |
| Larger company sponsoring ≥ $5/mo on GH Sponsors / Patreon / BMC | Addendum A | maintain sponsorship |
| Larger company without sponsorship | core license (Noncommercial only) | sponsor, license, or wait 4y (Addendum C) |
| Distro / registry packager | Addendum D | preserve LICENSE files |
Each tagged release auto-converts to Apache 2.0 four years after its tag date (Addendum C — anti-lock-in).
Upstream-derived portions remain available under Apache License 2.0
per Apache 2.0 §4. See NOTICE.md for full attribution.