Google Workspace from the command line — Gmail, Drive, Sheets, Docs, Calendar, and Forms. Unix philosophy: simple commands that compose with pipes.
uv tool install git+https://github.com/robpc/desk
desk setup
desk mail search "is:unread"uv tool install git+https://github.com/robpc/deskpipx install git+https://github.com/robpc/deskpython3 -m venv ~/.local/share/desk-venv
~/.local/share/desk-venv/bin/pip install git+https://github.com/robpc/desk
mkdir -p ~/.local/bin
ln -sf ~/.local/share/desk-venv/bin/desk ~/.local/bin/deskInstall uv — it manages Python for you:
curl -LsSf https://astral.sh/uv/install.sh | sh
uv tool install git+https://github.com/robpc/deskexternally-managed-environment error: Your system Python is managed by Homebrew (or similar). Use uv or pipx instead of pip install directly.
desk: command not found after install: ~/.local/bin may not be on your PATH. Add it:
# zsh (macOS default)
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc && source ~/.zshrc
# bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrcIf you have gcloud installed and authenticated:
desk setup --gcloudGet credentials.json from your team (e.g., 1Password vault):
desk setup --credentials ~/Downloads/credentials.jsonThis takes about 10 minutes the first time.
- Go to Google Cloud Console and create a project
- Go to APIs & Services → Library and enable: Gmail API, Google Drive API, Google Sheets API, Google Docs API, Google Calendar API, Google Forms API, Google Slides API, Google Meet REST API
- Go to APIs & Services → Credentials → Create Credentials → OAuth client ID
- If prompted, configure the OAuth consent screen (User type: External, App name: "Desk")
- Application type: Desktop app, Name: "Desk", click Create
- Download the JSON file, then:
mkdir -p ~/.desk
mv ~/Downloads/client_secret_*.json ~/.desk/credentials.json
desk auth loginAlready have a project from an earlier install? desk slides and desk meet need
the Google Slides API and Google Meet REST API enabled in that same project
(the Library page above), then desk auth login again to grant their scopes. A
disabled API shows up as a 403 ... has not been used in project error from Google,
not as a missing scope.
desk auth status # Shows the configured client_id, the token's client_id, missing scopes
desk auth logout # Remove the OAuth token; keep the client credentials
desk auth clear --yes # Remove token and client credentials (--token / --client to pick one)If auth status says the token's client_id doesn't match the configured client, the token
can't refresh — desk auth logout then desk auth login fixes it.
desk --help # All command groups
desk mail --help # All mail commands
desk mail search --help # Detailed help for a commanddesk --version
# desk, version 0.3.0 (a837d65, 2026-09-03)
desk --version --json # Same, as fieldsThe commit is baked in at build time, so it identifies the build even when
installed straight from git — where the version number alone cannot, since
that install tracks a branch rather than a release. It is also how to confirm
uv tool upgrade desk actually changed anything.
desk --capabilities all reports the same commit alongside the machine-readable
command list, so a script can check whether a fix is present before relying on
it.
desk mail search "from:boss" --max 10 # Search messages
desk mail unread # Unread messages
desk mail read <id> # Read a message
desk mail threads "from:boss" # Search by thread
desk mail thread <thread-id> # Read entire conversation
desk mail send --to "user@example.com" --subject "Hello" --body "Message"
desk mail reply <id> --body "Thanks!"
desk mail forward <id> --to "colleague@example.com" --body "FYI"
desk mail drafts # List drafts
desk mail labels # List labels
desk mail archive <id>... # Archive messages
desk mail trash <id>... # Move to trash
desk mail star <id>... # Star messages
desk mail label <label> <id>... # Add a label
desk mail attachments <id> # List attachments
desk mail download-attachments <id> --output-dir ./files/desk drive search "name contains 'report'" # Search files
desk drive recent --max 10 # Recently modified files
desk drive read <file-id> # Read file content
desk drive info <file-id> # File metadata
desk drive upload report.pdf # Upload a file
desk drive download <file-id> # Download a file
desk drive mkdir "Project Files" # Create a folder
desk drive move <file-id> <folder-id> # Move file to folder
desk drive trash <file-id> # Move to trash
desk drive share <file-id> bob@co.com # Share with someonedesk sheets read <spreadsheet-id> # Read entire first sheet
desk sheets read <id> --range "Sheet1!A1:C10" # Read specific range
desk sheets update-cell <id> "Sheet1!A1" "New value" # Update a cell
desk sheets create "Q1 Budget" # Create spreadsheet
desk sheets write <id> "Sheet1!A1:B2" '[["A","B"],["1","2"]]'
desk sheets append <id> "Sheet1!A:Z" '[["Alice","30"]]'
desk sheets clear <id> "Sheet1!A1:C10" # Clear a rangedesk docs create "Meeting Notes" # Create a doc
desk docs read <document-id> # Read document
desk docs update <id> "Appended text" # Append text
desk docs update <id> "New" --mode replace # Replace all content
desk docs export <id> report.pdf # Export as PDF
desk docs export <id> notes.txt --format txt # Export as textdesk cal today # Today's events
desk cal week # This week's events
desk cal next --max 5 # Upcoming events
desk cal list # List calendars
desk cal find "standup" # Search events
desk cal create "Meeting" --start 2024-01-15T10:00:00 --end 2024-01-15T11:00:00
desk cal create "Training" --start ... --end ... --meet --hide-guest-list
desk cal update <event-id> --summary "New Title"
desk cal update <event-id> --meet # Add a Meet link to an existing event
desk cal delete <event-id> --send-updates none # Delete without mailing attendees
desk cal delete <event-id>
# Any command takes -c/--calendar: a calendar ID, a friendly name from
# `desk cal list` (case-insensitive), or 'primary' (the default).
desk cal today -c Family # Read a shared calendar
desk cal create "Dinner" -c Family \
--start 2024-01-15T18:00:00 --end 2024-01-15T20:00:00
desk cal update <event-id> -c Family --summary "Late dinner"
desk cal delete <event-id> -c FamilyMeeting-space settings — recording and transcription. Needs the Google Meet REST
API enabled in your Cloud project (see Setup) and a scope older tokens don't have, so
run desk auth login again if desk meet reports a missing scope.
# The conferenceId from a cal read addresses the space
desk cal create "Training" --start ... --end ... --meet --json
desk meet read abc-defg-hij # Current settings
desk meet update abc-defg-hij --auto-record on --auto-transcript on
desk meet update abc-defg-hij --auto-record default # Defer to your Workspace policyCo-hosts are UI-only — Google restricts the Meet space-members API to its Developer
Preview Program. See desk meet --help.
desk forms create "Survey" # Create a form
desk forms read <form-id> # Read form structure
desk forms responses <form-id> # List responses
desk forms add-question <form-id> "Your name?" --type short-answer
desk forms add-section <form-id> "Part 2" # Add a sectiondesk mail archive ID1 ID2 ID3 # Multiple IDs
desk mail search "from:notifications" --json | jq -r '.[].id' | desk mail archive --stdin # Pipe
desk mail modify ID1 ID2 --remove-label INBOX --remove-label UNREAD # CombineAll commands support --json for machine-readable output:
desk mail search "is:unread" --json
desk drive recent --json
desk cal today --jsonIf you previously used gmail, Desk will auto-migrate your config from ~/.gmail-cli/ to ~/.desk/ on first run. You'll need to re-authenticate to grant the expanded scopes (Drive, Sheets, Docs, Calendar).
git clone https://github.com/robpc/desk
cd desk
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
# Run tests
pytest
# Lint
ruff check src/Copyright 2026 Robert Cannon
Licensed under the Apache License, Version 2.0. See LICENSE for the full text.