Using gs
Overview
gs is a Google Suite CLI. Structure is gs <service> <action>:
gs auth login | logout | status # one token for all services
gs gmail tail | send | read | mark | rm | mv | label | profile | repl
gs calendar ls | events | add | rm
gs drive ls | upload | download | mkdir | rm
Run from this repo with uv run gs ..., or gs ... if installed
(uv pip install -e .).
Lineage: gmailtail → gmail → gs. gmailtail --tail is now gs gmail tail --tail.
Authentication
One combined OAuth scope (Gmail full + Calendar + Drive) → one login covers all.
gs auth login --credentials credentials.json # browser once; token at ~/.gs/tokens
gs auth status # who am I / am I logged in
gs auth logout # delete cached token
- Enable Gmail, Calendar, and Drive APIs in Google Cloud Console; OAuth 2.0
Client ID (Desktop app) → download JSON.
gs auth login --auth-token key.json for a service account; --force-headless
for SSH/no-browser.
- Other commands reuse the cached token and error with "run gs auth login" if absent.
- Global options (
--credentials, --config-file, --verbose, --quiet,
--ignore-token, --cached-auth-token) go before the service:
gs --config-file gs.yaml gmail tail.
gmail
gs gmail tail --tail # stream new mail as JSON (follow)
gs gmail tail --once --format json-lines # one-shot, pipe to jq
gs gmail tail --from x@y.com --query "subject:alert" --tail
gs gmail send --to a@x.com --subject Hi --body hello \
--cc c@x.com --attach f.pdf --html --body-file - # attach repeatable; - = stdin
gs gmail read <id> --mark-read # display full JSON, optionally mark read
gs gmail mark <id...> --read|--unread # bulk read state
gs gmail rm <id...> # Trash (reversible)
gs gmail rm <id...> --permanently --yes # hard delete (prompts without -y)
gs gmail label ls|create <n>|rm <n>|rename <old> <new>
gs gmail mv <id...> --to Work [--from INBOX] # --from removes source label
gs gmail profile ; gs gmail repl
calendar
gs calendar ls # list calendars
gs calendar events --from today --to +7d # window: ISO or now/today/tomorrow/+7d/-2h
gs calendar add --summary "Mtg" --start 2026-06-01T10:00:00Z --end 2026-06-01T11:00:00Z \
[--calendar <id>] [--location ..] [--timezone America/New_York]
gs calendar add --summary Holiday --start 2026-06-01 --end 2026-06-02 # date-only = all-day
gs calendar add --summary Coffee --start 2026-06-01T10:00:00-07:00 --end 2026-06-01T11:00:00-07:00 \
--attendee a@x.com --attendee b@y.com # repeatable; emails invitations (sendUpdates=all)
gs calendar rm <event-id...>
Timezones: today/tomorrow use local midnight; now/+7d/-2h are
relative. A --start/--end with an offset (…-07:00) or Z is honored as-is; a
naive value (no offset) is treated as local time unless --timezone is given.
Gmail message timestamp is local time with an explicit offset.
drive
gs drive ls ["name contains 'report'"] # optional Drive query
gs drive upload <local-file> [--parent <folder-id>]
gs drive download <file-id> -o out.pdf
gs drive mkdir <name> [--parent <folder-id>]
gs drive rm <file-id...> # Trash (reversible)
gs drive rm <file-id...> --permanently --yes
Architecture (for editing the tool)
gs/cli.py — top group; shared options in ctx.obj (propagates to subgroups).
gs/commands/ — one module per command; auth.py, gmail_group.py,
calendar.py, drive.py define the subgroups. Helpers in commands/__init__.py:
build_config, get_auth, get_service (gmail), get_calendar_service,
get_drive_service, get_client.
gs/auth.py — GoogleAuth: combined SCOPES, credentials(allow_login),
service(api, version), login/logout/status, NotAuthenticatedError.
- Service layers:
gs/messages.py + gs/labels.py (Gmail),
gs/calendar_service.py (+ parse_when), gs/drive_service.py.
gs/client.py/gs/monitor.py back gs gmail tail/repl.
- Tests mock the Google
service: tests/test_services.py,
tests/test_calendar_drive.py, tests/test_cli.py (CliRunner). uv run pytest.
Common mistakes
- Global options after the service →
gs gmail tail --credentials … fails;
put --credentials/--config-file/--verbose before the service name.
- Skipping
gs auth login → other commands won't open a browser; they error
until you log in once.
- Expecting
gs gmail tail to stop → with --tail it follows forever; use --once.
rm default is permanent → no; default is Trash (Gmail and Drive). --permanently
is irreversible and prompts unless -y.
gmail mv without --from removes the old label → no; it only adds --to.
- Stale token after upgrade → scope changed;
gs auth logout then gs auth login.
Source: c4pt0r/gs — distributed by TomeVault.
1---2name: using-gs3description: Use when managing Google Suite from the command line with the `gs` CLI — Gmail (tail/stream as JSON, send with attachments, delete, mark read/unread, labels, move), Google Calendar (list calendars, list/create/delete events, invite attendees), Google Drive (list/upload/download/mkdir/delete), or authentication (gs auth login/logout/status). Covers the nested subcommand structure, combined OAuth scope, and per-command options.4---56# Using gs78## Overview910`gs` is a Google Suite CLI. Structure is `gs <service> <action>`:1112```13gs auth login | logout | status # one token for all services14gs gmail tail | send | read | mark | rm | mv | label | profile | repl15gs calendar ls | events | add | rm16gs drive ls | upload | download | mkdir | rm17```1819Run from this repo with `uv run gs ...`, or `gs ...` if installed20(`uv pip install -e .`).2122> Lineage: `gmailtail` → `gmail` → `gs`. `gmailtail --tail` is now `gs gmail tail --tail`.2324## Authentication2526One combined OAuth scope (Gmail full + Calendar + Drive) → one login covers all.2728```bash29gs auth login --credentials credentials.json # browser once; token at ~/.gs/tokens30gs auth status # who am I / am I logged in31gs auth logout # delete cached token32```3334- Enable **Gmail, Calendar, and Drive APIs** in Google Cloud Console; OAuth 2.035 Client ID (Desktop app) → download JSON.36- `gs auth login --auth-token key.json` for a service account; `--force-headless`37 for SSH/no-browser.38- Other commands reuse the cached token and error with "run gs auth login" if absent.39- Global options (`--credentials`, `--config-file`, `--verbose`, `--quiet`,40 `--ignore-token`, `--cached-auth-token`) go **before** the service:41 `gs --config-file gs.yaml gmail tail`.4243## gmail4445```bash46gs gmail tail --tail # stream new mail as JSON (follow)47gs gmail tail --once --format json-lines # one-shot, pipe to jq48gs gmail tail --from x@y.com --query "subject:alert" --tail49gs gmail send --to a@x.com --subject Hi --body hello \50 --cc c@x.com --attach f.pdf --html --body-file - # attach repeatable; - = stdin51gs gmail read <id> --mark-read # display full JSON, optionally mark read52gs gmail mark <id...> --read|--unread # bulk read state53gs gmail rm <id...> # Trash (reversible)54gs gmail rm <id...> --permanently --yes # hard delete (prompts without -y)55gs gmail label ls|create <n>|rm <n>|rename <old> <new>56gs gmail mv <id...> --to Work [--from INBOX] # --from removes source label57gs gmail profile ; gs gmail repl58```5960## calendar6162```bash63gs calendar ls # list calendars64gs calendar events --from today --to +7d # window: ISO or now/today/tomorrow/+7d/-2h65gs calendar add --summary "Mtg" --start 2026-06-01T10:00:00Z --end 2026-06-01T11:00:00Z \66 [--calendar <id>] [--location ..] [--timezone America/New_York]67gs calendar add --summary Holiday --start 2026-06-01 --end 2026-06-02 # date-only = all-day68gs calendar add --summary Coffee --start 2026-06-01T10:00:00-07:00 --end 2026-06-01T11:00:00-07:00 \69 --attendee a@x.com --attendee b@y.com # repeatable; emails invitations (sendUpdates=all)70gs calendar rm <event-id...>71```7273Timezones: `today`/`tomorrow` use **local** midnight; `now`/`+7d`/`-2h` are74relative. A `--start/--end` with an offset (`…-07:00`) or `Z` is honored as-is; a75naive value (no offset) is treated as **local** time unless `--timezone` is given.76Gmail message `timestamp` is local time with an explicit offset.7778## drive7980```bash81gs drive ls ["name contains 'report'"] # optional Drive query82gs drive upload <local-file> [--parent <folder-id>]83gs drive download <file-id> -o out.pdf84gs drive mkdir <name> [--parent <folder-id>]85gs drive rm <file-id...> # Trash (reversible)86gs drive rm <file-id...> --permanently --yes87```8889## Architecture (for editing the tool)9091- `gs/cli.py` — top group; shared options in `ctx.obj` (propagates to subgroups).92- `gs/commands/` — one module per command; `auth.py`, `gmail_group.py`,93 `calendar.py`, `drive.py` define the subgroups. Helpers in `commands/__init__.py`:94 `build_config`, `get_auth`, `get_service` (gmail), `get_calendar_service`,95 `get_drive_service`, `get_client`.96- `gs/auth.py` — `GoogleAuth`: combined SCOPES, `credentials(allow_login)`,97 `service(api, version)`, `login/logout/status`, `NotAuthenticatedError`.98- Service layers: `gs/messages.py` + `gs/labels.py` (Gmail),99 `gs/calendar_service.py` (+ `parse_when`), `gs/drive_service.py`.100- `gs/client.py`/`gs/monitor.py` back `gs gmail tail`/`repl`.101- Tests mock the Google `service`: `tests/test_services.py`,102 `tests/test_calendar_drive.py`, `tests/test_cli.py` (CliRunner). `uv run pytest`.103104## Common mistakes105106- **Global options after the service** → `gs gmail tail --credentials …` fails;107 put `--credentials/--config-file/--verbose` before the service name.108- **Skipping `gs auth login`** → other commands won't open a browser; they error109 until you log in once.110- **Expecting `gs gmail tail` to stop** → with `--tail` it follows forever; use `--once`.111- **`rm` default is permanent** → no; default is Trash (Gmail and Drive). `--permanently`112 is irreversible and prompts unless `-y`.113- **`gmail mv` without `--from` removes the old label** → no; it only *adds* `--to`.114- **Stale token after upgrade** → scope changed; `gs auth logout` then `gs auth login`.115116---117> Source: [c4pt0r/gs](https://github.com/c4pt0r/gs) — distributed by [TomeVault](https://tomevault.io).118<!-- tomevault:4.0:skill_md:2026-06-24 -->