Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

rscalendar - Google Calendar CLI Tool

A command-line tool for managing Google Calendar events, written in Rust.

Features

  • List events with flexible filtering by date and calendar
  • Create events with support for all-day and timed events
  • Update events by patching individual fields
  • Delete events by ID
  • Shell completions for bash, zsh, fish, and more

Technology

  • Built with Rust using direct Google Calendar API v3 calls via reqwest
  • OAuth2 authentication via yup-oauth2
  • CLI powered by clap

Installation

Prerequisites

  • Rust toolchain (edition 2024)
  • Google Cloud project with Calendar API enabled
  • OAuth2 credentials (Desktop application type)

Install from crates.io

cargo install rscalendar

This downloads, compiles, and installs the latest published version into ~/.cargo/bin/.

Building from Source

git clone https://github.com/veltzer/rscalendar.git
cd rscalendar
cargo build --release

The binary will be at target/release/rscalendar.

Google Cloud Setup

  1. Go to Google Cloud Console
  2. Create a new project (or use an existing one)
  3. Enable the Google Calendar API under APIs & Services
  4. Create OAuth2 credentials:
    • Go to APIs & Services > Credentials
    • Click “Create Credentials” > “OAuth client ID”
    • Choose “Desktop application” as the application type
    • Download the JSON file
  5. Place the credentials file at ~/.config/rscalendar/credentials.json

Getting Started

First-Time Setup

After installing rscalendar and placing your OAuth2 credentials, authenticate:

rscalendar auth

This opens your browser for Google OAuth2 consent. The token is cached at ~/.config/rscalendar/token_cache.json for future use.

If you’re on a headless machine:

rscalendar auth --no-browser

This prints the auth URL instead of opening a browser.

Basic Usage

List upcoming events:

rscalendar list

List more events:

rscalendar list --max-results 25

Create an all-day event:

rscalendar create --summary "Team offsite" --start 2026-04-01 --end 2026-04-02

Create a timed event:

rscalendar create --summary "Standup" --start "2026-04-01T09:00:00+03:00" --end "2026-04-01T09:15:00+03:00"

Delete an event by ID (shown in list output):

rscalendar delete --event-id <EVENT_ID>

Authentication

rscalendar uses OAuth2 to access the Google Calendar API on your behalf.

How It Works

  1. You provide OAuth2 client credentials (a JSON file from Google Cloud Console)
  2. On first use, rscalendar opens your browser to get consent
  3. The access token is cached locally for future requests

Files

FileLocationPurpose
Credentials~/.config/rscalendar/credentials.jsonOAuth2 client ID and secret
Token cache~/.config/rscalendar/token_cache.jsonCached access/refresh tokens

Commands

Authenticate (opens browser):

rscalendar auth

Authenticate without browser (prints URL):

rscalendar auth --no-browser

Force re-authentication (removes cached token first):

rscalendar auth --force

Scopes

rscalendar requests the https://www.googleapis.com/auth/calendar scope, which provides full read/write access to your Google Calendar.

Commands

rscalendar provides the following commands:

CommandDescription
list-calendarsList all accessible calendars
listList all events for a calendar
event createCreate a new event
event updateUpdate fields on an existing event
event deleteDelete an event
calendar createCreate a new public calendar
checkCheck events against property rules
properties addAdd properties to events
properties checkValidate event properties against config
properties deleteDelete a property from events
properties renameRename a property key on events
properties editEdit a property value on events
move-eventsMove events between calendars
authAuthenticate with Google
defconfigPrint default configuration
completeGenerate shell completions

Global Flags

FlagDescription
--show-builtinShow “(built-in)” labels on standard Google Calendar fields
--jsonOutput as JSON for processing with tools like jq

All commands that interact with the calendar require prior authentication via rscalendar auth.

list-calendars

List all calendars accessible to the authenticated user.

Usage

rscalendar list-calendars

Output

For each calendar, shows:

  • Name (with (primary) marker for the default calendar)
  • id — the calendar ID to use with --calendar-id or in config.toml
  • role — your access level (owner, writer, reader, freeBusyReader)
  • description — calendar description, if set

Example

$ rscalendar list-calendars
My Calendar (primary)
  id: user@gmail.com
  role: owner

Work
  id: abc123@group.calendar.google.com
  role: writer

Holidays in Israel
  id: en.jewish#holiday@group.v.calendar.google.com
  role: reader

Use the id value as --calendar-id or set it as calendar_id in ~/.config/rscalendar/config.toml.

list

List all events for a calendar.

