Back to productivity
productivity v2.0.0 5.1 min read 258 lines

google-workspace

Gmail, 캘린더, 드라이브, 연락처, 시트, 문서 통합 — gws CLI + OAuth2

Nous Research
MIT

Google Workspace

Gmail, Calendar, Drive, Contacts, Sheets, and Docs — powered by gws (Google's official Rust CLI). The skill provides a backward-compatible Python wrapper that handles OAuth token refresh and delegates to gws.

Architecture

google_api.py  →  gws_bridge.py  →  gws CLI
(argparse compat) (token refresh) (Google APIs)

  • setup.py handles OAuth2 (headless-compatible, works on CLI/Telegram/Discord)
  • gws_bridge.py refreshes the Hermes token and injects it into gws via GOOGLE_WORKSPACE_CLI_TOKEN
  • google_api.py provides the same CLI interface as v1 but delegates to gws

References

  • references/gmail-search-syntax.md — Gmail search operators (is:unread, from:, newer_than:, etc.)

Scripts

  • scripts/setup.py — OAuth2 setup (run once to authorize)
  • scripts/gws_bridge.py — Token refresh bridge to gws CLI
  • scripts/google_api.py — Backward-compatible API wrapper (delegates to gws)

Prerequisites

Install gws:

cargo install google-workspace-cli

or via npm (recommended, downloads prebuilt binary):


npm install -g @googleworkspace/cli

or via Homebrew:


brew install googleworkspace-cli

Verify: gws --version

First-Time Setup

The setup is fully non-interactive — you drive it step by step so it works
on CLI, Telegram, Discord, or any platform.

Define a shorthand first:

HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}"
GWORKSPACE_SKILL_DIR="$HERMES_HOME/skills/productivity/google-workspace"
PYTHON_BIN="${HERMES_PYTHON:-python3}"
if [ -x "$HERMES_HOME/hermes-agent/venv/bin/python" ]; then
PYTHON_BIN="$HERMES_HOME/hermes-agent/venv/bin/python"
fi
GSETUP="$PYTHON_BIN $GWORKSPACE_SKILL_DIR/scripts/setup.py"

Step 0: Check if already set up

$GSETUP --check

If it prints AUTHENTICATED, skip to Usage — setup is already done.

Step 1: Triage — ask the user what they need

**Question 1: "What Google services do you need? Just email, or also
Calendar/Drive/Sheets/Docs?"**

  • Email only → Use the himalaya skill instead — simpler setup.
  • Calendar, Drive, Sheets, Docs (or email + these) → Continue below.

Partial scopes: Users can authorize only a subset of services. The setup
script accepts partial scopes and warns about missing ones.

Question 2: "Does your Google account use Advanced Protection?"

  • No / Not sure → Normal setup.
  • Yes → Workspace admin must add the OAuth client ID to allowed apps first.

Step 2: Create OAuth credentials (one-time, ~5 minutes)

Tell the user:

1. Go to https://console.cloud.google.com/apis/credentials

2. Create a project (or use an existing one)

3. Enable the APIs you need (Gmail, Calendar, Drive, Sheets, Docs, People)

4. Credentials → Create Credentials → OAuth 2.0 Client ID → Desktop app

5. Download JSON and tell me the file path

$GSETUP --client-secret /path/to/client_secret.json

Step 3: Get authorization URL

$GSETUP --auth-url

Send the URL to the user. After authorizing, they paste back the redirect URL or code.

Step 4: Exchange the code

$GSETUP --auth-code "THE_URL_OR_CODE_THE_USER_PASTED"

Step 5: Verify

$GSETUP --check

Should print AUTHENTICATED. Token refreshes automatically from now on.

Usage

All commands go through the API script:

HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}"
GWORKSPACE_SKILL_DIR="$HERMES_HOME/skills/productivity/google-workspace"
PYTHON_BIN="${HERMES_PYTHON:-python3}"
if [ -x "$HERMES_HOME/hermes-agent/venv/bin/python" ]; then
PYTHON_BIN="$HERMES_HOME/hermes-agent/venv/bin/python"
fi
GAPI="$PYTHON_BIN $GWORKSPACE_SKILL_DIR/scripts/google_api.py"

Gmail

$GAPI gmail search "is:unread" --max 10
$GAPI gmail get MESSAGE_ID
$GAPI gmail send --to user@example.com --subject "Hello" --body "Message text"
$GAPI gmail send --to user@example.com --subject "Report" --body "

