Docs/Author a playbook with an LLM coding tool raw .md

Author a playbook with an LLM coding tool

The fastest way to write a playbook is to let your coding assistant (Claude Code, Cursor, etc.) draft it, then validate and sign it with the CLI. This page is written to be handed to that assistant — point it here, describe the flow you want, and iterate against obep lint until it's clean.

A playbook is a signed JSON recipe for one browser flow. It carries a fence (exactly which domains, actions, and credential keys are allowed) and an optional deterministic steps list. Secrets are never in the playbook — only key references resolved from the user's on-device vault.

#The closed action set

These are the only 8 actions. Adding one means shipping a new extension version — an LLM cannot invent actions.

action fields does
navigate url go to a URL
click selector click an element
fill selector, value type a non-secret value
fillSecret selector, credentialKey type a secret resolved from the vault (value never in the playbook)
upload selector, file attach a file
wait selector?, timeoutMs? wait for an element / timeout
extract selector, as read a value into the result under key as
done finish

#The shape

{
  "playbookId": "acme.portal-upload",        // ^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$
  "version": 1,
  "tenantId": "acme",
  "permissions": {                            // the FENCE — keep it as tight as the flow needs
    "allowedDomains": ["portal.example.com"], // exact hosts, or single-label wildcard "*.example.com". No bare "*".
    "allowedActions": ["navigate", "fillSecret", "click", "upload", "extract"],
    "allowedCredentialKeys": ["portal_username", "portal_password"], // ^[a-z0-9_]+$ — refs, never values
    "capture": { "screenshots": "never", "fullDom": false, "elementsOnly": true },
    "sensitiveSurfacePolicy": "block"         // or "acknowledge" + "acknowledgeSensitive": ["..."]
  },
  "steps": [                                  // optional deterministic happy-path; omit for pure-LLM
    { "action": "navigate", "url": "https://portal.example.com/login" },
    { "action": "fillSecret", "selector": "#username", "credentialKey": "portal_username" },
    { "action": "fillSecret", "selector": "#password", "credentialKey": "portal_password" },
    { "action": "click", "selector": "#signin" },
    { "action": "navigate", "url": "https://portal.example.com/claims/upload" },
    { "action": "upload", "selector": "input[type=file]", "file": "report.pdf" },
    { "action": "click", "selector": "#submit" },
    { "action": "extract", "selector": ".confirmation-number", "as": "confirmationNumber" }
  ],
  "fallback": "halt"                          // "halt" = stop if a step breaks; "llm" = hand off to your decider
}

issuedAt and signature are added by the CLI at sign time — the assistant does not write them.

#Rules the assistant must follow (these are lint errors otherwise)

  • Secrets only via fillSecret + credentialKey. Never put a password in a fill value or anywhere in the file. Credential keys are references to the user's vault.
  • Tightest possible fence. allowedDomains = only the hosts the flow touches (exact where you can); allowedActions = only the actions used; allowedCredentialKeys = only the keys referenced. Over-broad fences are flagged.
  • No bare * domain, no bare TLD. Single-label wildcards like *.example.com are allowed.
  • Default-tight capture: screenshots: "never", fullDom: false, elementsOnly: true. Anything looser must be justified and is flagged.
  • sensitiveSurfacePolicy: "block" unless a surface is explicitly acknowledged.

#The loop

Have the assistant write flow.json, then drive it against the CLI — the linter is the guardrail, and sign refuses a playbook with lint errors, so the assistant iterates until clean:

npx @obep/cli lint flow.json          # fix every ✖ error the assistant sees, re-run
npx @obep/cli sign flow.json --key keys/tenant.key --out flow-signed.json

Then register flow-signed.json with the relay; it gets a playbookId you call at runtime.

#A prompt you can paste

You are writing an OBEP playbook — a signed JSON recipe for one browser flow.
Read docs/PLAYBOOK_AUTHORING.md for the exact schema, the 8 allowed actions, and
the fence rules. Then write flow.json for this flow:

  <describe the flow: the site, the login fields, the steps, what to extract>

Requirements:
- Use ONLY the 8 actions. Secrets go through fillSecret + a credentialKey (e.g.
  portal_password) — never a literal value.
- Make permissions as tight as the flow needs (exact domains, only actions used,
  only credential keys referenced; capture screenshots:never, fullDom:false).
- Do not add issuedAt or signature.

Then run `npx @obep/cli lint flow.json`, fix every error, and repeat until it
reports clean. Do not sign — I hold the signing key.

The last line matters: the human holds the signing key. An assistant drafts and lints; a person reviews and signs. See the Integration guide for the rest.