Usage

rscalendar list [OPTIONS]

Options

OptionDefaultDescription
--calendar-name <NAME>from configCalendar name to query

Examples

List all events from the default calendar:

rscalendar list

List all events from a specific calendar:

rscalendar list --calendar-name Teaching

event create

Create a new calendar event.

Usage

rscalendar event create --summary <TEXT> --start <TIME> --end <TIME> [OPTIONS]

Options

OptionRequiredDescription
--summary <TEXT>YesEvent title
--start <TIME>YesStart time (RFC3339 or YYYY-MM-DD)
--end <TIME>YesEnd time (RFC3339 or YYYY-MM-DD)
--calendar-id <ID>NoCalendar ID (default: primary)
--description <TEXT>NoEvent description
--location <TEXT>NoEvent location

Date Handling

  • Timed events: Use RFC3339 format, e.g. 2026-04-01T09:00:00+03:00
  • All-day events: Use YYYY-MM-DD format. The end date is inclusive — rscalendar automatically converts it to Google’s exclusive format.

Examples

Create an all-day event:

rscalendar event create --summary "Holiday" --start 2026-04-01 --end 2026-04-01

Create a timed event with location:

rscalendar event create --summary "Lunch" --start "2026-04-01T12:00:00+03:00" --end "2026-04-01T13:00:00+03:00" --location "Cafe"

event update

Update fields on an existing calendar event. Only the fields you specify are changed; all others remain unchanged.

Usage

rscalendar event update --event-id <ID> [OPTIONS]

Options

OptionRequiredDescription
--event-id <ID>YesEvent ID to update
--calendar-id <ID>NoCalendar ID (default: primary)
--summary <TEXT>NoNew event title
--start <TIME>NoNew start time (RFC3339 or YYYY-MM-DD)
--end <TIME>NoNew end time (RFC3339 or YYYY-MM-DD)
--description <TEXT>NoNew description
--location <TEXT>NoNew location

At least one field must be provided.

Examples

Rename an event:

rscalendar event update --event-id abc123 --summary "New Title"

Change the time and location:

rscalendar event update --event-id abc123 --start "2026-04-02T10:00:00+03:00" --end "2026-04-02T11:00:00+03:00" --location "Room B"

event delete

Delete a calendar event by ID.

Usage

rscalendar event delete --event-id <ID> [OPTIONS]

Options

OptionRequiredDescription
--event-id <ID>YesEvent ID to delete
--calendar-id <ID>NoCalendar ID (default: primary)

Examples

rscalendar event delete --event-id abc123

The event ID can be found in the output of rscalendar list.

check

Check that events have all required properties based on their type property. The rules are defined in the [check] section of config.toml.

Usage

rscalendar check [OPTIONS]

Options

OptionRequiredDescription
--calendar-name <NAME>NoCalendar name (default: from config)

How It Works

  1. Reads the [check] section from config, which maps each type value to a list of required property keys
  2. For each event, looks up its type property
  3. Verifies that all required properties for that type are present

Reports

  • Events missing a type property
  • Events with a type value not defined in [check]
  • Events missing required properties for their type

Config

[check]
teaching = ["client", "company", "course"]
working = ["client", "company"]
call = ["client", "company"]
meeting = ["client", "company"]

This means: if an event has type=teaching, it must also have client, company, and course properties set.

Example

$ rscalendar check
Linux Workshop (2026-04-01T09:00:00+03:00):
  - missing required property 'course' (required for type 'teaching')
Team Sync (2026-04-02T14:00:00+03:00):
  - missing 'type' property

2 issue(s) in 2 event(s) out of 10.

calendar create

Create a new public Google Calendar.

Usage

rscalendar calendar create <NAME>

Details

Creates a new calendar with the given name and sets it to public (readable by anyone). The calendar ID is printed on success — use it with --calendar-id or in config.toml.

Examples

$ rscalendar calendar create Teaching
Created public calendar 'Teaching'
  id: abc123@group.calendar.google.com

properties add

Add shared extended properties to events. Validates keys and values against the [properties] section in config.toml.

Usage

rscalendar properties add [OPTIONS]

Options

OptionRequiredDescription
--calendar-name <NAME>NoCalendar name (default: from config)
--key <KEY>NoProperty key to set (must be in config). If omitted, prompts for all missing properties
--value <VALUE>NoProperty value (must be allowed for the key). Requires --key
--allNoApply to all events without prompting

Modes

Interactive (no –key/–value)

Walks each event and prompts for all missing properties using a numbered menu of allowed values from config:

