← Commit history

Ensure all sub-skills in main repo

John Lauer ·7de266688f ·4mo ago ·parent 3673e14
4 files changed +130−5
skills/driving-gusto/SKILL.md+45−4
@@ -23,10 +23,51 @@ Gusto = **Keycloak** (realm `zenpayroll`, at `login.gusto.com`) in front of a Ra - `action:"autofill-popup"` β€” a **split** "Enter your password" step that did NOT autofill. Needs the ~1s   foreground autofill popup (see driving-the-desktop). Prefer the combined-form path above, which autofills. -## πŸ”’ 2FA-A fresh/untrusted device hits **"Enter the 6-digit code in your authenticator app"** (or **"Get text instead"**-= SMS). It's a human gate β€” surface it; you can't complete it. **CHECK "Remember device for 30 days"** before-the human enters the code, so the next login skips 2FA.+## πŸ”’ 2FA β€” fully automatable via SMS (proven 2026-06-24)+A fresh/untrusted device hits **"Enter the 6-digit code in your authenticator app"**. If the user has no+authenticator, click **"Get text instead"** β†’ on **"Verify your identity"** select the **Text message** radio+(`input[value=SMS]`) β†’ **Continue** (`button.gwb_nUZqV`). Gusto texts a code from shortcode **64039**.++**Then complete it WITHOUT the human** if the user runs the Google Messages PWA β€” see+**reading-google-messages**:+1. Read the code via UIA: `desktop_find_control {hwnd:<Messages>, contains:"is your Gusto"}` β†’+   "64039 **071276** is your Gusto verification code." β†’ the 6-digit token is the code (64039 is the sender).+2. **ALWAYS tick "Remember device for 30 days"** before entering, so the next login skips 2FA entirely.+3. Enter it with **real keystrokes**: focus the OTP box (`desktop_find_control` role:edit, centerY>180 to skip+   the omnibox) β†’ `desktop_type {text:"071276"}`. ⚠️ **CDP `insertText`/synthetic keys DON'T register** on+   Gusto's React OTP field (value stays empty) β€” desktop_type is required. β†’ Continue.+4. If a code is rejected as invalid, it expired / got used β€” click **"Send again"**, re-read the NEWEST SMS.+5. **Post-2FA "Add a passkey to this device" page** (`/login-actions/required-action`): click **"Remind me+   later"** (NOT Continue β€” that starts a biometric enrollment). Then you land on `app.gusto.com/payroll_admin`.++## πŸ’΅ Running payroll (read-and-verify flow, proven 2026-06-24)+**β›” NEVER click the final "Submit payroll" without the human's explicit go-ahead** β€” it moves real+money. Drive to the summary, read it back, and STOP. The flow:+1. **Pay** β†’ `https://app.gusto.com/payroll_admin/pay`. The upcoming run shows under **"Up next"** with a+   **Run** button (next to Skip / More options). Click the button whose text is **exactly `Run`** (not+   "Run an off-cycle payroll", etc.).+2. The wizard is **3 steps**, URL tells you which: `.../review-timesheets` β†’ `.../inputs` β†’ `.../review`.+   - **Step 1 β€” Review timesheets:** hours only. If a banner says *"N timesheets to approve"*, click+     **"Approve all"**. The advance button here is **"Edit payroll"** (there's no "Continue").+   - **Step 2 β€” Edit payroll (`/inputs`):** the per-employee earnings grid. Read **Total pay** + **Rate**+     per person; header totals give **Total earnings** / **Total worked**. Advance with **"Review summary"**.+   - **Step 3 β€” Review and submit (`/review`):** the money. The grid has **Gross pay / Reimbursements /+     Company taxes / Company benefits / Subtotal** per person + **Grand totals**, plus **Total payroll**,+     **Withdrawal amount** (the actual bank debit β‰  total payroll cost), **bank account**, **withdrawal+     date**, **payday**. Read ALL of this to the human. The button is **"Submit payroll"** β€” DON'T click it.+3. **$0-check flag:** Gusto warns *"There is 1 employee with a $0 check"* (e.g. an owner taking no salary+   this run) β€” surface it so the human can choose to skip that person.+4. To read the grids, pull `document.querySelectorAll('table') β†’ tr β†’ th,td`; the SPA hydrates ~8–12s after+   each step, so **poll `document.body.innerText.length` until it's substantial** before trusting a read.++## ⚠️ Two gotchas when driving the payroll wizard+- **Eval goes blank for a few seconds after each in-app step change.** Gusto's wizard tears down the JS+  execution context on route change, so `nbrowser_eval` can return `undefined`/empty length for ~4–12s.+  Fixed in the extension (ext **0.0.13**: `evaluate()` retries on a torn-down context) β€” but still **poll+  until content appears** rather than reading once.+- **Aggressive inactivity sign-out.** Long reads can trip `?alert=signed_out_inactivity`. Because we keep+  the device-trust cookie (see logout rule below), re-login is background and **skips 2FA** β€” just+  `nbrowser_navigate` back to `app.gusto.com`, `nbrowser_login_form` (autofills) β†’ submit.  ## β›”β›” Logging OUT β€” do NOT wipe cookies (this caused a real lockout, 2026-06-24) **`nbrowser_clear_cookies` / any full cookie-wipe logs you out BUT removes the "remember this device" trust
skills/driving-sites/SKILL.md+2−1
@@ -11,7 +11,8 @@ hours once). A **site recipe** captures a site's playbook ONCE so the next run i ## Known recipes (in this repo) | Recipe | Site | What it covers | |---|---|---|-| **driving-gusto** | gusto.com | Autofill-and-submit login (background), Google SSO, 2FA, and β›” never-wipe-cookies-to-log-out |+| **driving-gusto** | gusto.com | Autofill-and-submit login (background), Google SSO, fully-automated SMS 2FA, and β›” never-wipe-cookies-to-log-out |+| **reading-google-messages** | Google Messages PWA | Read SMS / retrieve a 2FA code from the desktop Messages app (UIA, since PrintWindow is blank) β€” the helper that makes SMS-2FA logins fully automatic |  ## How a site recipe is USED 1. When you're about to drive a login/site, the **`nbrowser_login_form`** detector tells you the smart path
skills/driving-the-desktop/SKILL.md+14
@@ -119,6 +119,20 @@ a target ignores it: `desktop_bring_to_front` + `desktop_press_key ["enter"]` (b 3. Load-unpacked reads the **on-disk** build (version may jump). Verify the `edge:`/`chrome:` key returns to    `nbrowser_profiles` (reconnects in ~1–2s). +## β›”β›” NEVER take over a PWA / installed-app window (first-class guard, ext 0.0.14+)+Installed PWAs / "Add to taskbar" apps β€” **Google Chat, Google Messages, Gmail-as-app, etc.** β€” run in+their OWN window, which Chrome reports as **`type:'app'`** (vs `'normal'` for ordinary browsing). If you+let a drive/navigate verb fall back to the user's **currently-focused** window and that window is a PWA,+you **hijack their standalone app** (real regression 2026-06-24: a `nbrowser_navigate` with an unmapped+session fell back to `lastFocusedWindow` β€” the **Google Chat PWA** β€” and navigated it to Gusto).+- **The extension now refuses this at the choke point** (`resolveTabId` β†’ `assertDriveableWindow`): any+  verb that resolves to a non-`'normal'` window throws *"refusing to drive a PWA / installed-app window…"*+  instead of acting. So **always target a window you opened** with `nbrowser_open_window` (those are+  `'normal'`), never assume the focused window is yours.+- **Override (only when the user explicitly asks to drive that exact app window):** pass **`allowPwa:true`**.+- Note: **READING** a PWA via UIA (e.g. `reading-google-messages`) is fine β€” that doesn't navigate it. The+  ban is on **driving/navigating** an app window.+ ## β›” You CANNOT decrypt modern Chrome/Edge saved passwords (v20 app-bound encryption) Don't build a "decrypt the saved password + inject it" autologin β€” it's a dead end for modern browsers. Chrome/Edge passwords saved since ~Chrome 127 (2024) use the **`v20`** prefix = **app-bound encryption**,
skills/reading-google-messages/SKILL.mdadded+69
@@ -0,0 +1,69 @@+---+name: reading-google-messages+description: How to find the Google Messages PWA on a Windows desktop and READ SMS out of it β€” most importantly, to retrieve a one-time 2FA / verification code so you can complete a text-message two-factor login fully automatically. Google Messages for web is usually installed as a taskbar PWA ("Messages - Google Messages for web"); its window renders BLANK to PrintWindow, so you read it with UIA (desktop_find_control), not a background screenshot. Match when the user says "read my texts", "get the SMS code", "what's the 2FA code", "check Google Messages", "find the verification code", "automate the SMS 2FA", or any login that texts a code. Part of the adom-browser-extension site-recipe set (see driving-sites).+---+Part of the **adom-browser-extension** site-recipe set β†’ see **driving-sites**. Generic desktop/UIA+mechanics live in **driving-the-desktop**; THIS page is the Google Messages PWA's quirks.++**Why this exists:** lots of sites text a 6-digit code for 2FA (Gusto, banks, etc.). If the user runs+**Google Messages for web** as a desktop app, the AI can read that code and finish the login with **zero+human steps** β€” no "please type the code you got." This page is how. (Proven end-to-end on 2026-06-24:+automated a full Gusto SMS-2FA login by reading the code out of this PWA.)++## 1. Find the window (it's a taskbar PWA, not a browser tab)+Google Messages for web, "installed" from `messages.google.com/web`, runs as its **own PWA window** with+window class `Chrome_WidgetWin_1` and a title like **"Messages - Google Messages for web"** (sometimes just+"Messages"). Find its hwnd by title:+```+desktop_list_windows {}                       # scan titles for "Google Messages" / "Messages"+# or, if your build supports it:+desktop_find_window {titleContains:"Google Messages"}+```+Grab the `hwnd` (e.g. 459830). If there's NO such window, the user hasn't installed/opened the PWA β€” fall+back to surfacing the code request to the human (you can't read what isn't running).++## 2. ⚠️ The gotcha: PrintWindow returns BLANK β€” read it with UIA instead+The PWA is a heavy SPA; **`desktop_screenshot_window {hwnd}` (PrintWindow) captures an EMPTY/white frame**+when the window is in the background. Do NOT try to OCR a blank shot. Two things that DO work:+- **UIA text read (preferred β€” works in the BACKGROUND, no foregrounding):**+  ```+  desktop_find_control {hwnd:<msgHwnd>, contains:"verification code"}   # or "is your", or the sender+  ```+  UIA walks the accessibility tree and returns the message text even though PrintWindow can't see it. The+  freshest SMS is the **top conversation**; its preview/body holds the code. This is the money path β€” it+  reads the code without stealing focus from whatever you're doing.+- **Foreground screenshot (fallback):** `desktop_bring_to_front {hwnd}` β†’ wait ~600ms β†’+  `desktop_screenshot_window {hwnd}` renders correctly once it's foreground. Use only if UIA text is empty.+  (Restore the user's previous window afterward.)++## 3. Extract the code+The SMS body looks like: **"64039 071276 is your Gusto verification code. We'll never call or text you..."**+(`64039` = the sender shortcode, `071276` = the code). Pull the code with a regex over the UIA text:+- Prefer a digit-run **near** the words "code"/"verification": `/(\d{4,8})(?=[^\d]*\b(?:is your|verification|code))/i`+  then fall back to the first standalone `\b\d{6}\b`.+- **Ignore the shortcode** (4–5 digits like `64039`, `22000`) β€” it's the SENDER, not the code. The real OTP+  is usually 6 digits. If two numbers appear, the code is the longer/6-digit one, typically the 2nd token.+- **Freshness:** make sure you're reading the NEWEST message (top of the list). If you just triggered a+  resend, wait a few seconds and re-read β€” codes **expire and are single-use**; a stale code reads fine but+  gets rejected. When in doubt, resend and read the newest.++## 4. Hand the code to the login (entering it)+See **driving-the-desktop** / the site recipe for entry. The hard-won bit: a React single-box OTP field often+**ignores CDP `insertText`/synthetic keys** (value stays empty) β€” type it with **real OS keystrokes**+(`desktop_type {text:"071276"}`) into the focused field. Always tick "remember this device" if offered so the+2FA doesn't recur.++## 5. Etiquette+- Reading the user's texts is sensitive β€” only do it to complete a 2FA the user asked you to automate, read+  the **minimum** (just the code), and never log/echo full message bodies you don't need.+- If you foregrounded the PWA, restore the user's prior foreground window when done.++## Quick reference+| Thing | Value |+|---|---|+| Window title | **"Messages - Google Messages for web"** (or "Messages"), class `Chrome_WidgetWin_1` |+| Read the text | `desktop_find_control {hwnd, contains:"verification code"}` (UIA β€” works backgrounded) |+| ⚠️ Don't | `desktop_screenshot_window` in background β†’ BLANK (PrintWindow can't see the SPA) |+| Code shape | 6 digits; the 4–5-digit number is the **sender shortcode**, not the code |+| Newest SMS | **top** conversation; resend + re-read if a code is rejected (single-use/expiring) |+| Enter it | `desktop_type` (real keystrokes) β€” CDP insertText often fails on OTP boxes |