The Playbook System lets PassAgent execute password resets using human-readable instruction sets
rather than brittle CSS selectors. Each playbook is a JSON file containing plain-English steps
like “Click the Forgot Password link” that get compiled to BQL GraphQL mutations at runtime.This approach gives PassAgent three advantages:
Readability — non-engineers can author and audit reset flows.
Resilience — text-based element matching survives minor DOM changes.
Speed — compiled BQL runs in a single Browserless request with no round-trips.
PassAgent ships with pre-built playbooks for GitHub, Discord, Netflix, Instagram, Facebook,
Google, OpenAI, and X (Twitter). See data/reset-playbooks/ for the full set.
The PlaybookStore class in packages/reset-engine-core/src/playbook-store.ts is a file-based
store that manages playbook JSON files on disk.
Copy
Ask AI
import { PlaybookStore } from "@passagent/reset-engine-core";const store = new PlaybookStore("data/reset-playbooks");// Load a playbookconst playbook = await store.get("github.com");// List all available domainsconst domains = await store.list();// => ["accounts.google.com", "discord.com", "github.com", ...]// Save a new or updated playbookawait store.put(newPlaybook);// Remove a playbookawait store.delete("old-service.com");
The store sanitizes domain keys to filesystem-safe characters and validates that loaded playbooks
have version: 1, a non-empty steps array, and a defined entryUrl.
The compiler in packages/reset-engine-core/src/playbook-compiler.ts transforms a playbook into
a single BQL GraphQL mutation that Browserless executes atomically.
Resource blocking — Rejects images, fonts, and media for faster page loads.
Step compilation — Each step becomes one or more BQL operations with aliased names (step_0_nav, step_1_click, etc.).
Post-action screenshots — Every goto and click step captures a screenshot for debugging.
Final verification — After all steps, the compiler appends a 3-second settle wait, a final screenshot, and two evaluate calls that read document.body.innerText and window.location.href.
Copy
Ask AI
import { compilePlaybook } from "@passagent/reset-engine-core";const { query, operationName } = compilePlaybook( playbook, "user@example.com" // replaces __IDENTITY__ in steps);// query is a complete `mutation PlaybookReset { ... }` string
The click JS uses three-tier matching: exact match, starts-with, then includes — all
case-insensitive against trimmed innerText. It searches button, a, [role=button],
input[type=submit], label, div[tabindex], span[tabindex], and onclick elements.
The type JS tries selectors in priority order: input[type=email], input[name*=email],
input[autocomplete=email], input[name*=username], input[name*=login],
input[type=text] (first visible empty), then input[type=tel]. Values are set using the
React-compatible nativeSetter pattern that dispatches input and change events.
When no playbook exists for a domain, the PlaybookScout agent auto-generates one. The scout:
Navigates to the site’s login or password-reset URL via Browserless.
Takes a screenshot and reads the DOM.
Sends the screenshot and DOM context to Claude, asking it to produce a ResetPlaybook JSON.
Validates the output and saves it to the PlaybookStore.
The scout report at data/playbook-scout-report.json tracks generation results. In the
most recent run, the scout generated playbooks for 47 out of 57 sites (82% success rate).
Failures were primarily CAPTCHA blocks (15 sites), page load timeouts (4), and missing
reset forms (4). Average generation time was ~24 seconds with a single Claude call per site.
After BQL execution, the orchestrator checks whether the reset succeeded by comparing the
final page text against the playbook’s successPatterns array.
Copy
Ask AI
import { compilePlaybookSuccessCheck } from "@passagent/reset-engine-core";const { query } = compilePlaybookSuccessCheck([ "check your email", "we sent", "reset link"]);// Evaluates JS on the page that returns { found: boolean, matched: string[] }
The success check runs as a separate BQL mutation that searches the page’s innerText for
any of the provided patterns (case-insensitive).