rscalendar properties add

Single property (–key and –value)

Sets a specific property, prompting per event (y/n/q):

rscalendar properties add --key company --value AgileSparks

Skip prompting with --all:

rscalendar properties add --key company --value AgileSparks --all

Config

Properties must be defined in ~/.config/rscalendar/config.toml:

[properties]
company = ["AgileSparks", "Intel", "Google"]
course = ["Linux Fundamentals", "Advanced Python"]

properties check

Check that all event properties have keys and values defined in the [properties] section of config.toml.

Usage

rscalendar properties check [OPTIONS]

Options

OptionRequiredDescription
--calendar-name <NAME>NoCalendar name (default: from config)

What It Checks

  • Missing properties — keys defined in config but not present on the event
  • Invalid values — property values not in the allowed list for that key
  • Unknown properties — keys on the event that are not defined in config

Example

$ rscalendar properties check
Team Meeting (2026-04-01T10:00:00+03:00):
  - missing property 'course'
Workshop (2026-04-02T09:00:00+03:00):
  - property 'company' has value 'Foo' which is not in allowed values: AgileSparks, Intel, Google

2 issue(s) found across 5 event(s).

properties delete

Delete a shared extended property from events.

Usage

rscalendar properties delete --key <KEY> [OPTIONS]

Options

OptionRequiredDescription
--key <KEY>YesProperty key to delete
--calendar-name <NAME>NoCalendar name (default: from config)
--allNoApply to all events without prompting

Details

Interactive by default — prompts (y/n/q) for each event that has the property. Events that don’t have the property are skipped automatically.

The deletion works by sending the property key with a null value to the Google Calendar API, which is the only way to remove an extended property.

Examples

Delete interactively:

rscalendar properties delete --key client

Delete from all events without prompting:

rscalendar properties delete --key client --all

properties rename

Rename a shared extended property key on events. The value is preserved.

Usage

rscalendar properties rename --from <OLD_KEY> --to <NEW_KEY> [OPTIONS]

Options

OptionRequiredDescription
--from <OLD_KEY>YesCurrent property key name
--to <NEW_KEY>YesNew property key name
--calendar-name <NAME>NoCalendar name (default: from config)
--allNoApply to all events without prompting

Details

Interactive by default — prompts (y/n/q) for each event that has the old key. Events without the old key are skipped.

The rename removes the old key and adds the new key with the same value in a single PATCH request.

Examples

Rename interactively:

rscalendar properties rename --from source_calendar --to client

Rename on all events without prompting:

rscalendar properties rename --from source_calendar --to client --all

properties edit

Interactively edit properties on each event via TUI menus. For each event, you can add, change, or delete properties in a loop before moving to the next event.

Usage

rscalendar properties edit [OPTIONS]

Options

OptionRequiredDescription
--calendar-name <NAME>NoCalendar name (default: from config)

How It Works

For each event, the TUI shows:

  1. The event name and start time
  2. Current properties
  3. A numbered menu of actions:
    • add — for properties defined in config but not set on the event
    • change — for properties already set (pick a new value from the allowed list)
    • delete — remove a property from the event
    • n — save changes and move to the next event
    • q — save changes and quit

Changes are saved when you press n (next) or q (quit). If no changes were made, nothing is sent to the API.

Example Session

Event: Linux Workshop (2026-04-01T09:00:00+03:00)
  company: AgileSparks
  Actions:
    1: change 'company'
    2: delete 'company'
    3: add 'course'
    n: next event
    q: quit
  choice: 3
  Select course:
    1: Linux Fundamentals
    2: Advanced Python
    s: skip this property
  choice: 1
  company: AgileSparks
  course: Linux Fundamentals
  Actions:
    1: change 'company'
    2: delete 'company'
    3: change 'course'
    4: delete 'course'
    n: next event
    q: quit
  choice: n
  saved.

Config

Property keys and allowed values must be defined in ~/.config/rscalendar/config.toml:

[properties]
company = ["AgileSparks", "Intel", "Google"]
course = ["Linux Fundamentals", "Advanced Python"]

move-events

Move events from one calendar to another. Events are created in the target calendar and deleted from the source.

Usage

rscalendar move-events --source <NAME> --target <NAME> [OPTIONS]

Options

OptionRequiredDescription
--source <NAME>YesSource calendar name to move events from
--target <NAME>YesTarget calendar name to move events into
--interactiveNoPrompt for each event: y=move, n=skip, q=quit
--dry-runNoShow what would be done without making changes

