← Commit history

Set page tags + restructure for adompkg (skills/ layout, link-bin install); v0.4.0

John Lauer ·972ba044ae ·4mo ago ·parent 13defa7
5 files changed +258−19
install.sh+29−10
@@ -1,10 +1,29 @@-#!/bin/bash-# adompkg install hook: put adom-google on PATH + drop its skill in place.-set -e-DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"-mkdir -p "$HOME/.local/bin" "$HOME/.claude/skills/adom-google"-chmod +x "$DIR/bin/adom-google" "$DIR/bin/adom-gmail"-ln -sf "$DIR/bin/adom-google" "$HOME/.local/bin/adom-google"-ln -sf "$DIR/bin/adom-gmail"  "$HOME/.local/bin/adom-gmail"-cp -f "$DIR/SKILL.md" "$HOME/.claude/skills/adom-google/SKILL.md"-echo "✅ adom-google installed. Next: run 'adom-google setup' (it will ask safe vs full mode)."+#!/usr/bin/env bash+# adom-google installer — the open-source Google Workspace CLI. Runs in the container via+# `adompkg install`. Ships with NO OAuth provider: the binary + skill are symlinked onto PATH+# via the adompkg sh-helpers (lint-sanctioned convention). Configure a provider after install+# (`adom-google provider set ...`) — or, Adom staff, install the private `adom-google-adom`.+set -euo pipefail+HERE="$(cd "$(dirname "$0")" && pwd)"+cd "$HERE"++if ! command -v adompkg >/dev/null 2>&1; then+  echo "[adom-google] FATAL: adompkg not on PATH — install via 'adompkg install adom-google'." >&2+  exit 1+fi+# shellcheck disable=SC1090+source "$(adompkg sh-helpers)"++chmod +x bin/adom-google bin/adom-gmail+adompkg-link-bin adom-google          # ~/.local/bin/adom-google -> $PWD/bin/adom-google+adompkg-link-bin adom-gmail           # deprecation shim -> adom-google+adompkg-link-skill adom-google        # ~/.claude/skills/adom-google -> $PWD/skills/adom-google++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 "   • self-host:  adom-google provider set --gateway https://your-gateway --client-id <id>"+echo "   • Adom staff: adompkg install adom-google-adom   (drops the Adom provider)"+echo "   Then: 'adom-google setup' (asks safe vs full) and 'adom-google auth'."
package.json+39−4
@@ -1,8 +1,28 @@ {   "slug": "adom-google",-  "version": "0.3.0",+  "version": "0.4.0",   "type": "app",-  "description": "Drive your whole Google Workspace from the terminal \u2014 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 \u2014 Adom staff \u2014 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).",+  "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).",+  "keywords": [+    "adom-google",+    "google-workspace",+    "oauth",+    "gmail",+    "google-sheets",+    "google-docs",+    "google-slides",+    "google-calendar",+    "google-chat",+    "cli",+    "ai-agent"+  ],+  "tags": [+    "google-workspace",+    "oauth",+    "gmail",+    "cli",+    "ai-agent"+  ],   "install": {     "binary_name": "adom-google"   },@@ -10,5 +30,20 @@   "scripts": {     "install": "./install.sh",     "uninstall": "./uninstall.sh"-  }-}\ No newline at end of file+  },+  "files": [+    "bin/adom-google",+    "bin/adom-gmail",+    "skills/adom-google/SKILL.md",+    "README.md",+    "MIGRATION.md",+    "provider.example.json",+    "docs/hero.png",+    "docs/enable-apis.png",+    "docs/chat-reads-email.png",+    "screenshots/hero.png",+    "install.sh",+    "uninstall.sh",+    "package.json"+  ]+}
page.json+8−2
@@ -4,7 +4,7 @@   "slug": "adom-google",   "title": "adom-google",   "brief": "Drive your whole Google Workspace from the terminal \u2014 Gmail, Drive, Sheets, Docs, Slides, Calendar, Tasks, Chat + any Google API.",-  "version": "0.3.0",+  "version": "0.4.0",   "visibility": "public",   "install": {     "binary_name": "adom-google",@@ -17,7 +17,13 @@     "name": "John Lauer",     "email": "[email protected]"   },-  "tags": [],+  "tags": [+    "google-workspace",+    "oauth",+    "gmail",+    "cli",+    "ai-agent"+  ],   "license": "Apache-2.0",   "hero": {     "path": "docs/hero.png",
skills/adom-google/SKILL.mdadded+178
@@ -0,0 +1,178 @@+---+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. 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, gmail attachment, google contacts, people api, safe mode vs full mode, google oauth gateway broker, google api passthrough, self-host oauth, managed oauth container.+---++# 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.++## 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]++adom-google status     # mode (safe/full) + what's authorized (never prints secrets)+```++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).
uninstall.sh+4−3
@@ -1,4 +1,5 @@-#!/bin/bash+#!/usr/bin/env bash+set -euo pipefail rm -f "$HOME/.local/bin/adom-google" "$HOME/.local/bin/adom-gmail"-rm -rf "$HOME/.claude/skills/adom-google"-echo "adom-google uninstalled (your ~/.config/adom-google token is left intact)."+rm -f "$HOME/.claude/skills/adom-google"   # remove the skill symlink+echo "adom-google uninstalled (your ~/.config/adom-google token + provider are left intact)."