app
Adom Google
Public Made by Adomby adom
Drive your whole Google Workspace from the terminal, run by your AI. Read Gmail, write Google Docs, build Google Sheets and Google Slides, search Google Drive, manage Google Calendar and Google Tasks, and post to Google Chat, plus any Google API and a zero-setup personal Gmail.
← Commit history
Publish 0.8.22
5 files changed
+58−20
README.md+3−2@@ -323,7 +323,8 @@ adom-google gmail attachments <messageId> | read <messageId> <attId> | ics <mess adom-google chat spaces | dm <email> | read --from <space|email> adom-google chat send --to <space-id|email> "<text>|-|@file" [--thread <key>] -adom-google status # what's authorized (never prints secrets)+adom-google status [--json] # what's authorized (never prints secrets); exit 1 until authorized+adom-google auth --json # blocks until Allow; ONE JSON line on stdout for tooling ``` ## Security@@ -341,7 +342,7 @@ Nothing secret is ever stored in this repo or any package. Two local files hold both outside the repo, both `0600`: ```jsonc-// ~/.config/adom-google/config.json , your live auth (NEVER committed)+// ~/.config/adom-google/config.json , your live auth (NEVER committed) { "refresh_token": "***", "mode": "safe", "scopes": ["…"], "broker": true } // ~/.config/adom-google/provider.json, which gateway/client to use (no secret)
SKILL.md+52−15@@ -78,30 +78,67 @@ user clicks **Allow** once. > 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-bridge-cli nbrowser_open_window '{"sessionId":"gauth","url":"<URL>"}'`), or single-quote the+> (`adom-bridge 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. -> 🖐 **Consent is the sanctioned exception to background-only browsing.** abe/`nbrowser_*` opens-> windows in the BACKGROUND and [foreground-etiquette](/adom/foreground-etiquette) says never steal-> focus, correct almost everywhere, but a consent page is worthless until a human physically-> clicks Allow, and a silent background window is easy to miss. So you MAY raise the consent-> window, on one condition: **announce it first, never raise silently.**+> 🖐 **Three lanes to get Allow clicked, cheapest for the human first** (the `auth` output prints+> the ready-to-paste commands for each; issues #8 and #9):+>+> **Lane A, hands-free (try first): drive the consent page in pup.** pup's durable `adom-you`+> profile keeps the Google session and its OS-keychain vault holds logins, so once a Google login+> is saved there the user never clicks again. Check `adom-bridge credential_list` for a Google+> entry, then `pup_open_window` the URL on session `gauth`, `pup_login {submit:true, submitReason}`+> if a sign-in page appears, and click the account + Allow via `pup_eval` (the classic+> accounts.google.com consent page takes in-page clicks; a FedCM "Sign in with Google" chooser+> rejects them). **First machine, no Google login in the vault yet:** the first pass is ONE+> interactive sign-in inside that pup window (open it, toast the user with `focus` on it, they+> sign in once); every later `auth` on that machine is silent. Do not read "hands-free" as "always".+>+> **Lane B, one human click (always works): a clickable hyperlink.** Put the URL in your reply as+> a real link, not in a code fence. Hydrogen's browser picker lets the user choose browser and+> profile; nothing for the AI to get wrong and no extension needed.+>+> **Lane C, only if the Adom extension is installed in the target profile:** `adom-bridge+> nbrowser_profiles` must report `extensionInstalled:true`, then+> `adom-bridge nbrowser_open_window '{"sessionId":"gauth","url":"<URL>"}'`. Do not fall back to+> `nbrowser_open_os_window` (it returns ok without confirming what rendered).+>+> **Toasting the user (lanes A-first-pass and C) is the sanctioned foreground exception.** abe and+> `nbrowser_*` open windows in the BACKGROUND and [foreground-etiquette](/adom/foreground-etiquette)+> says never steal focus, correct almost everywhere, but a consent page is worthless until a human+> clicks Allow. Fire the toast AFTER the window exists, give it `focus` so the click raises the+> consent window (not Adom Bridge), make it sticky, and READ THE REPLY: > > ```bash-> adom-bridge-cli notify_user '{"title":"Google sign-in needed","body":"Opening the consent page in your browser now - click Allow."}'-> # then bring that window forward+> adom-bridge notify_user '{"id":"gauth","title":"Google sign-in needed","body":"Click Allow in the window I just opened. You have ~60 min.","scenario":"reminder","buttons":[{"label":"Done"},{"label":"Cancel"}],"focus":{"titleContains":"Sign in - Google"}}'+> # require clickBehavior:"focus-window" and focus.resolved:true in the reply; if it carries+> # _couldHaveDoneBetter, do what it says (e.g. pass the hwnd from the window verb's reply).+> adom-bridge notify_response '{"id":"gauth"}' # what they clicked; or add "wait":true to block > ``` >-> Prefer the gentler path when the user may be mid-task: leave it in the background and flash the-> taskbar (`browser_alert_window`), letting them come to it. Sessions survive dropped sockets and-> last ~60 minutes, so there is genuinely no rush. Never foreground anything else in the flow.+> A bare `notify_user {title, body}` fired BEFORE the window exists points at nothing and cannot be+> verified afterwards. Prefer the gentler path when the user may be mid-task: leave the window in+> the background and flash the taskbar (`browser_alert_window`). Never foreground anything else. > ⏱ **The user has ~60 minutes to click Allow, do NOT rush them.** The gateway keeps the > sign-in session alive for the full hour, and since v0.8.6 the session also SURVIVES a dropped > WebSocket: the CLI reconnects and claims the result, so a wifi blip, a sleeping laptop, or the-> ingress killing an idle socket no longer costs the user their sign-in. (Before that fix a-> disconnect silently destroyed the session and Allow showed "Session Expired" minutes in.) If a-> "Session Expired" page ever does appear, just re-run `adom-google auth`.+> ingress killing an idle socket no longer costs the user their sign-in. If a "Session Expired"+> page appears, or Google's generic **"Sorry, something went wrong there. Try again."** on the+> first load, stop the listener and re-run `adom-google auth`: a fresh URL clears it, the earlier+> session is recovered, and nobody clicks Allow twice. The listener prints+> `↳ Callback received from the gateway` the moment the callback lands, so "the user clicked and+> Google failed" (line printed, then an error) is distinguishable from "the user never clicked".++> ⏳ **Waiting for the click (AI): never poll `status | grep authorized`.** `auth` already blocks+> until Allow is clicked (up to ~60 min), so do NOT wrap it in a `timeout`, and do NOT wait on+> `adom-google status` with a grep: before 0.8.22 it exited 0 while unauthorized and its failure+> text contained the word "authorized", so that loop fired instantly on the failure. Now:+>+> ```bash+> adom-google auth --json # guidance on stderr; ONE JSON line on stdout when authorized, exit 0+> adom-google status --json # {"authorized": true|false, ...}; exit 1 until authorized+> ``` *(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@@ -252,7 +289,7 @@ adom-google chat read --from <space-id|email> [--limit N] [--json] # recent me # Every post is stamped clientAssignedMessageId=client-agent-<20 hex> so the RECEIVING # side can tell an agent-sent message from a hand-typed one. Chat shows an "Adom Google" # attribution badge in the UI for anything posted through the API with user credentials,-# but exposes that attribution NOWHERE in the REST API — not on Message/User/Annotation,+# but exposes that attribution NOWHERE in the REST API, not on Message/User/Annotation, # not in searchMessages filters, not in the Workspace Events payload, not in the Admin SDK # chat audit log. The marker is the only durable signal, and it is invisible in the UI. # Read it back with: adom-google api <message-url> | jq -r .clientAssignedMessageId
VERSION+1−1@@ -1 +1 @@-0.8.21+0.8.22
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-google",- "version": "0.8.21",+ "version": "0.8.22", "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. Connect a personal @gmail (or another org's Google account) with ZERO setup via Adom's hosted Connector (`adom-google connect-personal`), or bring your own token-broker gateway (`provider set`): Adom staff use the private adom-google-adom provider. One-click OAuth, no client secret on your machine.",
page.json+1−1@@ -4,7 +4,7 @@ "slug": "adom-google", "title": "Adom Google", "brief": "Drive your whole Google Workspace from the terminal, run by your AI. Read Gmail, write Google Docs, build Google Sheets and Google Slides, search Google Drive, manage Google Calendar and Google Tasks, and post to Google Chat, plus any Google API and a zero-setup personal Gmail.",- "version": "0.8.21",+ "version": "0.8.22", "visibility": "public", "install": { "binary_name": "adom-google"