Examples

Preview what would be moved:

rscalendar move-events --source "Client - John" --target Teaching --dry-run

Interactively move events, choosing which ones to move:

rscalendar move-events --source "Client - John" --target Teaching --interactive

Move all events without prompting:

rscalendar move-events --source "Client - John" --target Teaching

auth

Authenticate with Google via OAuth2 and cache the token locally.

Usage

rscalendar auth [OPTIONS]

Options

OptionDescription
--no-browserPrint the authorization URL instead of opening the browser
--forceRemove cached token and force re-authentication

Examples

Normal authentication (opens browser):

rscalendar auth

Headless authentication:

rscalendar auth --no-browser

Re-authenticate:

rscalendar auth --force

defconfig

Print the default configuration file to stdout.

Usage

rscalendar defconfig

Example

To create a fresh config file with all defaults commented out:

rscalendar defconfig > ~/.config/rscalendar/config.toml

complete

Generate shell completion scripts.

Usage

rscalendar complete <SHELL>

Supported Shells

  • bash
  • zsh
  • fish
  • elvish
  • powershell

Examples

Generate and install bash completions:

rscalendar complete bash > ~/.local/share/bash-completion/completions/rscalendar

Generate and install zsh completions:

rscalendar complete zsh > ~/.zfunc/_rscalendar

Configuration

rscalendar supports an optional TOML config file for setting defaults.

File Location

~/.config/rscalendar/config.toml

The config file is optional. If missing, built-in defaults are used. All config values can be overridden by CLI flags.

Options

KeyTypeDefaultDescription
calendar_namestring(none)Default calendar name for all commands
no_browserbooleanfalseDon’t open browser during auth

The [check] Section

The [check] section defines required properties per event type. It is used by the rscalendar check command to validate that events have all expected properties.

[check]
teaching = ["client", "company", "course"]
working = ["client", "company"]
call = ["client", "company"]
meeting = ["client", "company"]

Each key is a type property value; its array lists the property keys that must be present on events of that type.

Example

# Use a specific calendar by default
calendar_name = "Teaching"

# Always print URL instead of opening browser (headless machines)
no_browser = true

# Required properties per event type
[check]
teaching = ["client", "company", "course"]
working = ["client", "company"]

Precedence

CLI flags always win over config file values:

# Uses calendar_name from config.toml
rscalendar list

# Overrides config.toml with the flag value
rscalendar list --calendar-name "Work"

Files Overview

FilePurpose
~/.config/rscalendar/config.tomlUser preferences
~/.config/rscalendar/credentials.jsonOAuth2 client credentials
~/.config/rscalendar/token_cache.jsonCached access/refresh tokens

Design Decisions

Event Tags via Shared Extended Properties

Google Calendar API offers extendedProperties on events for storing custom key-value metadata. There are two scopes:

  • private — scoped to the OAuth client ID that created them. Only the same client ID can read them back.
  • shared — visible to anyone with access to the event (all attendees, all apps, including public calendar readers via the API).

Why shared?

We chose shared extended properties for the following reasons:

  1. Data durabilityprivate properties are tied to the OAuth client ID in credentials.json. If you ever recreate your OAuth credentials in Google Cloud Console (new project, deleted and re-created client ID, etc.), all private extended properties become permanently inaccessible. The data isn’t deleted, but there is no way to recover it with a different client ID. With shared, the data belongs to the event itself and survives credential changes.

  2. Acceptable visibility tradeoffshared properties are visible via the API to anyone who can read the event. On public calendars, this means anyone querying the API could see the tags. However, extended properties are not displayed in the Google Calendar web or mobile UI — they are only accessible programmatically. For a personal CLI tool, this is an acceptable tradeoff.

  3. Interoperabilityshared properties can be read and written by any tool or script with access to the calendar, not just rscalendar. This makes it possible to build other tools that interact with the same tags.

Implementation Plan

Tags will be stored as a comma-separated string under a single key in shared extended properties:

{
  "extendedProperties": {
    "shared": {
      "tags": "work,urgent,followup"
    }
  }
}

The Google Calendar API supports filtering events by extended properties using the sharedExtendedProperty query parameter, enabling efficient tag-based queries without client-side filtering.

Deleting Extended Properties

Google Calendar API does not remove an extended property when you simply omit it from a PATCH request. To delete a property, you must explicitly set its value to null:

{
  "extendedProperties": {
    "shared": {
      "key_to_delete": null
    }
  }
}

This is handled automatically by rscalendar properties delete.