Cadeno
Browser flow recorder · Chrome MV3

You demoed it once.
Why are you still doing it?

Skill Recorder is a Chrome extension that watches you walk through a browser task once — then writes a SKILL.md, a plain-markdown spec that Claude Code (or any agent on the browse CLI) replays without selectors, glue code, or babysitting.

v0.4.14.5 MB bundleMV3 service worker21 fixtures12 locales
~/Downloads/recordings/2026-03-create-po.jsonrecording
RAW EVENTS23 captured · 4.6s elapsed
01navigate "/orders/new" // 304 → 200
02click "button.tabs__new" // data-tab=create
03focus "#sku"
04type "SKU-1029" // 8 keystrokes, 1.2s
05blur "#sku"
06type "50" // into #qty
07click "button.submit" ⚠ inside dynamic list
… 16 more (scroll · focus · mousemove · blur · resize)
distilled in 30s ↓
SKILL.md4 steps · 2 inputs · 1 precondition
01# create-purchase-order
02
03// precondition: signed in to supplier portal
04
05## inputs
06- {{sku}} // auto-detected · 8 chars · alphanumeric
07- {{quantity}} // auto-detected · int · 1–999
08
09## steps
101. navigate "/orders/new"
112. fill "#sku" with {{sku}}
123. fill "#qty" with {{quantity}}
134. click submit row matching {{sku}}
↑ a five-minute purchase-order flow,
captured once and replayed forever.
Anatomy

Three stages, one click apart.

What we ship is a small Chrome MV3 extension and a 4-KB distillation pipeline. The recorder taps into chrome.debugger for DOM-level fidelity; the distiller runs entirely in the service worker; the replay reads the resulting SKILL.md through the browse CLI. No server. No glue code.

i.

Capture

Side panel
Skill RecorderREC
00:01click nav.Orders
00:02click btn.New
00:04focus #sku
00:05type "SKU-1029"
00:06blur #sku
00:08type "50" → #qty
00:09click btn.Submit

A per-frame port in the content script forwards every DOM event to the service worker. Shadow DOM and same-origin iframes are pierced transparently.

  • 6-tier selector resolver (testid → id → aria → text → css → xpath)
  • tabPorts: Map<tabId, Map<frameId, Port>>
  • IME-aware input buffering · paste · drag
23 events →
ii.

Distill

Service worker · 30s
dedupe consecutive clicks23 → 16
fold keystrokes into type()16 → 11
detect inputs as {{params}}conf 0.94
mark auth boundary+1 precondition
parameterize URL segments2 swapped
flag dynamic-list clicks1 ⚠ note

Six small passes turn a noisy event log into a deterministic spec. Auto-parameterization only fires above 0.7 confidence; below that the recorder asks rather than guesses.

  • paramConfidence(step) — heuristic ladder
  • auth-boundary detection — opaque token + cookies
  • UUID · numeric ID · email · ISO date · currency
SKILL.md →
iii.

Replay

browse CLI
~/work $ claude
> create POs for these 50 rows
Reading ~/.claude/skills/create-purchase-order/SKILL.md Loaded 4 steps · 2 params · 1 precondition ▸ resolving precondition: signed in to supplier portal cookie present, expires 2026-08-04 ▸ running batch [50 rows] 50/50 in 3m 11s 0 retries, 0 manual rescues

The browse CLI reads SKILL.md as plain markdown — no schema, no runtime, no escape hatches. Every step is auditable.

  • idempotent re-runs · auth pause + resume
  • structured logs ↦ JSONL replay history
  • works with Claude Code, Cline, plain shell
Specification

What survives a recording? Thirteen hard cases, end-to-end.

The browser is full of recorder traps — hashed classes, lazy modals, shadow DOM, cross-tab flows, IME composition, drag-and-drop. Each row below is a real fixture in the playground; click any of them to record against it yourself.

green dot = shipped · blue = automatic
CategoryTechniqueWhat we do about itFixture
Selectors
Hashed class & ID rotation

Tailwind-style classes such as btn__primary--ab3f9c rotate on every deploy; the resolver falls through 6 tiers (testid → id → aria → text → css → xpath) so a re-hashed button still picks.

A2
Selectors
Identical sibling rows

Six rows with literally identical "Pick" buttons — only position distinguishes them. We anchor each click by fingerprintIndex so the right row is clicked, even after the list reorders.

A3
Selectors
Locale-stable identifiers

"Continue" / "继续" / "Weiter" all map to the same data-i18n-key="action.continue". The recorder prefers locale-independent attributes so a skill recorded in English replays under Chinese.

A4
Async
SPA route transitions

pushState and hashchange transitions are tracked without reload; replay waits for the new content to mount instead of probing an empty container.

B1
Async
Lazy-mounted modals

The Confirm button doesn't exist when the trigger is clicked. elementVisible polls until the target shows up before dispatching the next step — no flaky timeouts.

B3
Surfaces
Same-origin iframes

all_frames: true injects per-frame; actions are tagged with the originating frameId, then re-resolved by URL across re-renders so a stale frameId never blocks replay.

