[object Object]

← back to Delivery Address Fix

delivery-address-fix skill: engine + ubereats(PROVEN)/doordash/instacart adapters (TK-10145)

4fddd9a436e512244268b009ea5dcce7e616231f · 2026-08-02 15:21:29 -0700 · Steve Abrams

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Files touched

Diff

commit 4fddd9a436e512244268b009ea5dcce7e616231f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun Aug 2 15:21:29 2026 -0700

    delivery-address-fix skill: engine + ubereats(PROVEN)/doordash/instacart adapters (TK-10145)
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
 .gitignore                      |   8 +++
 SKILL.md                        | 106 ++++++++++++++++++++++++++++++++++++++++
 references/_adapter-template.md |  20 ++++++++
 references/doordash.md          |  29 +++++++++++
 references/instacart.md         |  28 +++++++++++
 references/ubereats.md          |  46 +++++++++++++++++
 scripts/browser-server.js       |  20 ++++++++
 7 files changed, 257 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1924158
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
diff --git a/SKILL.md b/SKILL.md
new file mode 100644
index 0000000..bbb2bbf
--- /dev/null
+++ b/SKILL.md
@@ -0,0 +1,106 @@
+---
+name: delivery-address-fix
+description: Pluggable, provider-adapter skill that fixes/saves a delivery address on a food-delivery account (UberEats, DoorDash, Instacart) by driving a REAL headed Chrome through the provider's own site UI, with human-in-the-loop 2FA relayed over email. Use when Steve (or any authorized user) says "change my delivery address on <provider>", "my order is going to the wrong address", "save this address on ubereats/doordash/instacart", "/delivery-address-fix", or replies to an in-flight address-fix thread with a 2FA code, card digits, or an address. Born from the 2026-08-02 UberEats session (TK-10140); generalized per Steve's directive ("The system must work for all users. Allow user to plugin this skill for ubereats or Instacart and door dash", 2026-08-02, TK-10145).
+---
+
+# delivery-address-fix — pluggable delivery-address fixer
+
+One engine, N provider adapters. The engine owns the browser session, the
+human-in-the-loop (HITL) email relay, and the hard rails. Each adapter
+(`references/<provider>.md`) owns the provider-specific selectors and flow.
+
+## Architecture
+
+```
+user email/request
+      │
+      ▼
+ ENGINE (this file)          ADAPTERS (references/)
+ - session lifecycle    ──►  ubereats.md   (PROVEN 2026-08-02)
+ - HITL email relay          doordash.md   (scaffold — verify on first run)
+ - hard rails                instacart.md  (scaffold — verify on first run)
+ - screenshots/audit
+```
+
+## Engine: session lifecycle
+
+1. **Workdir per run**: `~/delivery-address-fix/<provider>-<yyyymmdd>/`
+   (screenshots, step scripts, HANDOFF.md). Keep a HANDOFF.md updated after
+   EVERY state change so any later session can resume cold.
+2. **Browser**: long-lived headed REAL Chrome via Playwright persistent
+   context — launcher template in `scripts/browser-server.js` (edit profile
+   dir, CDP port, start URL per provider; one port per provider, 9223+).
+   Headless gets bot-walled; headed real Chrome is the proven path.
+3. **Connect for each step over CDP** (`http://127.0.0.1:<port>`) with short
+   scripts — never restart the browser between steps; the parked session IS
+   the state.
+4. **⚠️ Select the target tab by URL substring** (e.g. `uber.com`), NEVER
+   `pages()[0]` — other tasks open tabs in the same browser (proven failure
+   2026-08-02: a Tesla auth tab became index 0).
+
+## Engine: HITL email relay (2FA, card gates, address)
+
+The user is remote; codes and card digits arrive by email reply, gated by the
+sender allowlist + secret phrase (per the mailbox's standing rules).
+
+- **Code gates (SMS/call/email) EXPIRE FAST** — Uber sessions died in well
+  under 30 min. Enter codes the moment they land. If expired: restart the
+  auth flow (adapter has the script pattern), tell the user the NEWEST code
+  wins, and that any resend INVALIDATES prior codes. Never resend
+  speculatively — only when the user says the code didn't arrive.
+- **Static gates (card-digit verification) do NOT expire** — park and wait.
+  NEVER guess digits, NEVER submit blank (risks killing the session). If the
+  reply contains no digits, re-ask specifying exactly which digits.
+- **Email-code self-read**: if the provider offers an email code and the
+  user's inbox is George-readable, read it yourself (strip `<style>` blocks;
+  beware numbers in the provider's footer address masquerading as codes).
+- Every ask to the user states: what's needed, the exact reply format
+  ("1234 <secret-phrase>"), and that sooner is safer.
+
+## HARD RAILS (engine-level, all providers)
+
+1. **VERBATIM ADDRESS ONLY.** Save exactly the address the user typed —
+   street, city, zip, verbatim. NEVER infer an address from location data,
+   map readings, charger positions, or context. (2026-08-02 lesson: an
+   inferred "668 S Alameda St" from a Supercharger reading was WRONG —
+   "I have never been at alameda!!". If the user hasn't stated the address,
+   ask for it.)
+2. **Never place, cancel, or modify orders** without an explicit per-action
+   go. Address save ≠ order authority.
+3. **Surface before deleting**: list any existing saved addresses that look
+   wrong to the user before removing anything.
+4. **Live-order first**: after login, check for an active order; if it shows
+   a wrong address, use the provider's order-Help change-address path (if
+   offered) before touching the address book.
+5. **Screenshot every step** into the workdir; update HANDOFF.md.
+6. **Credentials**: only from the secrets master (`~/Projects/secrets-manager/.env`)
+   or the user's explicit reply. Never echo full card numbers/codes back —
+   last-4 only.
+7. **Ticket everything** (`tk`); cost line on every report ($0 local browser).
+
+## Multi-user plugin model (Kickback.ai direction)
+
+Per-user config object — everything user-specific is parameterized, nothing
+hardcoded:
+
+```json
+{
+  "user_id": "steve",
+  "provider": "ubereats | doordash | instacart",
+  "login_email": "…",
+  "email_reader": "george:steve-personal | none",
+  "reply_gate": { "allowlist": ["…"], "secret": "…" },
+  "address": { "street": "…", "city": "…", "zip": "…", "nickname": "…" },
+  "cdp_port": 9223
+}
+```
+
+The engine + adapters read ONLY from this config. A hosted/product version
+(Kickback.ai) swaps the email relay for an in-app prompt but keeps the same
+adapter contract: `login() → passGates() → liveOrderCheck() → saveAddress()`.
+
+## Adding a provider
+
+Copy `references/_adapter-template.md`, fill in: auth URL, login selectors,
+2FA gate types observed, address-book path, order-help path, quirks. Mark
+UNVERIFIED until a real run proves each step; promote to PROVEN with dates.
diff --git a/references/_adapter-template.md b/references/_adapter-template.md
new file mode 100644
index 0000000..91e5d82
--- /dev/null
+++ b/references/_adapter-template.md
@@ -0,0 +1,20 @@
+# <Provider> adapter — SCAFFOLD (UNVERIFIED)
+
+Status: UNVERIFIED / PROVEN <date>. Promote only after a real run proves
+each step; note the date per proven step.
+
+## Auth flow
+1. Entry URL:
+2. Identity step (email/phone):
+3. Gate types observed (email OTP / SMS OTP / call / card-digits / captcha):
+4. Expiry behavior + recovery script pattern:
+
+## After login
+1. Live-order check path:
+2. Active-order address-change path (if any):
+3. Address book path + add-address mechanics (autocomplete? pin editor?):
+
+## Quirks
+- Tab-selection URL substring:
+- CDP port:
+- Bot-wall notes:
diff --git a/references/doordash.md b/references/doordash.md
new file mode 100644
index 0000000..fd72e78
--- /dev/null
+++ b/references/doordash.md
@@ -0,0 +1,29 @@
+# DoorDash adapter — SCAFFOLD (UNVERIFIED)
+
+Status: no live run yet. Every selector below must be verified against the
+real site on first run, then this file promoted to PROVEN with dates.
+
+## Expected auth flow
+
+1. `https://www.doordash.com/` → Sign In → `identity.doordash.com`.
+2. Email or phone entry → continue.
+3. Gate types to expect (verify): email OTP, SMS OTP. DoorDash is known to
+   bot-wall aggressively (PerimeterX family) — headed real Chrome mandatory;
+   if walled, the openclaw-real-chrome fallback pattern applies.
+4. No card-digit gate reported for login (verify).
+
+## After login
+
+1. Live-order check: home page banner / Orders tab shows active orders.
+   DoorDash DOES allow address change on an active order in some windows via
+   order Help → "Change address" (verify availability per order state).
+2. Address book: Account → Addresses (`/account/addresses`) → Add Address —
+   search field autocompletes; entering a verbatim street+zip then selecting
+   the exact match satisfies the verbatim rule. Supports apt/suite + drop-off
+   instructions + pin adjust.
+
+## Quirks (to confirm on first run)
+
+- Persistent-context profile reuse may keep the session alive for weeks —
+  prefer reusing the profile before re-running login gates.
+- CDP port suggestion: 9224 (keep one port per provider).
diff --git a/references/instacart.md b/references/instacart.md
new file mode 100644
index 0000000..4903c0d
--- /dev/null
+++ b/references/instacart.md
@@ -0,0 +1,28 @@
+# Instacart adapter — SCAFFOLD (UNVERIFIED)
+
+Status: no live run yet. Every selector below must be verified against the
+real site on first run, then this file promoted to PROVEN with dates.
+
+## Expected auth flow
+
+1. `https://www.instacart.com/` → Log in.
+2. Email entry → Instacart favors passwordless email OTP ("Enter the code we
+   sent to …"); password login exists behind "Use password instead" (verify).
+3. Gate types to expect: email OTP (George-readable if it's the user's
+   inbox), occasional SMS OTP.
+4. No card-digit login gate reported (verify).
+
+## After login
+
+1. Live-order check: Orders page shows active deliveries. Instacart address
+   change on an ACTIVE order is generally NOT self-serve once shopping
+   started — surfaces to care chat (do not attempt without explicit go).
+2. Address book: Account → Addresses → Add new address — autocomplete search;
+   select the exact verbatim match; supports unit + delivery instructions.
+   New orders pick the address at checkout — confirm the default flips to
+   the saved one (verify).
+
+## Quirks (to confirm on first run)
+
+- CDP port suggestion: 9225.
+- Instacart sessions are long-lived once the profile persists (verify).
diff --git a/references/ubereats.md b/references/ubereats.md
new file mode 100644
index 0000000..15a0232
--- /dev/null
+++ b/references/ubereats.md
@@ -0,0 +1,46 @@
+# UberEats adapter — PROVEN 2026-08-02 (TK-10140)
+
+Status: every step below was exercised in the live 2026-08-02 session
+(`~/uber-address-fix/`, screenshots + step scripts there are the reference
+implementation).
+
+## Auth flow (auth.uber.com)
+
+1. Go to `https://www.ubereats.com/` → sign in → lands on `auth.uber.com`.
+2. "What's your phone number or email?" → enter login email → Next
+   (pattern: `step3.js`).
+3. **Email code** (sometimes offered first): read from the user's inbox via
+   George (`step4–6.js`). Strip `<style>` blocks from the HTML; Uber's street
+   address ("1725 …") appears in the footer — do NOT mistake it for the code.
+4. **SMS / phone-call 4-digit code**: 4 separate single-digit inputs, then
+   Next (`step6.js` pattern). Gate behaviors, all observed:
+   - Codes + whole auth sessions expire in well under 30 min → "Bad request —
+     Current session has timed out" even with a correct code.
+   - Recovery: `step8-restart-flow.js` — reload auth.uber.com, resubmit the
+     email; Uber usually skips straight to a fresh 2FA and auto-fires a code.
+   - "Resend code via SMS" can rate-limit → "Error sending code / Bad
+     request / Please use another method"; the restart flow then tends to
+     fall over to PHONE CALL ("Call me with code") or EMAIL code.
+   - Every resend/re-fire INVALIDATES prior codes — newest wins, tell the user.
+5. **Card-verification gate** (final, static — does not expire): "confirm
+   your payment card's missing digits" — shows brand + last-4 (e.g.
+   AMERICAN EXPRESS `____-5001`), one text input + Next. Wants the 4 digits
+   immediately BEFORE the last-4 (on a 15-digit Amex: digits 8–11). No
+   alternate path exists (verified via full element dump). Never guess,
+   never submit blank.
+
+## After login (redirects to ubereats.com)
+
+1. Check for a LIVE order. Wrong address on an active order → order Help →
+   change delivery address if offered.
+2. Account → Addresses → add the user's verbatim address, set nickname; if
+   the pin editor appears, place the pin per the user's instruction.
+3. Screenshot each step; email the user the result.
+
+## Quirks
+
+- Select the Uber tab by URL containing `uber.com`, never `pages()[0]`
+  (`check-uber-tab.js`).
+- Headed real Chrome (channel: 'chrome') required; profile persists in the
+  workdir so a dead process can relaunch without full re-login (sometimes).
+- CDP port used: 9223.
diff --git a/scripts/browser-server.js b/scripts/browser-server.js
new file mode 100644
index 0000000..4dba847
--- /dev/null
+++ b/scripts/browser-server.js
@@ -0,0 +1,20 @@
+// Long-lived headed real-Chrome session with CDP for step scripts.
+// Generic launcher: node browser-server.js <profileDir> <cdpPort> <startUrl>
+const { chromium } = require('playwright');
+const [profileDir, cdpPort, startUrl] = process.argv.slice(2);
+if (!profileDir || !cdpPort || !startUrl) {
+  console.error('usage: node browser-server.js <profileDir> <cdpPort> <startUrl>');
+  process.exit(1);
+}
+(async () => {
+  const ctx = await chromium.launchPersistentContext(profileDir, {
+    channel: 'chrome',
+    headless: false,
+    viewport: { width: 1360, height: 900 },
+    args: [`--remote-debugging-port=${cdpPort}`],
+  });
+  const page = ctx.pages()[0] || (await ctx.newPage());
+  await page.goto(startUrl, { waitUntil: 'domcontentloaded', timeout: 60000 }).catch(e => console.log('nav err', e.message));
+  console.log('READY', page.url());
+  await new Promise(() => {});
+})().catch(e => { console.error('LAUNCH FAIL', e.message); process.exit(1); });

(oldest)  ·  back to Delivery Address Fix  ·  absorb delivery-address-skill (TK-10144): generic launch/sta f519a12 →