← Commit history
README.md+77−17
@@ -57,11 +57,11 @@ source for the gateway: [`adom/service-oauth`](https://wiki.adom.inc/adom/servic  --- -## Getting your own auth — three ways+## Getting your own auth — pick your path  The **code** is open source and ships with **no OAuth provider baked in** — it has no idea which gateway or Google client to use until you give it one. That "provider" (gateway URL +-client_id) is the only account-specific piece. Pick your path:+client_id) is the only account-specific piece. Four ways to get one:  ### 1. You're at Adom → just use it ```bash@@ -72,17 +72,29 @@ adom-google auth                   # one Allow click — done (`adom-google-adom` is a tiny private, Adom-only package that writes the Adom provider; the public `adom-google` package contains none of Adom's infrastructure.) -### 2. Any other company / team → let Adom host your OAuth (easiest) 💵-Email **[email protected]**. We spin up a dedicated OAuth gateway container for your org-(~**$5/month** for the service container), wire it to a Google client for your domain, and-your whole team gets the **full adom-google capability set — exactly what the Adom team-uses every day**. You install the CLI, click Allow once, and you're driving your Workspace.-No Cloud-console wrestling, no secret management.+### 2. ⭐ Any other org → self-serve onboarding (the AI sets up Google *for* you)+This is the one most teams want. You don't touch the Google Cloud Console — **your AI drives+your own browser through it**, auto-filling every field, while you watch and click the couple+of buttons Google reserves for a human: -### 3. Self-host it → fully DIY (free, more setup)-Fork this repo and stand up your own:+```bash+adompkg install adom-google         # the open-source CLI+adom-google onboard --org <your-org>   # the AI takes it from here ↓+```++Your team gets its **own** Google OAuth client (you own it, you control it), and your+**client secret never leaves your machine** — Adom only lends a shared callback URL. Full+walkthrough with screenshots below: **[Onboarding your org](#onboarding-your-org--the-ai-sets-up-google-for-you)**.++### 3. Prefer we run it for you → managed 💵+Email **[email protected]**. We stand up a dedicated OAuth gateway container for your org+(~**$5/month**) wired to a Google client for your domain; your whole team gets the full+capability set with zero console wrestling and zero secret management on your side.++### 4. Self-host the gateway too → fully DIY (free, most control)+Fork this repo and run the open-source gateway yourself: - A Google Cloud project + OAuth client (Internal to your Workspace for long-lived tokens),-  with the Gmail/Drive/Sheets/Docs/Slides/Calendar/Tasks/People/Chat APIs enabled.+  with all nine APIs enabled (the onboarding flow's one-click enable URL does this for you). - The open-source OAuth gateway ([`adom/service-oauth`](https://wiki.adom.inc/adom/service-oauth))   on a small container, with your client's secret in `creds/<app>.json`. - Point the CLI at **your** gateway + client (writes `~/.config/adom-google/provider.json`, 0600):@@ -91,14 +103,62 @@ Fork this repo and stand up your own:   adom-google provider show     # verify what's resolved (env > provider.json > bundled)   ``` -**Enabling the APIs** — in the Cloud Console go to **APIs & Services → Library**, search each-one, and click **Enable**. You want all nine:+---++## Onboarding your org — the AI sets up Google *for* you++> **Why this is a big deal.** Once your team is connected, your AI can **read your email**,+> **post in Google Chat on your behalf**, **compose Google Docs**, **build Google Sheets and+> Slides**, and **search Google Drive** — all from the terminal, all in one workflow. Teams+> describe it as a *game-changer*: the AI stops being a chat box and becomes a coworker that+> actually operates your Workspace. The only thing standing between a new org and that unlock+> is a Google Cloud OAuth client — normally a 20-field console slog. So we made the AI do it.++**What happens:** you run one command and the AI drives **your real, logged-in browser**+(via the Adom browser extension / native browser) straight through Google Cloud Console. It+auto-fills the project name, enables all nine APIs in a single click, configures the consent+screen, and — the part everyone fat-fingers — **pastes the exact redirect URI** into your new+OAuth client. You just watch and press the two buttons Google insists a human press (*Create*,+*Allow*). Five minutes, start to finish, and it's gorgeous.++**What you end up with:** your **own** Google OAuth client (Internal to your Workspace — you+own and control it), and your **client secret stays on your machine** (`0600`). Adom's gateway+only relays the one-time sign-in code back to you over a WebSocket — *it never sees your+secret*. (OAuth callbacks are near-zero load, so one Adom container can host the callback for+many orgs at once — that's all the gateway does for you: lend a public callback URL.)++### Step by step++```bash+adom-google onboard --org acme        # prints the plan + the exact values; the skill drives the rest+```++**1 — The plan.** `onboard` lays out every step and the exact values (so nothing is guessed).+The AI reads `adom-google onboard --json` for the machine-readable version it types into the console.++![adom-google onboard prints the step-by-step plan and the exact redirect URI, API list, and consent settings](/blob/app/adom-google/docs/onboard-1-plan.png)++**2 — Enable all nine APIs in one click.** The AI opens the one-shot enable URL (Gmail, Drive,+Sheets, Docs, Slides, Calendar, Tasks, People, Chat all pre-selected) and clicks **Enable** —+nine APIs, one screen, instead of nine separate visits.++![The AI driving Google Cloud Console to enable all nine Workspace APIs at once](/blob/app/adom-google/docs/onboard-2-enable-apis.png)++**3 — Auto-fill the redirect URI (the error-prone part).** When it creates your OAuth client+(*Web application*), the AI pastes the **Authorized redirect URI** verbatim — one wrong+character here is the classic `redirect_uri_mismatch`, so it's never left to hand-typing.++![The AI pasting the exact Authorized redirect URI into the new OAuth client](/blob/app/adom-google/docs/onboard-3-redirect-uri.png)++**4 — Capture the client + authorize.** The AI reads your new Client ID + secret from the final+dialog, stores them on **your** machine (`onboard finish`, secret `0600`), asks you safe-vs-full,+then runs `adom-google auth` — you click **Allow** once and you're live. -![Enable the Gmail, Drive, Sheets, Docs, Slides, Calendar, Tasks, People and Chat APIs in the Google Cloud Console](/blob/app/adom-google/docs/enable-apis.png)+![adom-google authorized and driving the whole Workspace after onboarding](/blob/app/adom-google/docs/onboard-4-authorized.png) -The Chat API also needs a one-time "Chat app" configuration (name/avatar/description) in the-Cloud console's **Chat API → Configuration** tab — otherwise `spaces.*` calls 404 with "Chat-app not found." See the [SKILL.md](/blob/app/adom-google/SKILL.md) for the exact steps.+The Chat API also needs a one-time "Chat app" config (name/avatar) so `chat spaces` works — the+AI handles that in the same sitting. Full agent playbook: the bundled+**[adom-google-onboarding](/blob/app/adom-google/skills/adom-google-onboarding/SKILL.md)** skill.  --- 
bin/adom-google+180−3
@@ -66,6 +66,82 @@ SCOPES_FULL = [ def _scopes_for(mode):     return SCOPES_FULL if mode == "full" else SCOPES_SAFE ++# ---------------------------------------------------------------- org onboarding+# Adom's shared OAuth gateway. Its /callback is a PUBLIC, MULTI-TENANT relay: any org can+# register a one-time `state` over the WebSocket and have its auth code relayed back — zero+# per-org config on the gateway, near-zero load (a callback is one tiny request). A new org+# points its OWN Google OAuth client's redirect_uri at this callback and keeps its OWN client+# secret on its OWN container (RELAY mode, broker:false) — Adom never sees the org's secret.+# (Adom employees use BROKER mode instead, where the secret lives on the gateway; see provider.)+ADOM_SHARED_GATEWAY = "https://oauth-9mycwxij7tif.adom.cloud"++# The nine Google APIs adom-google drives — (console "enable" id, human label).+ONBOARD_APIS = [+    ("gmail.googleapis.com",         "Gmail API"),+    ("people.googleapis.com",        "People API (Contacts)"),+    ("drive.googleapis.com",         "Google Drive API"),+    ("sheets.googleapis.com",        "Google Sheets API"),+    ("docs.googleapis.com",          "Google Docs API"),+    ("slides.googleapis.com",        "Google Slides API"),+    ("calendar-json.googleapis.com", "Google Calendar API"),+    ("tasks.googleapis.com",         "Google Tasks API"),+    ("chat.googleapis.com",          "Google Chat API"),+]++def _enable_apis_url(project=None):+    # One URL that enables ALL nine APIs in one click (the GCP "enable multiple APIs" flow).+    u = "https://console.cloud.google.com/flows/enableapi?apiid=" + ",".join(a for a, _ in ONBOARD_APIS)+    return u + ("&project=" + project if project else "")++def _onboard_config(org=None):+    """The exact, canonical values an org needs to wire up their own Google OAuth client.+    Consumed by the onboarding skill (via `onboard --json`) to auto-fill the Cloud Console."""+    gw = (_provider().get("gateway_url") or ADOM_SHARED_GATEWAY).rstrip("/")+    label = "Adom Workspace CLI" + (f" — {org}" if org else "")+    return {+        "org": org,+        "gateway_url": gw,+        "redirect_uri": gw + "/callback",          # paste verbatim into the OAuth client+        "javascript_origins": [],                   # none needed (no browser JS client)+        "enable_apis_url": _enable_apis_url(),+        "apis": [{"id": a, "label": l} for a, l in ONBOARD_APIS],+        "scopes_safe": SCOPES_SAFE,+        "scopes_full": SCOPES_FULL,+        "consent": {                                # OAuth consent screen settings+            "user_type": "Internal",               # Internal → only the org's Workspace, long-lived tokens+            "app_name": label,+            "client_type": "Web application",       # the OAuth client TYPE to create+            "client_name": label,+        },+        "console": {                                # deep links the skill drives the browser to+            "home":           "https://console.cloud.google.com/",+            "project_create": "https://console.cloud.google.com/projectcreate",+            "consent_screen": "https://console.cloud.google.com/auth/overview",+            "credentials":    "https://console.cloud.google.com/apis/credentials",+            "create_client":  "https://console.cloud.google.com/apis/credentials/oauthclient",+            "chat_config":    "https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat",+        },+    }++ONBOARD_HINT = """Hint: ORG ONBOARDING — drive the user's OWN browser through Google Cloud Console.+Hint: This is the hand-held setup that gives a brand-new org their own Google OAuth client so+Hint: adom-google works for their whole team. Use the `adom-google-onboarding` skill, which+Hint: drives the user's REAL logged-in browser (adom-desktop `nbrowser_*`; pup native-mode as+Hint: fallback) and AUTO-FILLS the Cloud Console fields. Get the exact values to type with:+Hint:     adom-google onboard --json        # redirect_uri, the 9 APIs, scopes, consent settings+Hint: Steps the skill performs (screenshotting + pausing for the user's approval clicks):+Hint:   1. Create/select a Google Cloud project.+Hint:   2. Enable all nine APIs in one click (the `enable_apis_url`).+Hint:   3. Configure the OAuth consent screen (User type = Internal; app name from config).+Hint:   4. Create an OAuth client (type = Web application); AUTO-FILL Authorized redirect URI+Hint:      with the gateway `redirect_uri` (the error-prone field — never make the user type it).+Hint:   5. Capture the client_id + client_secret from the final dialog.+Hint:   6. Save them:   adom-google onboard finish --org <slug> --client-id <id> --client-secret <secret>+Hint:   7. Ask the user safe vs full mode, then:   adom-google auth+Hint: The secret stays on THE USER'S container (0600) and rides Adom's shared callback in RELAY+Hint: mode — Adom never holds it."""+ TOKEN_URL = "https://oauth2.googleapis.com/token" AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth" GMAIL_API = "https://gmail.googleapis.com/gmail/v1/users/me"@@ -94,7 +170,7 @@ def _provider():     if _PROVIDER is not None:         return _PROVIDER     p = {"app": DEFAULT_APP, "gateway_url": None, "gateway_ws": None,-         "client_id": None, "client_secret": None}+         "client_id": None, "client_secret": None, "broker": None}     def merge(path, keymap):         if not os.path.exists(path):             return@@ -117,6 +193,17 @@ def _provider():     p["client_id"]      = e.get("ADOM_GOOGLE_CLIENT_ID",  p["client_id"])     p["client_secret"]  = e.get("ADOM_GOOGLE_CLIENT_SECRET", p["client_secret"])     p["app"]            = e.get("ADOM_GOOGLE_APP",        p["app"])+    # broker is a bool — merge() skips falsy values, so read it explicitly (later wins).+    for path in (BUNDLED_PROVIDER, PROVIDER_PATH):+        if os.path.exists(path):+            try:+                d = json.load(open(path))+                if "broker" in d:+                    p["broker"] = bool(d["broker"])+            except Exception:+                pass+    if "ADOM_GOOGLE_BROKER" in e:+        p["broker"] = e["ADOM_GOOGLE_BROKER"].strip().lower() in ("1", "true", "yes", "on")     if p["gateway_url"]:         p["gateway_url"] = p["gateway_url"].rstrip("/")         p["gateway_ws"] = (p["gateway_ws"] or "wss://" + p["gateway_url"].split("://", 1)[-1]).rstrip("/")@@ -770,6 +857,9 @@ def cmd_auth(a):         "scope": " ".join(scopes), "access_type": "offline", "prompt": "consent",         "state": state,     }+    # broker=False → RELAY mode: the gateway just relays the code back; THIS container exchanges+    # it locally with the org's own secret. (None/True → broker mode: gateway exchanges.)+    want_exchange = pr.get("broker") is not False     auth_link = AUTH_URL + "?" + urllib.parse.urlencode(params)     print(f"OAuth Gateway flow — {mode.upper()} mode"           + ("  (full Drive/Sheets/Docs/Slides + delete)" if mode == "full"@@ -783,7 +873,7 @@ def cmd_auth(a):         msg = _minimal_ws_roundtrip(             pr["gateway_ws"],             send_msgs=[{"type": "register", "state": state, "provider": "google",-                        "app": pr["app"], "exchange": True}],+                        "app": pr["app"], "exchange": want_exchange}],             want_types=("tokens", "callback"),         )     except Exception as e:@@ -878,6 +968,73 @@ def cmd_provider_show(a):     print(f"source:        {src}")  +def cmd_onboard(a):+    """Guided org onboarding. Prints the exact plan + canonical values (or --json for the+    skill that drives the browser). The actual Cloud-Console clicking is done by the+    `adom-google-onboarding` skill via the native browser; this command supplies the truth."""+    org = getattr(a, "org", None)+    cfg = _onboard_config(org)+    if getattr(a, "json", False):+        print(json.dumps(cfg, indent=2))+        return+    R = cfg["redirect_uri"]+    print("┌─ adom-google · onboard your org to Google Workspace ─────────────────────────┐")+    print("│  One-time setup: your team gets its OWN Google OAuth client so the AI can     │")+    print("│  read mail, post Chat on your behalf, build Slides/Sheets/Docs, search Drive. │")+    print("│  Your client SECRET stays on YOUR machine (0600). Adom only lends its public  │")+    print("│  callback URL — it never sees your secret.                                    │")+    print("└──────────────────────────────────────────────────────────────────────────────┘\n")+    print("The AI can drive your real browser through every step below and auto-fill the")+    print("fields (recommended) — or you can do it by hand. Either way, the exact values:\n")+    print("1) Create / pick a Google Cloud project:")+    print("     " + cfg["console"]["project_create"])+    print("\n2) Enable all nine APIs in ONE click:")+    print("     " + cfg["enable_apis_url"])+    for ap in cfg["apis"]:+        print(f"        • {ap['label']}")+    print("\n3) OAuth consent screen  →  User type = Internal")+    print("     " + cfg["console"]["consent_screen"])+    print(f"        App name:  {cfg['consent']['app_name']}")+    print("\n4) Create an OAuth client  →  Application type = Web application")+    print("     " + cfg["console"]["create_client"])+    print("     ⚠ Authorized redirect URI — paste EXACTLY (this is the gateway callback):")+    print("        " + R)+    print("\n5) Copy the Client ID + Client secret it shows, then run:")+    print("     adom-google onboard finish --org <your-org-slug> \\")+    print("         --client-id <client-id> --client-secret <client-secret>")+    print("\n6) Pick safe vs full mode, then authorize:")+    print("     adom-google auth          # one Allow click with your Workspace account")+    print("\n(Chat also needs a one-time Chat-app config: " + cfg["console"]["chat_config"] + ")")+    print("\n" + ONBOARD_HINT)+++def cmd_onboard_finish(a):+    """Record the org's freshly-created OAuth client and wire RELAY mode (secret stays local)."""+    gw = (a.gateway or ADOM_SHARED_GATEWAY).rstrip("/")+    prov = {}+    if os.path.exists(PROVIDER_PATH):+        try:+            with open(PROVIDER_PATH) as f:+                prov = json.load(f)+        except Exception:+            prov = {}+    prov["gateway_url"] = gw+    prov["client_id"] = a.client_id+    prov["client_secret"] = a.client_secret      # the org's OWN secret, on the org's OWN box+    prov["app"] = a.org+    prov["broker"] = False                         # relay mode: gateway relays, we exchange locally+    os.makedirs(CFG_DIR, exist_ok=True)+    fd = os.open(PROVIDER_PATH, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)+    with os.fdopen(fd, "w") as f:+        json.dump(prov, f, indent=2)+    os.chmod(PROVIDER_PATH, 0o600)+    print(f"✅ Saved provider → {PROVIDER_PATH} (0600, RELAY mode).")+    print(f"   org/app:      {a.org}")+    print(f"   gateway:      {gw}  (shared callback; your secret never leaves this box)")+    print(f"   redirect_uri: {gw}/callback  (must match your OAuth client)")+    print("\nNext: ask the user safe vs full, then `adom-google auth`.")++ def cmd_setup(a):     # Canonical first-run entry point. Always prints the ask-the-user hint (even mid-config),     # so the installing AI is reminded to ask the safe/full question before authorizing.@@ -897,7 +1054,8 @@ def cmd_status(a):     print(f"OAuth client: {(cid[:18] + '…') if cid != '(none)' else cid} (from {src})")     print(f"Refresh token: {'present' if cfg.get('refresh_token') else 'MISSING — run auth'}")     print(f"Mode:         {cfg.get('mode', '(unset)')}  (safe = no 'delete all'; full = broad)")-    print(f"Auth:         {'broker (secret on gateway, none here)' if cfg.get('broker') else 'local (client secret in config)'}")+    relay = pr.get("broker") is False+    print(f"Auth:         {'broker (secret on gateway, none here)' if cfg.get('broker') else ('relay (your secret 0600 here; Adom lends only its callback)' if relay else 'local (client secret in config)')}")     print(f"Scopes:       {', '.join(s.split('/')[-1] for s in (cfg.get('scopes') or [])) or '(none)'}")     if cfg.get("_migrated_from"):         print(f"Migrated from: {cfg['_migrated_from']} (gmail.readonly only — run `auth` to add contacts)")@@ -917,6 +1075,15 @@ AUTH (one time; one consent → long-lived refresh token, stored 0600):   adom-google init <id> <secret>   set an explicit desktop OAuth client (for --manual)   adom-google status            show mode + what's authorised (never prints secrets) +ONBOARD A NEW ORG (give your team its OWN Google OAuth client — the AI hand-holds you):+  adom-google onboard [--org <slug>]   print the step-by-step plan + exact values to use+  adom-google onboard --json           same, machine-readable (the onboarding skill reads this)+  adom-google onboard finish --org <slug> --client-id <id> --client-secret <secret>+                                       record your new client (RELAY mode: secret stays 0600+                                       on THIS box; rides Adom's shared callback URL)+  → The `adom-google-onboarding` skill drives your real browser through Google Cloud Console+    and auto-fills the fields. Then: pick safe/full, `adom-google auth`, done.+ GMAIL (read-only; migrated from adom-gmail):   adom-google gmail attachments <messageId>            list attachments + ids   adom-google gmail read <messageId> <attId> [-o f]    dump attachment bytes@@ -966,6 +1133,16 @@ def main():     s = sub.add_parser("status"); s.set_defaults(fn=cmd_status)     s = sub.add_parser("setup"); s.set_defaults(fn=cmd_setup) +    ob = sub.add_parser("onboard"); ob.add_argument("--org"); ob.add_argument("--json", action="store_true")+    ob.set_defaults(fn=cmd_onboard)+    obx = ob.add_subparsers(dest="sub")+    y = obx.add_parser("finish")+    y.add_argument("--org", required=True)+    y.add_argument("--client-id", dest="client_id", required=True)+    y.add_argument("--client-secret", dest="client_secret", required=True)+    y.add_argument("--gateway", help="override the shared callback gateway (advanced)")+    y.set_defaults(fn=cmd_onboard_finish)+     g = sub.add_parser("gmail"); gs = g.add_subparsers(dest="sub")     x = gs.add_parser("attachments"); x.add_argument("message_id"); x.set_defaults(fn=cmd_gmail_attachments)     x = gs.add_parser("read"); x.add_argument("message_id"); x.add_argument("attachment_id"); x.add_argument("-o", "--out"); x.set_defaults(fn=cmd_gmail_read)
docs/onboard-1-plan.pngadded
⋯ 1 unchanged line ⋯
docs/onboard-2-enable-apis.pngadded
⋯ 1 unchanged line ⋯
docs/onboard-3-redirect-uri.pngadded
⋯ 1 unchanged line ⋯
docs/onboard-4-authorized.pngadded
⋯ 1 unchanged line ⋯
install.sh+5−3
@@ -17,13 +17,15 @@ 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+adompkg-link-skill adom-google             # ~/.claude/skills/adom-google -> $PWD/skills/adom-google+adompkg-link-skill adom-google-onboarding  # the guided org-onboarding skill (drives the browser)  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 "   • 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:           adompkg install adom-google-adom   (drops the Adom provider)" echo "   Then: 'adom-google setup' (asks safe vs full) and 'adom-google auth'."
package.json+6−1
@@ -1,6 +1,6 @@ {   "slug": "adom-google",-  "version": "0.5.1",+  "version": "0.6.0",   "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).",@@ -38,6 +38,7 @@     "bin/adom-google",     "bin/adom-gmail",     "skills/adom-google/SKILL.md",+    "skills/adom-google-onboarding/SKILL.md",     "README.md",     "LICENSE",     "MIGRATION.md",@@ -45,6 +46,10 @@     "docs/hero.png",     "docs/enable-apis.png",     "docs/chat-reads-email.png",+    "docs/onboard-1-plan.png",+    "docs/onboard-2-enable-apis.png",+    "docs/onboard-3-redirect-uri.png",+    "docs/onboard-4-authorized.png",     "screenshots/hero.png",     "install.sh",     "uninstall.sh",
page.json+51−33
@@ -1,22 +1,22 @@ {-  "schema_version": 1,-  "type": "app",   "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.5.1",-  "visibility": "public",-  "install": {-    "binary_name": "adom-google",-    "install_dir": "",-    "install_hint": "",-    "version_cmd": ""-  },-  "author": {-    "id": "6738b00a4dc87b8adbb0a5a3",-    "name": "John Lauer",-    "email": "[email protected]"-  },+  "version": "0.6.0",+  "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).",+  "keywords": [+    "adom-google",+    "google-workspace",+    "oauth",+    "gmail",+    "google-sheets",+    "google-docs",+    "google-slides",+    "google-calendar",+    "google-chat",+    "cli",+    "ai-agent"+  ],   "tags": [     "google-workspace",     "oauth",@@ -26,21 +26,39 @@     "open-source",     "mit"   ],-  "license": "MIT",-  "hero": {-    "path": "docs/hero.png",-    "type": "image"+  "install": {+    "binary_name": "adom-google"   },-  "sample_prompts": [],-  "discovery_triggers": [],-  "discovery_pitch": null,-  "metadata": {-    "hero_path": "docs/hero.png",-    "hero_type": "image"+  "dependencies": {},+  "scripts": {+    "install": "./install.sh",+    "uninstall": "./uninstall.sh"   },-  "created_at": "2026-06-16T22:28:58.933Z",-  "updated_at": "2026-06-17T00:57:47.322Z",-  "skills": [],-  "hero_path": "docs/hero.png",-  "hero_type": "image"-}+  "files": [+    "bin/adom-google",+    "bin/adom-gmail",+    "skills/adom-google/SKILL.md",+    "skills/adom-google-onboarding/SKILL.md",+    "README.md",+    "LICENSE",+    "MIGRATION.md",+    "provider.example.json",+    "docs/hero.png",+    "docs/enable-apis.png",+    "docs/chat-reads-email.png",+    "docs/onboard-1-plan.png",+    "docs/onboard-2-enable-apis.png",+    "docs/onboard-3-redirect-uri.png",+    "docs/onboard-4-authorized.png",+    "screenshots/hero.png",+    "install.sh",+    "uninstall.sh",+    "package.json"+  ],+  "visibility": "public",+  "org": "adom",+  "author": {+    "name": "John Lauer",+    "email": "[email protected]"+  }+}\ No newline at end of file
skills/adom-google-onboarding/SKILL.mdadded+149
@@ -0,0 +1,149 @@+---+name: adom-google-onboarding+description: Hand-hold a brand-new ORG through standing up its OWN Google Workspace OAuth client so adom-google works for their whole team — read mail, post Google Chat on their behalf, build Slides/Sheets/Docs, search Drive. You drive the user's REAL logged-in browser (adom-desktop nbrowser_* extension; pup native-mode fallback) to Google Cloud Console, AUTO-FILL the fields (especially the error-prone redirect URI), capture the new client_id + client_secret, and wire it in RELAY mode (their secret stays 0600 on their box; Adom only lends its shared callback URL — it never sees the secret). Use when a NON-Adom org wants to get going themselves — triggers: onboard my org/company to adom-google, set up google workspace for my team, configure google oauth for <company>, get my company on adom-google, self-serve google setup, "we want to use adom-google at <org>", set up the google cloud project, create our oauth client. (For an Adom EMPLOYEE, skip all this — they install adom-google-adom and just run auth. This skill is for everyone else.)+---++# adom-google — Org Onboarding (drive the browser, auto-configure Google)++Goal: take a new org from *nothing* to *"the AI is reading our mail, posting in Chat, and building+decks"* in one guided sitting. The hard part — creating a Google Cloud OAuth client — is normally a+20-field console slog. You do it FOR them: drive their real browser, auto-fill every field, pause only+for the clicks Google forces a human to make. Pristine, billboard-grade setup.++## The architecture you're wiring (know this cold)++- The org creates **their own** Google Cloud project + OAuth client (Internal to their Workspace).+- Their OAuth client's **Authorized redirect URI = Adom's shared gateway `/callback`** — a public,+  multi-tenant **relay**. Any org can register a one-time `state` over its WebSocket and get the auth+  code relayed back. Near-zero load; no per-org config on the gateway.+- The org's **client secret stays on the org's own container** (`~/.config/adom-google/provider.json`,+  `0600`) in **RELAY mode** (`broker:false`). Adom's gateway only relays the `code`; the org's CLI+  exchanges it and refreshes locally with their own secret. **Adom never holds their secret.**+- Result: the org owns everything; Adom just lends a callback URL. (Adom *employees* use BROKER mode+  via `adom-google-adom`, where the secret lives on the gateway — different package, not this flow.)++## Get the exact values FIRST (single source of truth)++```bash+adom-google onboard --org <slug> --json+```++Returns `redirect_uri`, `enable_apis_url` (one click → all 9 APIs), `apis[]`, `scopes_safe/full`,+`consent` (User type = **Internal**, app name), and `console{}` deep links. **Every field you type+comes from here** — never hand-author a redirect URI or API id.++## Which browser surface++You need the user's **real, logged-in Google admin session**. Use the **extension** (`nbrowser_*`) —+it IS their everyday Chrome/Edge with their Workspace login + device enrollment already active. Pup+Chrome-for-Testing has no logins; pup *native-mode* is the fallback if the extension is unavailable.++```bash+adom-desktop --target AdomLapper nbrowser_status     # connected? which browser?+```++If "not connected": the target profile has no open Chrome window — ask the user to open Chrome (any+window) in the profile that's signed into their Workspace, then `nbrowser_rescan`. Fallback to pup:+`adom-desktop browser_open_window '{"sessionId":"gcp","nativeBrowser":"chrome","nativeProfile":"Default","url":"https://console.cloud.google.com"}'`.++> ⚠ **Confirm the signed-in account first.** Drive to `https://console.cloud.google.com`, screenshot,+> and verify the top-right account is the user's **Workspace admin** (not a personal @gmail). Creating+> the project under the wrong account is the #1 way this goes sideways. If wrong, have them switch+> accounts in Chrome before continuing.++## Drive loop (repeat for every step)++1. `nbrowser_navigate` to the deep link from the config.+2. `nbrowser_screenshot` → **read it** (don't assume the DOM; Google Console mutates constantly).+3. Act with **trusted** input: `nbrowser_type {selector,text}` to fill, `nbrowser_click {selector}` to+   click. Selectors are modal-aware; prefer visible text / `aria-label` (`[aria-label="Create"]`,+   `button:has-text("Enable")`). If a selector misses (`elementsMatched:0`), screenshot again and try a+   coordinate click (`nbrowser_input_dispatch {type:"click",x,y}`) off the screenshot.+4. `nbrowser_screenshot` again to confirm the result before moving on.+5. Narrate with `nbrowser_caption {text:"Step 3/6 — enabling APIs…"}` so the user follows along, and+   `desktop_bring_to_front {titleContains:"Google Cloud"}` so they can watch.+6. **Shotlog every screenshot** with a description and hand the user the phone URL (per the all-screenshots+   rule) so they can audit each step from their phone.++### Always pause for the human on these (Google requires a real click; never fake it)+- Account chooser / re-auth prompts.+- The final **Create** on the OAuth client.+- The OAuth consent screen **Publish/Save** if a confirm dialog appears.+Show a screenshot, say exactly what to click, wait, then continue. Auto-fill everything *up to* these.++## The six steps++**1 · Project.** Navigate `console.project_create`. Auto-fill the project name+(`type` into the Project name field) e.g. `adom-workspace`. Pause for the user to click **Create**.+Wait for the create to finish (poll screenshots / the notifications bell), then make sure the new+project is the **active** project (project picker, top bar).++**2 · Enable all nine APIs in one click.** Navigate to `enable_apis_url` (it pre-selects all 9). Screenshot.+Click **Enable**. This is the single biggest time-saver — one screen instead of nine. Confirm "APIs enabled."++**3 · OAuth consent screen → Internal.** Navigate `console.consent_screen`. Choose **User type = Internal**+(critical — Internal = only their Workspace can sign in, and gives long-lived refresh tokens). Auto-fill+App name (`consent.app_name`), user support email (their admin), developer contact (their admin). Save.+*(Internal apps need no verification and skip the scary "unverified app" screen.)*++**4 · Create the OAuth client.** Navigate `console.create_client`. Application type = **Web application**.+Auto-fill Name (`consent.client_name`). Then the load-bearing move: **AUTO-FILL the Authorized redirect+URI** with `redirect_uri` from the config — paste it verbatim, never make the user type it (one wrong+char = `redirect_uri_mismatch` later). Screenshot to prove it matches. Pause for the user to click **Create**.++**5 · Capture the credentials.** The post-create dialog shows **Client ID** + **Client secret**. Read them:+`nbrowser_eval` to scrape the dialog text, and ALSO screenshot as a backup. Verify the client_id ends in+`.apps.googleusercontent.com` and the secret looks like `GOCSPX-…`. Then store them — **secret to the CLI,+never echoed into chat**:++```bash+adom-google onboard finish --org <slug> --client-id "<id>" --client-secret "<secret>"+```++This writes `provider.json` (`0600`, `broker:false`). The secret lives only on the user's box.++**6 · Chat app config (once).** Navigate `console.chat_config` (Chat API → Configuration). Set an app+name + avatar + "Make available to … your domain." Without this, `chat spaces` 404s "Chat app not found."+This is the one step pure-data tools skip; do it so `adom-google chat` works out of the box.++## Authorize + verify (the payoff)++ASK the user **safe vs full** (use AskUserQuestion — never pick for them; safe = no "delete all" on the+consent screen, the recommended default). Then:++```bash+adom-google auth            # safe   (or: adom-google auth --full)+```++Print the consent URL; drive their browser to it (`nbrowser_navigate`), screenshot the consent, pause+for **Allow**. The gateway relays the code; the CLI exchanges it locally. Then prove it works — small,+real, delightful:++```bash+adom-google status                                   # mode + "Auth: relay (your secret 0600 here…)"+adom-google chat spaces                              # lists their spaces+adom-google api https://www.googleapis.com/drive/v3/about?fields=user   # "you're signed in as …"+```++End by showing them ONE magic moment — e.g. compose a Google Doc or post a Chat message — so the+"oh, this is a game-changer" lands immediately.++## Gotchas+- **Wrong Google account** under the project → everything downstream is wrong. Verify in step 0.+- **redirect_uri mismatch** at `auth` time → the client's Authorized redirect URI ≠ `redirect_uri`. Re-open+  the OAuth client, fix the field (auto-fill again), save, retry `auth`. This is why step 4 auto-fills.+- **Internal vs External**: always Internal for an org. External forces app verification + a scary screen.+- **Extension "not connected"** = no open Chrome window in the signed-in profile. Open one, `nbrowser_rescan`.+- **Secrets**: never print the client_secret or refresh token to the transcript/logs. Pass the secret+  straight into `onboard finish`. Screenshots of the secret dialog → shotlog them privately; don't paste+  the secret text into chat.+- **Don't auto-click Create/Allow/Publish** — Google wants a human there, and the user *should* see what+  they're approving. Auto-fill up to the button; let them press it.++## Why this matters (say it to the user)+Once they're authed, their AI can read their email, post Google Chat **on their behalf**, compose Google+Docs, build Google Sheets and Slides, and search Google Drive — all from the terminal. That's the+workflow unlock teams fall in love with. You just made the painful part painless.++Related: [adom-google](/adom/adom-google) (the CLI + full command set) · the `driving-the-extension`+skill (nbrowser_* deep reference) · [adom-gchat](/adom/adom-gchat) (attributed bot posts).
skills/adom-google/SKILL.md+11−1
@@ -1,6 +1,6 @@ --- 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, 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.+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@@ -38,6 +38,16 @@ Gemini CLI, and `gcloud`. Conclusion: **call the REST APIs directly.** 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: