poltertab 1.3.0 → 1.5.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -82,14 +82,13 @@ The wizard targets Claude Code. Any other MCP client works too — register
82
82
  Running from a clone instead of npm? Point the client at the file directly:
83
83
  `{"command": "node", "args": ["/absolute/path/to/poltertab/mcp-server/index.js"]}`
84
84
 
85
- > **Legacy:** This extension began as a ZeroClaw plugin and still speaks ZeroClaw's built-in REST bridge (`backend = "bridge"` in `~/.zeroclaw/config.toml`). That path is optional and not required for the MCP setup above.
86
-
87
85
  ## Supported MCP Tools
88
86
 
89
- The server exposes 21 tools to the LLM (all prefixed with `browser_`). Every
87
+ The server exposes 23 tools to the LLM (all prefixed with `browser_`). Every
90
88
  page-facing tool also takes optional `tabId` and `session` to target a specific
91
- tab — omit both and PolterTab uses the last tab it navigated, falling back to
92
- your active tab.
89
+ tab — omit both and PolterTab uses the last tab it navigated. The first
90
+ `browser_navigate` of a session opens its own tab rather than commandeering the
91
+ one you are looking at.
93
92
 
94
93
  ### Page actions
95
94
 
@@ -104,15 +103,77 @@ your active tab.
104
103
 
105
104
  ### Reading the page
106
105
 
106
+ Every tool here takes `output_file`, which writes the payload to
107
+ `~/.poltertab/downloads/` and returns only a summary — row count, field names,
108
+ fill rates and two sample records. Use it for anything bigger than a glance; the
109
+ raw payload should never be the default path into a context window.
110
+
107
111
  | Action | Parameters | Description |
108
112
  | -------- | ----------- | ------------- |
109
- | `browser_scrape` | `selector`, `attribute`, `multiple` | Scrape given elements, or the whole page (title, meta, links, headings, body text) |
110
- | `browser_snapshot` | — | Ref-tagged tree of visible and interactive nodes, capped at 400 |
111
- | `browser_get_text` | `selector` (required) | Text content of one element |
113
+ | `browser_extract` | `record`, `fields` (required), `anchor`, `max_text`, `probe`, `output_file` | Repeating records with fields grouped per record — see below |
114
+ | `browser_extract_all` | `url_template`, `record`, `fields` (required), `key`, `limit`, `offset`, `start_page`, `max_pages`, `fill_tolerance`, `output_file` | Paginate and extract in one call, no model round-trip per page |
115
+ | `browser_scrape` | `selector`, `attribute`, `multiple`, `fields`, `max_text`, `output_file` | Scrape given elements, or the whole page. `attribute` accepts `text` as well as any attribute name; `href`/`src` come back absolute. `fields` picks which parts of a full-page scrape to return — `["meta","jsonld"]` gets og: tags and schema.org blobs without 50KB of body text |
116
+ | `browser_snapshot` | `interactive_only`, `max_nodes`, `max_depth`, `output_file` | Ref-tagged tree of visible and interactive nodes, capped at 400 |
117
+ | `browser_get_text` | `selector` (required), `max_text`, `output_file` | Text content of one element. Flags it in the result when text was cut |
112
118
  | `browser_get_title` | — | Page title and URL |
113
119
  | `browser_get_url` | — | Page URL and title |
114
120
  | `browser_screenshot` | — | Visible tab as base64 PNG (focuses the tab) |
115
121
 
