Google Workspace read access — Drive, Gmail, Calendar, Contacts
This document captures the essential vocabulary, the real use cases, the choice of the API over an rclone mount, and the configuration guide with a strict security model. It is not an ADR — it documents how to access and why. Architecture decisions remain in ADR-0001 and ADR-0004.
The vault
Section titled “The vault”“The vault” is the Google Workspace space. The “Personal Knowledge Vault” of the architecture is not something to build: it is the personal Google space — GDrive (the PARA structure: Projects, Areas, Resources, Archive) and twenty years of history, plus Gmail, Google Calendar and contacts. Nothing to migrate, nothing to synchronize. The vault already exists; Hermes needs the ability to access it in read mode.
Why the API and not an rclone mount?
Section titled “Why the API and not an rclone mount?”| API mode (Google Workspace skill) | Mount mode (rclone, Drive Bridge filesystem) | |
|---|---|---|
| Finding a document | drive search full-text → ID → docs get |
ls / grep in the tree |
Native Google documents (.gdoc, .gsheet) |
Read natively, structured | On-the-fly export (.md/.csv), latency, “empty” files without a special option |
| Gmail / Calendar / Contacts | Accessible via the API (read) | Not accessible — rclone only mounts Drive |
| Disk footprint | Zero (no local files) | Bounded VFS cache (1-2 GB) but FUSE to operate (systemd, monitoring) |
| Search | Google full-text (excellent), including in emails | Local browse + grep (Drive only) |
The dominant use case is “find me this document, this email, this deadline — read it, synthesize it.”
Use cases
Section titled “Use cases”| # | Use case | Drive | Gmail | Calendar | Contacts | Example |
|---|---|---|---|---|---|---|
| 1 | Find | ✅ | ✅ | — | — | “what did I write about X?” |
| 2 | Paperwork / administrative | ✅ | ✅ | ✅ | — | lease dossier: lease + template (Drive), exchanges with the landlord (Gmail), deadlines/visit (Calendar) |
| 3 | Dossier tracking | ✅ | ✅ | ✅ | — | “where is my dossier X?” — cross-reference recent emails + documents + appointments |
| 4 | Deadline watch | — | ✅ | ✅ | — | “what happens next week?”, missing documents |
| 5 | Templates / Resources | ✅ | — | — | — | find a model, create a new one from it |
| 6 | Project synthesis | ✅ | ✅ | — | — | feed a personal project with notes + emails |
| 7 | Writing (phase 2) | ✅ | — | — | — | draft a post backed by notes |
| 8 | Contacts | — | — | — | ✅ | find a contact, an address for a letter |
| 9 | Routines | ✅ | ✅ | ✅ | — | weekly review, morning brief (cron reads docs + unread emails + agenda) |
| 10 | Filing (phase 2) | ✅ | — | — | — | empty the notes-inbox and file into PARA |
Concrete example (Adeo Dev Summit notes) :
- API:
drive search "Adeo Dev Summit"→ ID →docs get ID→ read the content. - Mount (alternative):
ls /mnt/gdrive/Resources/→read_file .../adeo-dev-summit.md.
The “paperwork” case in detail (the most demanding, it justifies access to all three):
- Hermes searches Drive for the previous lease, the template, the supporting documents.
- Hermes searches Gmail for the email thread with the landlord (exchanges, evidence, attachments).
- Hermes checks the Calendar for deadlines (notice period, visit, move-in date).
- Hermes produces an unified synthesis of the dossier — and, in phase 2 (writing), copies the template, fills a draft and provides the review link.
Access scope (security first)
Section titled “Access scope (security first)”These are personal data. The scope is drawn for the least privilege possible.
How security is applied: OAuth scopes
Section titled “How security is applied: OAuth scopes”This is not a promise (“Hermes won’t write”): it is a technical limitation imposed by Google. The OAuth token that Hermes holds only contains certain rights, listed in its scopes. If the skill tries an action outside this list, Google answers 403 Forbidden — impossible to bypass.
What the token can do (read only) — summary
Section titled “What the token can do (read only) — summary”| Service | Scope granted | What it allows | Command example |
|---|---|---|---|
| Drive | drive.readonly |
Catalogue: search, list, metadata, download/export binary files (pdf, images, md) | drive search "lease", drive download DOC_ID |
| Docs | documents.readonly |
Read the content of native Google Docs (.gdoc) |
docs get DOC_ID |
| Sheets | spreadsheets.readonly |
Read the cells of native Google Sheets (.gsheet) |
sheets get SHEET_ID "Sheet1!A1:D10" |
| Gmail | gmail.readonly |
Search + read emails (body, sender, date, attachments) | gmail search "is:unread", gmail get MESSAGE_ID |
| Calendar | calendar.readonly |
Read events (dates, summaries, participants) | calendar list |
| Contacts | contacts.readonly |
Read contact details (names, emails, phones) | contacts list --max 20 |
gmail.readonly allows reading emails; calendar.readonly reading the agenda; contacts.readonly reading contacts. Without these scopes, files are visible but their content remains inaccessible.
What the token CANNOT do — summary
Section titled “What the token CANNOT do — summary”| Forbidden | Why |
|---|---|
| Send/edit/delete emails | gmail.send / gmail.modify scopes not granted — gmail send fails with 403 |
| Create/edit/delete events | calendar (full) scope not granted — calendar create fails with 403 |
| Write in Drive (create, copy, edit, move, delete, share, upload) | drive.readonly scope — drive upload / create-folder / delete / share fail with 403 |
| Modify a Docs or a Sheets | documents.readonly / spreadsheets.readonly scopes — docs create / append, sheets update fail |
In short: the token is a “on-site consultation only” pass for the library (Drive/Docs/Sheets), the mailbox (Gmail read), the agenda and the address book — with zero pen: no writing, no sending, no deletion. It is Google that imposes this limit on every call, not a rule of good conduct.
SOUL rule: if content read from the vault turns out to be sensitive (tax, medical, legal, banking), Hermes warns before sending it to an external LLM. The token grants access; the content read goes to the LLM provider — these are two different things. Gmail reading is now included in this scope: emails (often more sensitive than notes) follow the same rule.
Permission crescendo — read-only first, targeted write on demand
Section titled “Permission crescendo — read-only first, targeted write on demand”Read-only is the default posture: Hermes searches, reads, answers questions, and prepares material; every decision — and every send — stays with François. Write capabilities are added deliberately, one notch at a time, only when a real use case justifies them, never speculatively. Two mechanisms enforce this staircase:
- The consent screen is the gate. Google only grants the scopes François explicitly approves during the browser authorization (Step 5). Widening a scope always triggers a fresh consent flow — nothing writes before François clicks.
- Least privilege stays the norm. Each notch is granted for one validated use case and revoked when the use case disappears. Read-only remains the baseline.
Synthesis: stages, use cases, and config mapping
Section titled “Synthesis: stages, use cases, and config mapping”| Stage | Use case (when it becomes real) | Hermes can… | Scope(s) to add | Config change | Validation gate |
|---|---|---|---|---|---|
| 1. Read-only (current) | Questions, dossier synthesis, deadlines, routines | Search/read Drive, Docs, Sheets, Gmail, Calendar, Contacts; synthesize | — (the 6 readonly scopes) |
None — Step 4 as-is | François answers; no write capability exists |
| 2. Email drafts (next, on request) | “Prepare the answer, I’ll send it” | Scan a thread, write a draft into Gmail Drafts, notify — never send | gmail.compose |
Widen the SCOPES block in setup.py and google_api.py, then re-authorize (Step 5 flow) |
François opens the draft in Gmail, edits, presses send |
| 3. Doc/sheet drafts (on request) | Lease-renewal letter from template, weekly report sheet | Copy a template, fill a draft Doc/Sheet from notes | documents, spreadsheets + drive (write) |
Same widen + re-authorize | François reviews the link and content before use |
| Never (without a real, validated need) | Send directly, delete, share, change events | — | gmail.send, gmail.modify, full calendar, drive write |
Permanently out of scope | — |
Why gmail.compose and not gmail.send
Section titled “Why gmail.compose and not gmail.send”gmail.compose is the narrowest scope that covers Gmail Drafts. Honest caveat: Google’s description of this scope also includes sending — so in stage 2 the “François sends” guarantee is not a Google-imposed 403 the way read-only is. It rests on two local mechanisms:
- Agent rule: Hermes creates drafts, never sends — the SOUL boundary extended to drafting.
- Hard local block (optional hardening): the same SCOPES patch that makes read-only possible can make sending impossible at the CLI level —
google_api.pycan be patched sogmail sendexits with an explicit error even aftergmail.composeis granted.
Widening a notch — the runbook (one notch = one consent)
Section titled “Widening a notch — the runbook (one notch = one consent)”- Declare the use case (validated need, not speculation).
- Add the scope to the SCOPES block in both scripts (Step-4 patch mechanics — the skill is frozen, the patch survives updates).
- Re-run the authorization:
$GSETUP --revoke, then--auth-url(the consent screen lists the new scope — this is where François decides), then--auth-code. - Verify:
$GSETUP --checkshows the new scope; one real API call confirms it works. - Revocation is symmetric: remove the scope from the SCOPES block and re-authorize, or revoke entirely at https://myaccount.google.com/permissions.
Step-by-step guide: configure API access
Section titled “Step-by-step guide: configure API access”Total ~15 minutes once, almost all on the Google Cloud side, then a browser authorization.
Step 1 — Google Cloud project
Section titled “Step 1 — Google Cloud project”- https://console.cloud.google.com/projectselector2/home/dashboard → New project → name:
hermes-workspace(or similar). - Note the project ID (useful for step 3, test user).
Step 2 — Enable the APIs
Section titled “Step 2 — Enable the APIs”API Library → enable exactly 6 APIs (no more):
- Google Drive API
- Google Docs API
- Google Sheets API
- Gmail API
- Google Calendar API
- People API (for contacts)
Do NOT enable other APIs in phase 1. No scope, no attack surface.
Step 3 — Create the OAuth client + consent screen
Section titled “Step 3 — Create the OAuth client + consent screen”-
Consent screen (APIs & Services → OAuth consent screen):
- User type: External (personal Gmail account — “Internal” is reserved for Workspace spaces).
- App name:
Hermes Personal Assistant; support email: a reachable address. - Scopes: leave empty for now (added via the code in step 4).
- Test users: add the Gmail address of the concerned account here (essential for the authorization to pass).
What to fill / what to leave empty (Testing mode):
Field Action App name Required — Hermes Personal AssistantSupport email Required — a reachable address Developer contact email Required — same address App logo Optional — purely cosmetic (shown on the consent screen), no verification App domain / Authorized domains Skip — only relevant for web apps hosting OAuth redirect URIs on that domain. Entering a domain triggers an ownership verification (Search Console / DNS TXT), pointless for a Desktop client (redirects go to localhost)Privacy policy / Terms of service Skip — required only if the app is submitted for Google verification, never for Testing or a plain Production publish Error “Your app’s OAuth configuration is incomplete”: if the console shows it when creating the OAuth client, the consent screen (branding) has not been completed and saved yet. Return to the branding page, set at least the app name + support email, Save, and retry. The warning does not block the JSON download — but the browser authorization in step 5 will fail without a complete consent screen and the account in test users. Fix the branding before authorizing, not after.
-
Credentials → Create credentials → OAuth 2.0 Client ID → Type: Desktop application → Create.
-
Download the JSON file (
client_secret_....json) — never send it in a chat; place it on the machine (e.g.~/Downloads/) and provide its path.- The JSON never expires: it is the app’s identity (client ID + secret), downloaded once. Only the token (step 5) has a lifetime, and only in Testing mode.
-
Publishing status:
gmail.readonlyis a sensitive scope at Google. In Testing mode, the refresh token expires after 7 days. Weekly re-authorization is a ~1-minute flow (step 5 again — the JSON is not re-downloaded), acceptable for validation. Once phase 1 is validated, switch the app to Production (“Publish app”): the refresh token becomes durable.Testing vs Production:
Testing (validation) Production, unverified (phase 1 validated) Consent flow (browser auth, step 5) Required — and repeated weekly (token expires after 7 days) Required — but once (token becomes durable) Refresh token Expires after 7 days Durable (until revoked or ~6 months unused) Test users list Required (account must be added) Dropped — any account could consent in principle Users Hard cap of 100 test users Hard cap of 100 total users until verified Extra branding needed to publish — None — “Publish app” is a single click “Unverified app” warning — Cosmetic screen at consent time Google verification (logo, domain, privacy policy, security audit) Not needed Not needed for personal use — only required to remove the unverified warning for a public audience - Production ≠ public: publishing does not list or index the app anywhere; access still requires an explicit consent on the consent screen. In practice the 100-user cap on unverified apps with sensitive scopes limits it anyway. Publish once phase 1 is validated — nothing to decide today.
Step 4 — Restrict the scopes in the 2 scripts
Section titled “Step 4 — Restrict the scopes in the 2 scripts”The Hermes skill requests by default LARGE scopes (gmail.send, gmail.modify, full calendar, full drive, spreadsheets, documents). For read-only, patch both files with the read-only list:
~/.hermes/skills/productivity/google-workspace/scripts/setup.py — replace the SCOPES block (lines ~47):
SCOPES = [ "https://www.googleapis.com/auth/drive.readonly", "https://www.googleapis.com/auth/documents.readonly", "https://www.googleapis.com/auth/spreadsheets.readonly", "https://www.googleapis.com/auth/gmail.readonly", "https://www.googleapis.com/auth/calendar.readonly", "https://www.googleapis.com/auth/contacts.readonly",]~/.hermes/skills/productivity/google-workspace/scripts/google_api.py — same block (lines ~45):
SCOPES = [ "https://www.googleapis.com/auth/drive.readonly", "https://www.googleapis.com/auth/documents.readonly", "https://www.googleapis.com/auth/spreadsheets.readonly", "https://www.googleapis.com/auth/gmail.readonly", "https://www.googleapis.com/auth/calendar.readonly", "https://www.googleapis.com/auth/contacts.readonly",]Why both?
setup.pybuilds the authorization URL with the scopes,google_api.pyuses them on refresh. If only one is patched, the refresh may fail (invalid_scope) or the consent screen may re-request unwanted rights. No secret is touched — it is only the list of requested permissions, reduced to the essential.If a write operation becomes necessary (phase 2: copy a template, create a draft, prepare an email as draft), the scopes will need to be widened to
drive+documents+spreadsheets+gmail.compose(drafts only) and a new authorization will be required — never fullgmail.send/calendarwithout a real and validated need. Each widening is one notch of the permission crescendo (see above), gated by a fresh consent.
Why this patch is essential (and what happens if this step is skipped):
The OAuth token only has the scopes that were requested at authorization time. The authorization URL is built by setup.py from its SCOPES list:
- Without patch: the URL requests the wide scopes (
gmail.send,gmail.modify,calendar, fulldrive…) → the Google consent screen displays them → if the authorization is granted, the token can do everything (write, send emails, create events). No 403, the skill has the rights. - With patch: the URL only requests the 6
readonlyscopes → the token can never write → Google answers403 Forbiddenat the slightest write attempt.
The Google consent screen does not allow unchecking individual scopes. The patch is therefore THE mechanism that makes the token read-only, not an extra safety belt. Without it, the only barrier is the skill’s behavioral rule (“do not write without confirmation”) — a good intention, not a technical guarantee.
What happens at the next Hermes update? Nothing: local modifications are preserved.
Hermes synchronizes its bundled skills (hermes update) via a hash manifest (~/.hermes/skills/.bundled_manifest):
| Case | Sync behavior |
|---|---|
| Unmodified skill + new version available | Update applied |
| Locally modified skill (hash ≠ origin) | Update ignored — modification preserved |
| Skill deleted locally | Respected, not re-added |
Consequence: by patching the two scripts, the google-workspace skill is “frozen” at its current version — future updates of this skill will no longer be applied automatically. This is the intended behavior: the patch will be re-applied manually if a later version brings a useful fix.
To check at any time:
cat ~/.hermes/skills/.bundled_manifest | grep google-workspace # the hash changes after the patchStep 5 — Place the secret and authorize
Section titled “Step 5 — Place the secret and authorize”Working directory: none required.
setup.pyresolves all paths from its own location and$HERMES_HOME(default~/.hermes) — run the commands from any folder. Shell variable:GSETUPmust be defined in the same shell where you run$GSETUP ...— a new terminal session starts empty. If you see--auth-url: command not found, the variable is missing (redefine it). The commands below match the installedsetup.pyat the time of writing. The skill is frozen locally (Step 4), so these flags are stable; if the skill is ever updated upstream, verify with$GSETUP --helpbefore following the exact flags.
5.0 — One-time: Python environment
Section titled “5.0 — One-time: Python environment”The scripts need the Google client libraries, but the VPS system python3 is PEP-668 locked
(no pip). Create a dedicated venv (uv is installed) — once per machine:
uv venv ~/.hermes/venvs/gwsuv pip install --python ~/.hermes/venvs/gws/bin/python \ "google-api-python-client==2.194.0" "google-auth==2.55.1" \ "google-auth-oauthlib==1.3.1" "google-auth-httplib2==0.3.1" \ "httplib2==0.32.0" "pyasn1==0.6.4"All $GSETUP / $GAPI commands below use this venv’s python. If a command fails with
No module named pip or externally-managed-environment, you used system python3 — retry with
the venv python.
5.1 — Obtain the client secret JSON
Section titled “5.1 — Obtain the client secret JSON”The OAuth client is an identity (client ID + secret), not a token — it never expires. The JSON is its container. Two ways to get it:
- Download (recommended) — Google Cloud Console → APIs & Services → Credentials → OAuth 2.0 Client IDs → the client → Download JSON. The file bundles the client ID, the client secret and the fixed endpoint fields.
- Rebuild from client ID + secret — the JSON is a plain file; Google reads
client_idandclient_secretfrom it (the other fields have standard defaults). With the two values, write this exact structure — client type Desktop application → keyinstalled:
{ "installed": { "client_id": "....apps.googleusercontent.com", "project_id": "hermes-workspace", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_secret": "....", "redirect_uris": ["http://localhost"] }}Get the file onto the VPS, then either let the script place it (recommended, step 5.2 #1) or copy it manually yourself:
# Manual copy — the script expects EXACTLY this name (underscores) and path:# a differently-named file (e.g. google-client-secret.json) is ignored.cp ~/Downloads/client_secret.json ~/.hermes/google_client_secret.jsonchmod 600 ~/.hermes/google_client_secret.jsonNever paste the client secret in a chat or a terminal transcript — the file stays on disk.
⚠️ Client type check: the OAuth client must be a Desktop application (
installedkey). A Web application client (webkey) would refuse thehttp://localhost:1redirect (redirect_uri_mismatch). If the wrong type was created, recreate the client as Desktop in the console.
5.2 — Store, authorize, verify
Section titled “5.2 — Store, authorize, verify”# Define once per shell (does NOT persist between sessions). Python = the venv from 5.0.GSETUP="$HOME/.hermes/venvs/gws/bin/python $HOME/.hermes/skills/productivity/google-workspace/scripts/setup.py"
# 1. Store the client secret (copies the file into ~/.hermes/google_client_secret.json)$GSETUP --client-secret ~/Downloads/client_secret.json# → OK: Client secret saved to /home/hermes/.hermes/google_client_secret.json# (skip #1 if you already placed the file manually — the script will still find it)
# 2. Build the authorization URL (printed alone, ready to copy)$GSETUP --auth-url# → copy the printed URL, open it on the machine logged into the concerned Google# account (e.g. M1Max), approve the 6 readonly scopes on the consent screen# → the browser ends on "can't reach this site" at http://localhost:1 — NORMAL:# Google redirects to the loopback address with the code in the URL# → copy the FULL ADDRESS from the address bar (the one containing code=...)
# 3. Exchange the code (accepts the full redirect URL or the bare code)$GSETUP --auth-code "http://localhost:1/?code=....&scope=....&state=...."# → OK: Authenticated. Token saved to /home/hermes/.hermes/google_token.json
# 4. Verify the token locally — scopes must cover the 6 readonly$GSETUP --check# → AUTHENTICATED (or AUTHENTICATED (partial) if a scope is missing — at this stage,# if (partial) lists anything other than the 6 readonly scopes, it is a problem to fix)
# 5. Verify with a real API call (Calendar) — catches disabled client/account issues$GSETUP --check-live# → LIVE_CHECK_OK: Real API call succeeded.What each command does (read the script’s output with this in mind):
Persistent setup (optional) — GSETUP / GAPI die at the end of the shell session. To have them always available, append to ~/.bashrc (paths are stable: venv + frozen skill):
# Hermes Agent — Google Workspace skill CLI shortcuts (venv python: system python3 is PEP-668 locked)export GSETUP="$HOME/.hermes/venvs/gws/bin/python $HOME/.hermes/skills/productivity/google-workspace/scripts/setup.py"export GAPI="$HOME/.hermes/venvs/gws/bin/python $HOME/.hermes/skills/productivity/google-workspace/scripts/google_api.py"| Command | Effect |
|---|---|
--client-secret PATH |
Copies the JSON into ~/.hermes/google_client_secret.json. No network call — the JSON never expires. |
--auth-url |
Builds the consent URL from the SCOPES list in setup.py (Step 4) — this is where the read-only restriction becomes concrete: the consent screen can only offer the 6 readonly scopes. |
--auth-code |
Exchanges the single-use code for a refresh token (durable) + access token, stored in ~/.hermes/google_token.json. |
--check / --check-live |
Local token inspection / one real Calendar API call. |
Troubleshooting
| Symptom | Cause / fix |
|---|---|
error: unrecognized arguments: --format json |
The installed setup.py has no --format flag (the skill’s own SKILL.md still shows it). Just drop it — the output is already machine-readable. |
--auth-url: command not found |
GSETUP is empty in this shell — define it first (the variable does not persist between sessions). |
No module named pip / externally-managed-environment |
You used system python3 — retry with the venv python from 5.0. |
ERROR: No client secret stored. |
Step 1 skipped or wrong path, or the manual copy used the wrong filename (~/.hermes/google_client_secret.json, underscores). |
| Consent screen shows “OAuth configuration incomplete” | Step 3 branding not saved, or the account missing from Test users. |
redirect_uri_mismatch at authorization |
OAuth client is type Web application, not Desktop — recreate as Desktop. |
ERROR: OAuth state mismatch. |
Stale pending session (google_oauth_pending.json). Run --auth-url again for a fresh one. |
TOKEN_REVOKED / invalid_grant at --check |
Code already used / expired, or Testing-mode token expired (7 days). Re-run --auth-url + --auth-code — the client secret is not re-downloaded. |
AUTHENTICATED (partial) naming a non-readonly scope |
Wrong scopes granted at consent. $GSETUP --revoke, then redo the flow. |
Step 6 — Real test
Section titled “Step 6 — Real test”Once authenticated, Hermes can:
# Same venv python as GSETUP (5.0) — system python3 fails with 'No module named pip'GAPI="$HOME/.hermes/venvs/gws/bin/python $HOME/.hermes/skills/productivity/google-workspace/scripts/google_api.py"
# Drive search$GAPI drive search "Adeo Dev Summit" --max 5
# Read a native Google Doc$GAPI docs get DOC_ID
# Read a Sheets range$GAPI sheets get SHEET_ID "Sheet1!A1:D10"
# Search + read emails$GAPI gmail search "is:unread" --max 10$GAPI gmail get MESSAGE_ID
# Agenda (next week)$GAPI calendar list
# Contacts$GAPI contacts list --max 20
# One-off export of a doc to .md / .pdf$GAPI drive download DOC_ID --export-mime text/plain --output ~/doc.mdSecurity: what NOT to do
Section titled “Security: what NOT to do”- Never run the authorization with the default unpatched scopes:
gmail.send,gmail.modify, fullcalendar, fulldrivegive write and send capabilities to a token that does not need them. - Never send the
client_secret.jsonor the token in a chat, a ticket, a public repo. The refresh token is a permanent access key to the Workspace (Drive + emails + agenda + contacts). - Never commit
~/.hermes/google_token.jsonorgoogle_client_secret.jsonin a repo (even private). They stay in~/.hermes/(outside the repo), protected by the folder permissions (mode 0600/0700). - Backup: the daily backup (
backup.sh→ hermes-backup repo) never collectsgoogle_token.jsonorgoogle_client_secret.json— no secret reaches the GitHub repo. A lost VPS therefore costs a re-authorization (Step 5 again, ~5 min); the client secret is the safe recovery point (kept on the local machine, and downloadable again from the Google console). Note: the runningbackup.shperforms no encryption — ADR-0008’s age-encrypted pipeline describes the design, not the implemented script. The token stays out of the backup until encryption exists; plaintext would defeat the purpose. - Dashboard:
hermes.ducatillon.netmust never display these files (rule already in place: no.env, no secrets in the UI). - Revocation: two levels —
$GSETUP --revoke(removes the local token) and https://myaccount.google.com/permissions (revokes access on the Google side, definitive). When in doubt, do both. The client secret can also be regenerated in the Google console. - Rotation: if the VPS is compromised or if doubt arises, revoke on the Google side + regenerate the client secret + re-authorize.
Limits and pitfalls
Section titled “Limits and pitfalls”- No writing in phase 1: the skill can “upload”, “send”, “create”, etc. — these commands will fail with the readonly scopes: this is the intended guardrail.
- No filesystem view: free tree exploration (“show me what’s there”) is not possible in phase 1. If this need becomes real → phase 2: read-only rclone mount with the same OAuth client (inject the client_id/secret into the rclone remote to have only one authorization to manage).
- Google search ≠ local search:
drive search/gmail searchare full-text on the Google side (very good), but do not cover everything a local grep would (binary file content, OCR…). - Testing refresh token = 7 days: as long as the OAuth app is in Testing mode, weekly re-authorization (same flow as step 5, ~1 minute — the
client_secret.jsonis not re-downloaded, it never expires; only the token does). Switching to Production (even unverified) makes the refresh token durable — the recommended path once phase 1 is validated. - Sensitive scopes (Gmail):
gmail.readonlyis classified as sensitive by Google. In unverified production, an “unverified app” warning is displayed and a 100-user cap applies — no impact for strictly personal use. drive.filevsdrive.readonly: the choice isdrive.readonlybecausedrive.fileonly covers files created/opened by the application — unusable to read 20 years of PARA history.- Rate limits: Google quotas are comfortable for targeted search/read. Listing/downloading the whole Drive is forbidden (no “mirror” usage).
References
Section titled “References”- Hermes local skill:
~/.hermes/skills/productivity/google-workspace/(official doc: https://hermes-agent.nousresearch.com/docs/user-guide/skills/google-workspace) - Architecture skill:
google-workspace-agent-integration(agent ↔ Workspace integration pattern, writing phases) - ADR-0001 (Drive = SSOT) · ADR-0004 (Drive docs / GitHub code) · ADR-0007 (plaintext secrets + OS hardening)