Download

name: adom-google description: "Programmatic read+write access to a user's WHOLE Google Workspace — Gmail, Contacts, Drive, Sheets, Docs, Slides, Calendar, Tasks, Google Chat — plus a generic api passthrough to hit ANY Google API. CLI + OAuth only (no browser automation). Public/open-source; used daily by the Adom team. Auth is the account-specific part — Adom employees use it out of the box; other orgs either self-host their own OAuth gateway or email [email protected] for a managed OAuth container (~$5/mo). First run just authorizes with FULL Workspace access (no mode question to ask the user). Use when setting up/installing adom-google, authorizing Google, reading Gmail attachments/.ics, creating/listing/searching Contacts, editing Sheets/Docs/Slides, Drive files, Calendar events, posting to Google Chat, or any Google Workspace automation. For a brand-new ORG that needs its own Google OAuth client (non-Adom team getting started), use the companion adom-google-onboarding skill — it drives the user's real browser through Google Cloud Console and auto-configures everything. Trigger words — adom-google, install adom-google, set up adom-google, configure adom-google, google workspace cli, edit google sheet/doc/slides, drive upload, calendar event, post to google chat, post to chat as me, message colby, send a chat, dm , gmail attachment, google contacts, people api, full workspace access, google oauth gateway broker, google api passthrough, self-host oauth, managed oauth container, onboard my org to google, set up google workspace for my company/team, configure google oauth for , get my company on adom-google, create our google cloud oauth client."

adom-google

One CLI for a user's Google Workspace from inside an Adom container. REST-direct — it calls the Google APIs straight, owns auth via the Adom OAuth Gateway, and stores a long-lived refresh token 0600. No browser automation, no per-user Google Cloud project.

adom OAuth layer   one shared Adom OAuth client + Gateway redirect → refresh token (0600)
adom-google        modular wrapper: gmail · contacts · (drive/calendar = +1 scope line)
Google REST APIs   called directly (gmail.googleapis.com, people.googleapis.com, …)

Why REST-direct (not a base-CLI shim)

We evaluated wrapping a base CLI — Google's googleworkspace/cli (gws, Rust, Discovery-driven, JSON, agent skills), gog/gogcli (Go), GAM/GAMADV-XTD3, the Gemini CLI, and gcloud. Conclusion: call the REST APIs directly.

  • Auth is the part Adom must own anyway. One shared internal OAuth client + the Gateway's static redirect + a long-lived refresh token is Adom-specific; no base CLI speaks it. Adopting one means reverse-engineering its keyring/credential format to inject our token — more work than one requests.post to the token endpoint.
  • The data surface we need is tiny and stable. gmail.readonly attachment reads + a handful of People API calls. Each is one HTTPS request. A base CLI adds a process boundary, an install/version dependency, and output coupling for no benefit at this size.
  • No per-user GCP project. REST-direct with the shared Adom client satisfies the hard constraint. gws/gog/GAM all push users toward their own OAuth client or gcloud.
  • Gemini CLI is the wrong layer (an AI coding agent, not a Workspace data client) and gcloud can't touch Workspace user data (GCP-only). gws is the closest "official" Google Workspace CLI and is a reasonable future accelerator if we ever need broad Sheets/Docs/Chat coverage — but for Gmail+Contacts it's overkill.

The code is structured so a gws backend could be slotted behind the same subcommands later; today the win is zero new runtime dependencies and a clean migration from adom-gmail.

Onboarding a brand-new org (give a team its OWN Google client)

If a non-Adom org wants to get going on their own, they need their own Google OAuth client. Don't make them slog through Google Cloud Console by hand — use the adom-google-onboarding skill, which drives the user's real logged-in browser (adom-desktop nbrowser_*) through the console, auto-fills the fields (especially the redirect URI), captures the new client, and wires it in RELAY mode (their secret stays 0600 on their box; Adom only lends a shared callback URL). Kick it off with adom-google onboard --org <slug> (or --json for the exact values). Adom employees skip all this — they install adom-google-adom and just run auth.

Install

adom-wiki pkg install adom/adom-google     # copies the CLI to ~/.local/bin + skills to ~/.claude/skills

install.sh copies real files (no symlinks): the binaries land in ~/.local/bin and each skill in ~/.claude/skills/<name>/. Runtime: Python 3 + requests (already in the container). The Gateway WebSocket handshake is implemented in the stdlib (no ws/websockets package).