122
+ ### Extracting records
123
+
124
+ `browser_scrape` returns a flat list, which loses track of which value belongs
125
+ to which record. That is fine for one element and dangerous for ninety: the
126
+ moment a field is optional, a flat scrape of names against phone numbers shifts
127
+ every later phone up a row and mis-assigns the rest of the page.
128
+
129
+ `browser_extract` takes a record container and resolves each field *inside* it:
130
+
131
+ ```json
132
+ {
133
+ "record": ".agent-card",
134
+ "fields": {
135
+ "name": { "sel": "a.agent-card-name", "get": "text" },
136
+ "url": { "sel": "a.agent-card-name", "get": "href" },
137
+ "phone": { "sel": "a[href^='tel:']", "get": "href", "strip": "tel:" },
138
+ "socials": { "sel": "a.social-button", "get": "href", "many": true }
139
+ },
140
+ "anchor": "url",
141
+ "output_file": "agents.csv"
142
+ }
143
+ ```
144
+
145
+ - **A missing field yields `null`.** It never shifts.
146
+ - `sel` is relative to the record root; omit it (or use `"."`) for the root itself.
147
+ - `get` is `text` (default), `href`, `src`, or any attribute name.
148
+ - `anchor` names the field a real record always has. Records missing it — the
149
+ placeholder cards that pad the last page of a listing — are dropped and
150
+ counted in `dropped` rather than emitted as rows of nulls.
151
+
152
+ The result reports what happened, so an empty column cannot pass for an answer:
153
+
154
+ ```
155
+ { "count": 12, "dropped": 0,
156
+ "fill_rates": { "name": 12, "phone": 10, "socials": 0 },
157
+ "warnings": ["socials: 0/12 within record scope, but 40 matches page-wide
158
+ for a.social-button — record boundary likely too narrow"] }
159
+ ```
160
+
161
+ That warning is the loosening probe. Any field that comes back entirely empty is
162
+ re-checked page-wide, because the container that *looks* like the record often
163
+ excludes a sibling holding some of the fields — and "0 socials" reads as "these
164
+ people have no social accounts" rather than "your selector scope is wrong".
165
+
166
+ **`browser_extract_all`** takes the same spec plus a `url_template` containing
167
+ `{page}`, and runs the loop itself. It dedups on `key` and halts on the first of:
168
+ `limit_reached`, `empty_page`, `duplicate_page` (a page whose records repeat an
169
+ earlier one — what a site does when it ignores an unrecognised page-size
170
+ param), `fill_rate_deviation` (a page whose fill rates collapse against the
171
+ first page's baseline, which is how a spec learned on page 1 quietly degrades on
172
+ page 10), or `max_pages`. It always reports which condition fired and returns
173
+ everything collected up to that point, so a halt never costs you the run.
174
+
175
+ `.csv` and `.jsonl` output files are written in those formats.
176
+
116
177
  ### Network capture
117
178
 
118
179
  For sites whose DOM is obfuscated or virtualized, read the JSON driving the UI
@@ -148,20 +209,14 @@ next use.
148
209
 
149
210
  Selectors are resolved in order:
150
211
 
151
- 1. **CSS selector** — `#id`, `.class`, `div > span`
152
- 2. **XPath** — `//div[@class="foo"]`
153
- 3. **Text match** — exact text content of an element
154
-
155
- ## CLI Wrapper (Legacy)
156
-
157
- `zc-browser.sh` lets you send commands directly to the legacy ZeroClaw REST bridge for testing (only relevant if you use the optional ZeroClaw bridge path above):
212
+ 1. **Snapshot ref** — `@e12`, as returned by `browser_snapshot`. Resolves to the
213
+ exact node the snapshot described, and beats a generated class chain.
214
+ 2. **CSS selector** — `#id`, `.class`, `div > span`
215
+ 3. **XPath** — `//div[@class="foo"]`
216
+ 4. **Text match** — exact text content of an element
158
217
 
159
- ```bash
160
- ./zc-browser.sh navigate url=https://example.com
161
- ./zc-browser.sh scrape selector="h1"
162
- ./zc-browser.sh get_title
163
- ./zc-browser.sh health
164
- ```
218
+ A miss on all four is retried with shadow-root piercing, so a selector that only
219
+ exists inside a web component still resolves.
165
220
 
166
221
  ## Troubleshooting & Options
167
222
 
@@ -225,7 +280,6 @@ poltertab/
225
280
  │ ├── popup.html/js # Connected-agents UI
226
281
  │ ├── options.html/js # Port configuration UI
227
282
  │ └── icons/
228
- ├── zc-browser.sh # CLI wrapper (legacy)
229
283
  └── README.md
230
284
  ```
231
285
 
@@ -271,7 +325,7 @@ Nothing the server accumulates is stored inside the package:
271
325
  ```
272
326
  ~/.poltertab/
273
327
  ├── navigation_memory/ # Per-domain obstacles and fixes (browser_save_site_memory)
274
- └── downloads/ # output_file payloads from browser_get_network_state
328
+ └── downloads/ # output_file payloads from any read tool
275
329
  ```
276
330
 
277
331
  This matters for a global npm install. The package sits in `node_modules/`, so