C1
Surfaces
Sensitive-field redaction

password / phone / credit-card / SSN inputs are detected by type + autocomplete + name and replaced with *** before they ever hit storage. The bytes never leave the page.

C2
Surfaces
Shadow DOM piercing

A custom shadow selector kind encodes the path as { host, inner } segments; the resolver walks shadowRoot.querySelector at each boundary, so Web Components are first-class.

C3
Surfaces
Cross-tab handoff

When a flow opens a new tab — print preview, OAuth, quote generation — recording follows via chrome.tabs.onCreated. Replay re-creates the tab through chrome.tabs.create.

C4
Input
Native drag & drop

A dragstart → dragover → drop chain with a shared DataTransfer coalesces into one drag step (source + target + types observed). No brittle pixel coordinates.

D1
Input
Modifier-key chords

A bare "k" keypress is text-input noise; with metaKey: true it's a Cmd-K shortcut. The recorder keeps the chord and drops the noise, preserving modifier state for replay.

D3
Input
contenteditable blocks

change never fires on contenteditable. We hook input + compositionend, debounce 300ms, and emit one change step with the final innerText — same shape as a text input.

D4
Input
ARIA combobox & typeahead

Each click step is enriched with comboboxContext (option text, root selector); if the direct click fails on replay, the fallback types the text, ArrowDown until aria-activedescendant matches, then Enter.

D6

Thirteen of sixteen. Plus four composite fixtures — Notion, Linear, Jira, Salesforce — that chain twelve to fifteen of these techniques into a single coherent flow. See the playground →

Worked example

A five-minute browser ritual, distilled to thirty seconds.

Field notebookRecorded 2026-02-19, 16:42 UTCReplay 50× without intervention

Every Monday morning an operations engineer at a small supplier opens the same portal, clicks New purchase order, pastes a SKU from a CSV, types a quantity, and clicks Submit. Then they do it again. And again. Two hundred rows a week, every week, for the last fourteen months. It is a five-minute ritual repeated until the spreadsheet ends.

This is the kind of task that gets a recorder pitched at it — and exactly the kind of task most recorders fail at. The supplier portal rotates Tailwind class hashes on every deploy. The submit button lives inside a dynamic table row that re-renders after each save. The auth cookie expires at noon. Selenium scripts written against this page survive two weeks on average, then quietly start failing.

The recording

The engineer pins the extension to their toolbar, opens the portal, clicks Start recording, and performs the task exactly once. Eight clicks, two typed values, one submit. The side panel shows a live feed of events as they happen — including the noise: scroll, focus, blur, accidental mousemoves over the help icon. Twenty-three events captured in 4.6 seconds.

raw events · 23elapsed 4.6s
01 navigate "/orders/new" // 304 → 200 02 click "button.tabs__new" // data-tab=create 03 focus "#sku" 04 type "SKU-1029" // 8 keystrokes 05 blur "#sku" 06 focus "#qty" 07 type "50" 08 click "button.submit" inside .row[data-id=r9]16 more (scroll · focus · mousemove · blur · resize)
The raw log keeps everything; the distiller decides what matters.

The distillation

main.example.p4

before / after23 events → 4 steps
— BEFORE (raw)
type "S" → #sku
type "K" → #sku
type "U" → #sku
type "-" → #sku
type "1" → #sku
type "0" → #sku
type "2" → #sku
type "9" → #sku
+ AFTER (distilled)
fill "#sku" with {{sku}}
+ AUTO-DETECTED
// 8 chars · alphanumeric · prefix SKU-
// confidence 0.94
Eight keystrokes become one parameterized fill — the same shape every replay.

The hand-off

The engineer drops the resulting markdown into ~/.claude/skills/create-purchase-order/SKILL.md and types a single sentence into Claude Code. The agent finds the skill by name, resolves the precondition (the saved cookie is still valid until noon), iterates over the fifty rows in the CSV, and reports back. Three minutes eleven seconds later, the work is done. No human watched it run.

claude code · stdout3m 11s · 0 retries
> create purchase orders for the rows in ~/Desktop/feb-orders.csv Reading ~/.claude/skills/create-purchase-order/SKILL.md 4 steps · 2 params · 1 precondition Resolving precondition: signed in to supplier portal cookie present (expires 2026-02-19 12:00 UTC) Running batch (50 rows) 50/50 submitted 0 retries · 0 manual rescues median 3.8s per row · longest 5.1s
One sentence in, fifty purchase orders out. The agent never asked a question.
Postscript
The cost of a five-minute ritual,
amortized over a working year.
4:18min
median manual run
3.8sec
median agent run
~187hrs/yr
recovered, this task alone
Want to see it on harder fixtures? Try the playground →
Get the recorder

Record one flow. Let an agent run the next thousand.

Free during the open beta. No account, no upload, no telemetry. The entire bundle is 4.5 MB; recordings live on your machine until you choose to export them.

SKILL.md — a plain-markdown contract between humans and agents.v0.4.1 · Updated 2026-02-19