First-run setup — just run adom-google auth (no mode quiz)

When setting up adom-google for the first time (it's unconfigured — adom-google setup or any command prints a Hint: block), do NOT ask the user any scope/mode question. Full Workspace access is the default and the tool "just works." Simply run:

adom-google auth

This grants full Workspace scopes (gmail.modify, contacts, drive, spreadsheets, documents, presentations, calendar, tasks, chat.messages, chat.spaces.readonly) plus the generic adom-google api <url> passthrough for any Google API. It prints a Google consent URL; the user clicks Allow once.

Open the consent URL as ONE value — never unquoted in a shell. It contains &; if you drop it into an unquoted shell/open command the shell truncates it at the first &, Google receives only client_id, and shows "Error 400: invalid_request — missing response_type" (this bit real users). Safe: hand the URL to a browser tool with the URL inside JSON (adom-desktop nbrowser_open_window '{"sessionId":"gauth","url":"<URL>"}'), or single-quote the whole URL. The adom-google auth output prints the ready-to-paste safe command for you.

The user has ~10 minutes to click Allow. The gateway keeps the sign-in session alive with a WebSocket heartbeat, so there's no rush. If they ever see a "Session Expired" page, just re-run adom-google auth.

(Advanced/rare: adom-google auth --safe grants narrower create-only scopes — only the Drive/Sheets/Docs/Slides files this app makes, no "delete all". Do NOT prompt for it; use it only if the user explicitly asks to minimize scopes.)

adom-google auth            # authorize (FULL Workspace access — the default; ~10 min to approve)
adom-google auth --safe     # advanced: narrower create-only scopes (no delete-all)
adom-google auth --manual   # fallback: desktop-client paste flow
adom-google auth-code '<code>'   # finish --manual
adom-google status          # shows access + what's authorised (never prints secrets)

How Adom handles the OAuth (Gateway broker flow)

Google requires one fixed redirect_uri, but every container has a unique hostname. The Adom OAuth Gateway gives one static callback for all containers and acts as a confidential-client token broker, so the client secret never reaches user containers:

  1. adom-google auth builds the Google consent URL with redirect_uri = <your gateway>/callback (from the resolved provider) and a random state, and opens a WebSocket to the Gateway registering {state, app:"adom-google", exchange:true}.
  2. The user approves in their browser (their own Google Workspace account).
  3. Google redirects to the Gateway /callback; the Gateway does the code→token exchange itself (using the secret it alone holds) and pushes {type:"tokens", …} over the WS.
  4. The container stores only the refresh token 0600 — no client secret. Later refreshes go through the gateway's POST /refresh, so the secret stays on the gateway forever.

The package ships NO provider — no gateway URL, no client_id, nothing Adom-specific. The provider ({gateway_url, client_id, app}) is resolved at runtime, later wins: bundled provider.json → legacy credentials.json~/.config/adom-google/provider.json → env (OAUTH_GATEWAY_URL, ADOM_GOOGLE_CLIENT_ID/SECRET/APP). With none configured, every command prints the NO_PROVIDER hint. Adom employees install the private adom-google-adom package, which writes the Adom provider (gateway + Internal client_id) to ~/.config/adom-google/provider.json; self-hosters run adom-google provider set --gateway <url> --client-id <id>. The client secret is never in any package — in broker mode it lives only on the gateway's creds/<app>.json (0600).

If the Gateway is down, auth --manual uses a desktop-client loopback paste flow (that one exchanges locally and does need a client secret via init).

Getting auth as a NON-Adom user (this tool is public)

The code is open source; the OAuth app + gateway are account-specific. If a user is not on Adom's Workspace, the bundled Internal client won't let them sign in — Google blocks non-adom.inc accounts at consent. Two options to surface to them:

  1. Managed by Adom (easiest): email [email protected] — Adom stands up a dedicated OAuth gateway container for their org (~$5/mo) wired to a Google client for their domain; they then get the full adom-google capability set, same as the Adom team.
  2. Self-host (DIY): fork the repo; create their own Google Cloud project + OAuth client (Internal to their Workspace), enable the APIs they need, run the open-source OAuth gateway (john/service-oauth) with their client secret in creds/<app>.json, then point the CLI at theirs: adom-google provider set --gateway https://their-gateway --client-id <their-id> (writes ~/.config/adom-google/provider.json). Google Chat also needs a one-time Chat-app configuration (name/avatar/description) in the Cloud console's Chat API → Configuration tab, or spaces.* calls 404 with "Chat app not found".

Commands

# Generic passthrough — ANY Google API, signed with the user's token. This is the workhorse:
# Sheets/Docs/Slides/Drive/Calendar/Chat/Tasks/YouTube all go through here, zero per-API code.
adom-google api <url> [-X METHOD] [-d '<json>'|@file] [-q key=val ...] [--raw]
#   e.g. create a sheet:   adom-google api -X POST https://sheets.googleapis.com/v4/spreadsheets -d '{"properties":{"title":"BOM"}}'
#        post to Chat:      adom-google api -X POST https://chat.googleapis.com/v1/spaces/<id>/messages -d '{"text":"shipped"}'
#        calendar event:    adom-google api -X POST https://www.googleapis.com/calendar/v3/calendars/primary/events?sendUpdates=all -d '{...}'

# Gmail (read; behaviour-compatible with old adom-gmail)
adom-google gmail attachments <messageId>
adom-google gmail read <messageId> <attachmentId> [-o file]
adom-google gmail ics  <messageId>

# Contacts (People API; read + write)
adom-google contacts create --name "First Last" [--org O] [--title T] \
    [--email [email protected] ...] [--phone +1... ...] [--notes "..."] [--json]
adom-google contacts list [--limit N] [--json]
adom-google contacts search <query> [-v] [--json]

# Google Chat — posts as the SIGNED-IN USER (their own identity, NO attribution prefix).
# This is distinct from `adom-gchat`, which posts via an org webhook with "(on behalf of …)".
adom-google chat spaces [--json]                          # list your spaces (id · type · name)
adom-google chat dm <email> [--json]                      # resolve the 1:1 DM space with a person
adom-google chat send --to <space-id|email> "<text>"      # post; email auto-resolves the DM
#   "<text>" may be '-' (stdin) or '@file'; add --thread <key> to reply in a thread
adom-google chat read --from <space-id|email> [--limit N] [--json]   # recent messages

adom-google status     # access (full/safe) + what's authorized (never prints secrets)
adom-google accounts   # list all configured accounts (work + personal, isolated)

Multiple accounts (v0.8.0+) — e.g. work + personal, fully isolated

adom-google --account <name> <cmd> (or ADOM_GOOGLE_ACCOUNT=<name>) runs ANY command against a named account stored at ~/.config/adom-google/accounts/<name>/ with its OWN refresh token, mode, and provider.json (inherits the default account's provider unless overridden — a Workspace-Internal OAuth client can't sign in a gmail.com user, so a personal account typically needs its own External client via --account <name> provider set). The default (unnamed) account keeps the original paths — existing setups are untouched. First-run auth for a new account follows the SAME ask-the-user each account authorizes independently: adom-google --account personal auth.

chat vs adom-gchat: adom-google posts as you and does the full Chat lifecycle — post, read, edit, delete, react — as your own Google identity (clean, no prefix). For an attributed bot post to a team space (deploys/alerts as "Kel (on behalf of john)"), use adom-gchat, the post-only webhook bot. Rule of thumb: posting as yourselfadom-google chat; an attributed bot announcementadom-gchat. The chat.messages + chat.spaces.readonly scopes are in both safe and full mode, so this works out of the box once authorized.

We use Google Contacts to store service identities (portal/account metadata for an API vendor), e.g. the JPMorgan Payments developer portal. Keep secrets (client secrets, portal passwords) in a password manager — never in a contact's notes.

Adding Drive / Calendar

Add the scope to SCOPES at the top of bin/adom-google, re-run adom-google auth, and add a few REST calls against www.googleapis.com/drive/v3 or /calendar/v3. Token refresh and the _request() helper are already generic.

Security

  • Config ~/.config/adom-google/config.json is created 0600; the client secret and refresh token are never echoed to stdout/stderr or logs (status prints presence only).
  • The Gateway only ever sees the short-lived auth code, never tokens or the client secret.

Migration from adom-gmail

See MIGRATION.md. Short version: adom-gmail is now a forwarding shim; its old ~/.config/adom-gmail/config.json is auto-imported on first adom-google run (gmail.readonly only — run adom-google auth once to add the contacts scope).