Q4

" --html
$GAPI gmail reply MESSAGE_ID --body "Thanks, that works for me."
$GAPI gmail labels
$GAPI gmail modify MESSAGE_ID --add-labels LABEL_ID

Calendar

$GAPI calendar list
$GAPI calendar create --summary "Standup" --start 2026-03-01T10:00:00+01:00 --end 2026-03-01T10:30:00+01:00
$GAPI calendar create --summary "Review" --start ... --end ... --attendees "alice@co.com,bob@co.com"
$GAPI calendar delete EVENT_ID

Drive

$GAPI drive search "quarterly report" --max 10
$GAPI drive search "mimeType='application/pdf'" --raw-query --max 5

Contacts

$GAPI contacts list --max 20

Sheets

$GAPI sheets get SHEET_ID "Sheet1!A1:D10"
$GAPI sheets update SHEET_ID "Sheet1!A1:B2" --values '[["Name","Score"],["Alice","95"]]'
$GAPI sheets append SHEET_ID "Sheet1!A:C" --values '[["new","row","data"]]'

Docs

$GAPI docs get DOC_ID

Direct gws access (advanced)

For operations not covered by the wrapper, use gws_bridge.py directly:

GBRIDGE="$PYTHON_BIN $GWORKSPACE_SKILL_DIR/scripts/gws_bridge.py"
$GBRIDGE calendar +agenda --today --format table
$GBRIDGE gmail +triage --labels --format json
$GBRIDGE drive +upload ./report.pdf
$GBRIDGE sheets +read --spreadsheet SHEET_ID --range "Sheet1!A1:D10"

Output Format

All commands return JSON via gws --format json. Key output shapes:

  • Gmail search/triage: Array of message summaries (sender, subject, date, snippet)
  • Gmail get/read: Message object with headers and body text
  • Gmail send/reply: Confirmation with message ID
  • Calendar list/agenda: Array of event objects (summary, start, end, location)
  • Calendar create: Confirmation with event ID and htmlLink
  • Drive search: Array of file objects (id, name, mimeType, webViewLink)
  • Sheets get/read: 2D array of cell values
  • Docs get: Full document JSON (use body.content for text extraction)
  • Contacts list: Array of person objects with names, emails, phones

Parse output with jq or read JSON directly.

Rules

  • Never send email or create/delete events without confirming with the user first.
  • Check auth before first use — run setup.py --check.
  • Use the Gmail search syntax reference for complex queries.
  • Calendar times must include timezone — ISO 8601 with offset or UTC.
  • Respect rate limits — avoid rapid-fire sequential API calls.

Important Notes

  • Drive에 업로드된 .xlsx 파일은 Sheets API로 읽을 수 없음 ("This operation is not supported for this document" 에러).
대안: gws 또는 google-api-python-client로 파일을 다운로드한 후 pandas/openpyxl로 읽기.
  • OAuth 동의 화면이 테스트 모드인 경우: 테스터 목록에 사용자 이메일을 추가해야 함 (403 access_denied).
  • 인증 URL 생성 시 scope 형식 주의: https://www.googleapis.com/auth/spreadsheets (슬래시 중복 불가).

Troubleshooting

| Problem | Fix |
|---------|-----|
| NOT_AUTHENTICATED | Run setup Steps 2-5 |
| REFRESH_FAILED | Token revoked — redo Steps 3-5 |
| gws: command not found | Install: npm install -g @googleworkspace/cli |
| HttpError 403 | Missing scope — $GSETUP --revoke then redo Steps 3-5 |
| HttpError 403: Access Not Configured | Enable API in Google Cloud Console |
| HttpError 403 access_denied | OAuth 동의 화면 테스트 모드 → 테스터 이메일 추가 |
| HttpError 400: not supported | .xlsx 파일은 Sheets API 미지원 → 다운로드 후 pandas로 읽기 |
| invalid_scope | scope URL에 슬래시 중복 확인 |
| Advanced Protection blocks auth | Admin must allowlist the OAuth client ID |

Revoking Access

$GSETUP --revoke

Related Skills / 관련 스킬

linear

Linear 이슈/프로젝트/팀 관리 — GraphQL API, API 키 인증, curl만으로 동작

maps

>

nano-pdf

자연어 명령으로 PDF 편집 — 텍스트 수정, 오타 교정, 제목 업데이트

notion

Notion API로 페이지/데이터베이스/블록 관리 — curl로 검색, 생성, 업데이트, 쿼리