Skip to content

Commit 9446f91

Browse files
derekmeeganclaude
andcommitted
Address PR #88 review: rename to browser-to-api, drop lift doc, fix bugs
Renaming and doc cleanup (per shrey150): - Rename skill from `browser-reverse` to `browser-to-api`. Updates SKILL.md frontmatter + heading, package.json, REFERENCE.md heading, the OpenAPI doc's `info.description`, and the report.md heading. - Fix the stale `discover-api-spec` reference in SKILL.md's composition diagram (left over from an earlier rename). - Drop `BODY-CAPTURE-LIFT.md` from the PR; it's a separate proposal. - Remove the `exec.sendFile()` reference in SKILL.md (browserbase-internal, not a generic skill primitive). - REFERENCE.md restructured to lead with the script/CLI/file-format reference rather than an architecture intro. Pipeline diagram dropped. Bug fixes (per Cursor Bugbot): - `filter.mjs`: rework precedence so `--include` actually rescues URLs that would be hit by a default exclude, matching the documented contract. User `--exclude` still wins. Added a unit-style test path. - `infer.mjs`: skip response-body samples whose CDP status is null. Previously they were keyed under `"0"` but `emit.mjs` only iterates `ep.statusCodes` (which excludes nulls), silently discarding the body. - `load.mjs`: fix the comment in `urlQuery()` — code is first-value-wins, not last-value-wins. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 45eb151 commit 9446f91

15 files changed

Lines changed: 36 additions & 154 deletions

File tree

skills/browser-reverse/BODY-CAPTURE-LIFT.md

Lines changed: 0 additions & 118 deletions
This file was deleted.
Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,10 @@
1-
# Browser Reverse — Reference
1+
# Browser to API — Reference
22

3-
Technical reference for the discovery pipeline, file formats, and configuration.
4-
5-
## Pipeline
6-
7-
```
8-
browser-trace run discover.mjs
9-
.o11y/<run>/cdp/network/ ┌─────────┐ ┌────────┐ ┌──────────┐ ┌─────────┐ ┌──────┐
10-
requests.jsonl ──────────▶ │ load │ ─▶ │ filter │ ─▶ │ normalize│ ─▶ │ infer │ ─▶ │ emit │
11-
responses.jsonl └─────────┘ └────────┘ └──────────┘ └─────────┘ └──────┘
12-
paired filtered endpoints endpoints openapi
13-
.jsonl .jsonl .jsonl .with- .yaml
14-
schemas report.md
15-
.jsonl
16-
```
17-
18-
Each stage is a discrete script that reads a file and writes a file. `discover.mjs` is the dispatcher; pass `--stage <name>` to run a single stage for debugging.
3+
Exhaustive reference for every script, flag, file format, and configuration knob the skill exposes.
194

205
## Scripts
216

22-
All scripts are Node ESM (`type: module`). They depend only on the Node standard library.
7+
All scripts are Node ESM (`type: module`). They depend only on the Node standard library. `discover.mjs` is the top-level dispatcher; the others are stage scripts the dispatcher calls in order. Run an individual stage with `discover.mjs --stage <name>` for debugging or partial reruns.
238

249
### `discover.mjs --run <path> [flags]`
2510

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
---
2-
name: browser-reverse
3-
description: Reverse-engineer a website's HTTP API into a best-effort OpenAPI 3.1 spec by analyzing a `browser-trace` capture. Use when the user wants to discover/extract API endpoints from a browser session, build an OpenAPI doc from network traffic, or document a third-party site's XHR/fetch surface for client integration.
2+
name: browser-to-api
3+
description: Turn a website's observable HTTP traffic into a best-effort OpenAPI 3.1 spec by analyzing a `browser-trace` capture. Use when the user wants to discover/extract API endpoints from a browser session, build an OpenAPI doc from network traffic, or document a third-party site's XHR/fetch surface for client integration.
44
compatibility: "Requires Node 18+ and a `browser-trace` run directory (`.o11y/<run>/`) produced by the sibling `browser-trace` skill. The scripts use only the Node standard library — no `npm install` step. `jq` is referenced in docs for ad-hoc querying but is not required by the scripts."
55
license: MIT
66
allowed-tools: Bash, Read, Grep
77
---
88

9-
# Browser Reverse
9+
# Browser to API
1010

11-
Replay-driven API reverse-engineering. Consume a `browser-trace` capture, pair its CDP request / response events, templatize observed URLs, infer JSON schemas from samples, and emit an **OpenAPI 3.1** document plus a human-readable coverage report.
11+
Replay-driven API discovery. Consume a `browser-trace` capture, pair its CDP request / response events, templatize observed URLs, infer JSON schemas from samples, and emit an **OpenAPI 3.1** document plus a human-readable coverage report.
1212

1313
This skill **does not capture traffic**. It is purely offline post-processing on top of `browser-trace`'s `cdp/network/*.jsonl` buckets. The two skills compose:
1414

1515
```
16-
browser-trace → .o11y/<run>/cdp/network/{requests,responses}.jsonl
17-
discover-api-spec → .o11y/<run>/api-spec/openapi.yaml + report.md
16+
browser-trace → .o11y/<run>/cdp/network/{requests,responses}.jsonl
17+
browser-to-api → .o11y/<run>/api-spec/openapi.yaml + report.md
1818
```
1919

