← Commit history

Publish 0.6.5

John Lauer ·f0d91da633 ·3mo ago ·parent 7f2add6
5 files changed +226−23
README.md+1−1
@@ -378,4 +378,4 @@ that it's a service Adom runs. Built and battle-tested at **[Adom](https://adom.inc)**. Questions, or want us to host your org's OAuth? **[email protected]**. -> [Read the full SKILL.md source](/blob/app/adom-google/skills/adom-google/SKILL.md)+> [Read the full SKILL.md source](/blob/app/adom-google/SKILL.md)
SKILL.mdadded+204
@@ -0,0 +1,204 @@+---+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). On FIRST-RUN SETUP you MUST ask the user to pick safe mode (no delete) vs full mode (incl. delete) before authorizing. 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 <person>, gmail attachment, google contacts, people api, safe mode vs full mode, 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 <org>, 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++`install.mjs` symlinks the CLI onto PATH automatically. Manual:++```bash+ln -sf <gallia>/skills/adom-google/bin/adom-google ~/.local/bin/adom-google+ln -sf <gallia>/skills/adom-google/bin/adom-gmail   ~/.local/bin/adom-gmail   # deprecation shim+```++Runtime: Python 3 + `requests` (already in the container). The Gateway WebSocket handshake+is implemented in the stdlib (no `ws`/`websockets` package).++## First-run setup — YOU MUST ASK THE USER which mode (REQUIRED)++When setting up adom-google on a user's container for the first time (it's unconfigured —+`adom-google setup` or any command prints a `Hint:` block saying so), **do NOT pick a mode+for them. Ask.** Use `AskUserQuestion` with these two options:++- **Safe mode (recommended default)** — read/organize Gmail, full Contacts, Calendar events,+  Tasks, post+read Google Chat, and only the Drive/Sheets/Docs/Slides files *this app+  creates*. Scopes: `gmail.modify, contacts, calendar.events, tasks, chat.messages,+  chat.spaces.readonly, drive.file`. The Google consent screen shows **no "see & delete ALL+  your files"** — nothing scary.+- **Full mode** — everything safe mode has, but the narrow Drive/Sheets/Docs/Slides/Calendar+  scopes are swapped for broad read/write/**delete** across **all** the user's existing+  files and calendars. Scopes add `drive, spreadsheets, documents, presentations, calendar`.+  Only if they explicitly want full control.++Both modes also expose the generic `adom-google api <url>` passthrough, which can hit **any**+Google API the granted scopes allow (Sheets/Docs/Slides/Drive/Calendar/Chat/Tasks/YouTube/…).++Then run exactly one:+```bash+adom-google auth          # safe mode  (user picked safe)+adom-google auth --full   # full mode  (user picked full — includes delete)+```+The CLI emits this same instruction as `Hint:` lines whenever it's unconfigured, so you'll+be reminded at the exact moment of need even if this skill has been compacted out of context.++## Auth — one consent, long-lived refresh token++```bash+adom-google auth            # SAFE mode (default): no "delete all" on the consent screen+adom-google auth --full     # FULL mode: broad access incl. delete (trusted users)+adom-google auth --manual   # fallback: desktop-client paste flow (combine with --full)+adom-google auth-code '<code>'   # finish --manual+adom-google status          # shows mode + 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++```bash+# 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     # mode (safe/full) + what's authorized (never prints secrets)+```++**`chat` vs [adom-gchat](/adom/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](/adom/adom-gchat)**, the post-only webhook bot.+Rule of thumb: posting *as yourself* ⇒ `adom-google chat`; an *attributed bot announcement* ⇒+`adom-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](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).
install.sh+17−18
@@ -1,11 +1,10 @@ #!/usr/bin/env bash-# adom-google installer — the open-source Google Workspace CLI. Runs in the container via+# adom-google installer — the open-source Google Workspace CLI + its skills. Runs via # `adom-wiki pkg install adom/adom-google`. Ships with NO OAuth provider. #-# We install by COPYING real files (NO symlinks): the binaries land in ~/.local/bin and the-# skills land in ~/.claude/skills as real directories. Copies are self-contained — they keep-# working if the module dir moves or is pruned, and the agent reads a real file (no symlink-# chains, no dangling links). Re-run the installer (pkg update) to pick up a new version.+# Skillpack layout: the MAIN skill is the repo-root SKILL.md (adom-google); sub-skills live+# under skills/<name>/SKILL.md (adom-google-onboarding). We install by COPYING real files+# (NO symlinks): binaries → ~/.local/bin, each skill → ~/.claude/skills/<name>/SKILL.md. set -euo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" cd "$HERE"@@ -14,34 +13,34 @@ BIN_DIR="$HOME/.local/bin" SKILLS_DIR="$HOME/.claude/skills" mkdir -p "$BIN_DIR" "$SKILLS_DIR" -install_bin() {            # install_bin <name>  → copy bin/<name> to ~/.local/bin/<name>+install_bin() {            # copy bin/<name> → ~/.local/bin/<name> (clears any old file OR symlink)   local name="$1"-  rm -rf "$BIN_DIR/$name"               # clear any prior file OR symlink from an old install+  rm -rf "$BIN_DIR/$name"   install -m 0755 "bin/$name" "$BIN_DIR/$name"   echo "   bin   → $BIN_DIR/$name" } -install_skill() {         # install_skill <slug> → copy skills/<slug>/ to ~/.claude/skills/<slug>/-  local slug="$1"-  [ -f "skills/$slug/SKILL.md" ] || { echo "[adom-google] WARN: skills/$slug/SKILL.md missing" >&2; return; }-  rm -rf "$SKILLS_DIR/$slug"            # clear any prior dir OR symlink from an old install-  mkdir -p "$SKILLS_DIR/$slug"-  cp -R "skills/$slug/." "$SKILLS_DIR/$slug/"-  echo "   skill → $SKILLS_DIR/$slug/"+install_skill() {          # install_skill <install-name> <source SKILL.md path>+  local name="$1" src="$2"+  [ -f "$src" ] || { echo "[adom-google] WARN: $src missing" >&2; return; }+  rm -rf "$SKILLS_DIR/$name"            # clear any prior dir OR symlink from an old install+  mkdir -p "$SKILLS_DIR/$name"+  cp "$src" "$SKILLS_DIR/$name/SKILL.md"+  echo "   skill → $SKILLS_DIR/$name/SKILL.md" }  echo "[adom-google] installing (copies, not symlinks)…" install_bin   adom-google-install_bin   adom-gmail              # deprecation shim → forwards to adom-google-install_skill adom-google-install_skill adom-google-onboarding  # guided org-onboarding skill (drives the browser)+install_bin   adom-gmail                                            # deprecation shim → adom-google+install_skill adom-google             SKILL.md                      # MAIN skill (repo root)+install_skill adom-google-onboarding  skills/adom-google-onboarding/SKILL.md   # sub-skill  case ":$PATH:" in   *":$HOME/.local/bin:"*) ;;   *) echo "[adom-google] NOTE: $HOME/.local/bin is not on PATH — add it to your shell rc." ;; esac -echo "✅ adom-google installed. It ships with NO OAuth provider — set one up:"+echo "✅ adom-google installed (main skill + onboarding sub-skill). It ships with NO OAuth provider — set one up:" echo "   • new org (self-serve): adom-google onboard   (the AI drives your browser through Google setup)" echo "   • self-host (manual):   adom-google provider set --gateway https://your-gateway --client-id <id>" echo "   • Adom staff:           adom-wiki pkg install adom/adom-google-adom   (drops the Adom provider)"
package.json+2−2
@@ -1,6 +1,6 @@ {   "slug": "adom-google",-  "version": "0.6.4",+  "version": "0.6.5",   "type": "app",   "license": "MIT",   "description": "Drive your whole Google Workspace from the terminal — Gmail, Contacts, Drive, Sheets, Docs, Slides, Calendar, Tasks, Google Chat, plus a generic `api` passthrough to hit ANY Google API. Open source, ships with NO OAuth provider baked in: point it at your own token-broker gateway (`provider set`), buy a managed one, or — Adom staff — install the private adom-google-adom provider. One-click OAuth, no client secret on your machine. First run asks safe mode (no delete) vs full mode (incl. delete).",@@ -69,7 +69,7 @@   "files": [     "bin/adom-google",     "bin/adom-gmail",-    "skills/adom-google/SKILL.md",+    "SKILL.md",     "skills/adom-google-onboarding/SKILL.md",     "README.md",     "LICENSE",
page.json+2−2
@@ -4,7 +4,7 @@   "slug": "adom-google",   "title": "Adom Google",   "brief": "Drive your whole Google Workspace from the terminal — Gmail, Drive, Sheets, Docs, Slides, Calendar, Tasks, Chat + any Google API.",-  "version": "0.6.4",+  "version": "0.6.5",   "visibility": "public",   "install": {     "binary_name": "adom-google"@@ -96,7 +96,7 @@   "files": [     "bin/adom-google",     "bin/adom-gmail",-    "skills/adom-google/SKILL.md",+    "SKILL.md",     "skills/adom-google-onboarding/SKILL.md",     "README.md",     "LICENSE",