2020
## When to use
@@ -67,7 +67,7 @@ node scripts/discover.mjs --run .o11y/my-site
6767

6868
`discover.mjs` auto-detects `<run>/cdp/network/bodies/`. To use a body capture from elsewhere (e.g. didn't snapshot, want the live `browse network` dir), pass `--bodies <path>` explicitly.
6969

70-
Then deliver the artifacts to the user (`exec.sendFile()` for `openapi.yaml` and `report.md`).
70+
The two primary deliverables are `openapi.yaml` (machine-readable spec) and `report.md` (human-readable coverage summary).
7171

7272
## CLI flags
7373

@@ -115,7 +115,7 @@ What changes when bodies are present:
115115
- ✅ Request-body schemas — `postData` from CDP is enough; bodies dir is a nice-to-have for non-`postData` cases.
116116
-**Response-body schemas** — fully inferred from real samples. Without bodies you get `{ description, content: <mimeType> }` skeletons.
117117

118-
The report flags every endpoint that has no response-body sample. For a sketch of what it would take to teach `browser-trace` itself to capture response bodies natively (no separate `browse network on` step), see [BODY-CAPTURE-LIFT.md](BODY-CAPTURE-LIFT.md).
118+
The report flags every endpoint that has no response-body sample.
119119

120120
## Limitations
121121

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "browser-reverse",
2+
"name": "browser-to-api",
33
"version": "0.1.0",
44
"private": true,
55
"type": "module"
File renamed without changes.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ export function emit(outDir, opts = {}) {
247247
info: {
248248
title,
249249
version: '0.1.0-discovered',
250-
description: 'Spec discovered from a browser-trace capture by the browser-reverse skill. Inductive, not contractual — see `report.md` and `x-confidence` extensions for caveats.',
250+
description: 'Spec discovered from a browser-trace capture by the browser-to-api skill. Inductive, not contractual — see `report.md` and `x-confidence` extensions for caveats.',
251251
},
252252
servers,
253253
paths,
@@ -290,7 +290,7 @@ export function emit(outDir, opts = {}) {
290290

291291
function buildReport({ kept, dropped, servers, redaction, minSamples }) {
292292
const lines = [];
293-
lines.push('# Browser-reverse: discovered API\n');
293+
lines.push('# Discovered API\n');
294294
lines.push('## Servers\n');
295295
for (const s of servers) lines.push(`- ${s.url}`);
296296
if (!servers.length) lines.push('_(none)_');
Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,13 @@ const DEFAULT_EXCLUDES = [
3535

3636
export function filter(outDir, opts = {}) {
3737
const { include = [], exclude = [], origins = [] } = opts;
38+
// Precedence:
39+
// 1. --origins gates everything; non-matching is dropped.
40+
// 2. User --exclude always wins (explicit user intent).
41+
// 3. Default excludes can be rescued by --include (REFERENCE.md contract).
42+
// 4. When --include is set, anything that doesn't match it is dropped.
43+
const userExcludeRes = exclude.map(s => new RegExp(s));
3844
const includeRes = include.map(s => new RegExp(s));
39-
const excludeRes = [...DEFAULT_EXCLUDES, ...exclude.map(s => new RegExp(s))];
4045
const originSet = new Set(origins);
4146

4247
const paired = readJsonl(intermediatePath(outDir, 'paired.jsonl'));
@@ -49,8 +54,13 @@ export function filter(outDir, opts = {}) {
4954
const matched = [...originSet].some(o => host === o || host.endsWith('.' + o));
5055
if (!matched) { droppedOrigin++; continue; }
5156
}
52-
if (excludeRes.some(re => re.test(row.url))) { droppedExclude++; continue; }
53-
if (includeRes.length && !includeRes.some(re => re.test(row.url))) { droppedInclude++; continue; }
57+
if (userExcludeRes.some(re => re.test(row.url))) { droppedExclude++; continue; }
58+
59+
const matchesInclude = includeRes.length > 0 && includeRes.some(re => re.test(row.url));
60+
const matchesDefaultExclude = DEFAULT_EXCLUDES.some(re => re.test(row.url));
61+
if (matchesDefaultExclude && !matchesInclude) { droppedExclude++; continue; }
62+
if (includeRes.length && !matchesInclude) { droppedInclude++; continue; }
63+
5464
out.push(row);
5565
}
5666

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,12 @@ export function infer(outDir, opts = {}) {
5555
if (!pickedReqExample) { pickedReqExample = s.reqBody; pickedReqStatus = s.status; }
5656
}
5757
if (s.respBody != null && typeof s.respBody === 'object') {
58-
const status = s.status ?? 0;
59-
let p = respProtoByStatus.get(status);
60-
if (!p) { p = newProto(); respProtoByStatus.set(status, p); }
58+
// Skip when we have no status: emit.mjs only renders schemas under
59+
// statuses that appear in ep.statusCodes (which excludes nulls), so
60+
// a body keyed under "0" would be silently discarded.
61+
if (s.status == null) continue;
62+
let p = respProtoByStatus.get(s.status);
63+
if (!p) { p = newProto(); respProtoByStatus.set(s.status, p); }
6164
ingest(p, s.respBody);
6265
if (s.status >= 200 && s.status < 300 && !pickedRespExample) {
6366
pickedRespExample = s.respBody;
File renamed without changes.

0 commit comments

Comments
 (0)