@browserwright/pi 0.12.0 → 0.14.0

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
@@ -4,8 +4,8 @@ Two tools for [pi](https://github.com/badlogic/pi-mono), backed by **declarative
4
4
  providers** that drive [browserwright](https://github.com/broven/browserwright):
5
5
 
6
6
  ```
7
- web_fetch(url, provider?) → the page as Markdown
8
- web_search(query, provider?) → ranked links + the SERP features Google showed
7
+ bw_web_fetch(url, provider?) → the page as Markdown
8
+ bw_web_search(query, provider?) → ranked links + the SERP features Google showed
9
9
  ```
10
10
 
11
11
  Both run through the user's **own Chrome**, so they see what the user sees —
@@ -29,14 +29,18 @@ browserwright version check # expect drift=equal
29
29
  The npm package and the Python package are cut from the same git tag, so their
30
30
  versions always match. Install them together.
31
31
 
32
+ The names are `bw_`-prefixed rather than bare `web_fetch`/`web_search` because
33
+ providers reserve generic tool names: grok rejects a custom function named
34
+ `web_search` with a 400, so the prefix keeps every provider safe.
35
+
32
36
  ## The two tools
33
37
 
34
- `web_search` returns **links, never page bodies**. The model then calls
35
- `web_fetch` on the one or two worth reading. That split is deliberate: fetching
38
+ `bw_web_search` returns **links, never page bodies**. The model then calls
39
+ `bw_web_fetch` on the one or two worth reading. That split is deliberate: fetching
36
40
  all ten hits costs ~30 seconds and 50KB+ of context to answer a question that
37
41
  usually needs one of them.
38
42
 
39
- ### What `web_search` returns
43
+ ### What `bw_web_search` returns
40
44
 
41
45
  | field | contents |
42
46
  |-------|----------|
@@ -106,11 +110,11 @@ This package ships **only the browserwright rungs**. There is one per tool:
106
110
 
107
111
  | tool | provider | kind |
108
112
  |------|----------|------|
109
- | `web_fetch` | `browserwright` | `command` — `browserwright markdown <url>` |
110
- | `web_search` | `browserwright-search` | `module` — a session lifecycle in TS |
113
+ | `bw_web_fetch` | `browserwright` | `command` — `browserwright markdown <url>` |
114
+ | `bw_web_search` | `browserwright-search` | `module` — a session lifecycle in TS |
111
115
 
112
116
  That is a real trade-off, and it points the wrong way for casual fetches: every
113
- `web_fetch` opens a tab in the daily browser and takes ~4-7s, where a hosted
117
+ `bw_web_fetch` opens a tab in the daily browser and takes ~4-7s, where a hosted
114
118
  reader API answers in ~1s without touching Chrome. What you get for it is login
115
119
  state and full JS rendering, which no anonymous rung has.
116
120
 
@@ -247,11 +251,14 @@ worth repeating.
247
251
  ## Probe: rules from evidence, not guesses
248
252
 
249
253
  ```
250
- /browserwright list # show both chains
251
- /browserwright probe # every fetch provider
252
- /browserwright probe browserwright
254
+ /bw list # show both chains
255
+ /bw probe # every fetch provider
256
+ /bw probe browserwright
253
257
  ```
254
258
 
259
+ The slash command is `/bw` (not `/browserwright`) so it cannot be confused with
260
+ pi's `browserwright` skill, which pi exposes as `/skill:browserwright`.
261
+
255
262
  Runs a provider against the real URLs in `probe-cases.json` — a normal article,
256
263
  a client-rendered shell, a bot wall, a login wall, a 404, a page past the
257
264
  truncation limit, a PDF, and localhost — then prints what came back and writes
@@ -304,7 +311,7 @@ the `tool_result` event see `isError: false`. So a chain failure here throws.
304
311
  attacker, and blocking private addresses would remove localhost fetching,
305
312
  which is a real workflow. The requested URL is printed so internal fetches
306
313
  stay visible in the transcript.
307
- - **No body fetching inside `web_search`.** See the two-tool split above.
314
+ - **No body fetching inside `bw_web_search`.** See the two-tool split above.
308
315
 
309
316
  ## License
310
317
 
package/core/chain.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * The engine is payload-agnostic: it never looks inside `content`, it only asks
10
10
  * the caller-supplied `inspect` to reduce it to text plus an item count. That is
11
- * what lets one chain serve `web_fetch` (a Markdown blob) and `web_search`
11
+ * what lets one chain serve `bw_web_fetch` (a Markdown blob) and `bw_web_search`
12
12
  * (a list of results).
13
13
  */
14
14
 
package/core/config.ts CHANGED
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * Provider declarations are plain JSON files in providers/. Adding a provider
5
5
  * means dropping one file in there — no code change, no registration table.
6
- * A declaration that names no `role` serves `web_fetch`, which is what the
7
- * majority of reader APIs are.
6
+ * A declaration that names no `role` serves the fetch role (`bw_web_fetch`),
7
+ * which is what the majority of reader APIs are.
8
8
  */
9
9
 
10
10
  import { existsSync, readdirSync, readFileSync } from "node:fs";
@@ -30,7 +30,7 @@ const DEFAULT_CONFIG: PiConfig = {
30
30
  // The default line of defence. minChars stays 0 on purpose: a false positive
31
31
  // escalates to a rung that opens a tab in the user's real Chrome, so
32
32
  // over-eager rejection interrupts them. Per-provider thresholds are meant to
33
- // come from `/browserwright probe` evidence, not from guesses.
33
+ // come from `/bw probe` evidence, not from guesses.
34
34
  defaultFailWhen: {
35
35
  minChars: 0,
36
36
  minResults: 0,
@@ -25,7 +25,7 @@ import type { ModuleContext, ModuleProvider, ModuleRunner, ProviderOutcome, Role
25
25
  /**
26
26
  * Completed and in-flight loads, keyed by spec.
27
27
  *
28
- * This stores the PROMISE, not the resolved runner, on purpose. Two web_search
28
+ * This stores the PROMISE, not the resolved runner, on purpose. Two bw_web_search
29
29
  * calls fired in the same turn both miss a value cache and both import; under
30
30
  * pi's jiti runtime that race is not merely wasteful but wrong — the second
31
31
  * caller can observe a half-initialized module record and fail the load (see
package/core/format.ts CHANGED
@@ -117,7 +117,7 @@ const ANSWER_BOX_CAP = 1200;
117
117
 
118
118
  /**
119
119
  * The search success path. Links plus whatever SERP features the query
120
- * triggered — but never page bodies: `web_fetch` already does that, and the
120
+ * triggered — but never page bodies: `bw_web_fetch` already does that, and the
121
121
  * model is in a better position to decide which two of ten links are worth the
122
122
  * tokens.
123
123
  *
@@ -171,7 +171,7 @@ export function renderResults(result: ChainResult<SearchPayload>, query: string)
171
171
  out.push("## Related searches", payload.relatedSearches.join(" · "), "");
172
172
  }
173
173
 
174
- out.push("Use web_fetch on a URL above to read it.");
174
+ out.push("Use bw_web_fetch on a URL above to read it.");
175
175
  return out.join("\n");
176
176
  }
177
177
 
package/index.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  /**
2
- * @browserwright/pi — `web_fetch` and `web_search` for pi, backed by
2
+ * @browserwright/pi — `bw_web_fetch` and `bw_web_search` for pi, backed by
3
3
  * declarative providers that drive browserwright.
4
4
  *
5
+ * Tool names are `bw_`-prefixed (not bare `web_fetch`/`web_search`) because
6
+ * providers reserve generic tool names: grok rejects a custom function named
7
+ * `web_search` with a 400. The prefix keeps every provider safe.
8
+ *
5
9
  * A provider is a JSON file in providers/; adding one needs no code change.
6
10
  * This package ships only the browserwright rungs, which are the ones that
7
11
  * carry the user's login state. Drop your own JSON in to add a cheaper or
@@ -48,10 +52,10 @@ export default function (pi: ExtensionAPI) {
48
52
  onUpdate?.({ content: [{ type: "text", text: `*${text}*` }] });
49
53
  };
50
54
 
51
- // ---- web_fetch ---------------------------------------------------------
55
+ // ---- bw_web_fetch ------------------------------------------------------
52
56
 
53
57
  pi.registerTool({
54
- name: "web_fetch",
58
+ name: "bw_web_fetch",
55
59
  label: "Fetch Web Page",
56
60
  description:
57
61
  "Fetch a URL and return its content as Markdown. " +
@@ -60,7 +64,7 @@ export default function (pi: ExtensionAPI) {
60
64
  "Output over 50KB is truncated and the full text written to a temp file whose path is given.",
61
65
  promptSnippet: "Fetch a URL as markdown, through the user's real browser",
62
66
  promptGuidelines: [
63
- "Prefer `web_fetch` over curl or a shell HTTP client for reading web pages — it renders JavaScript " +
67
+ "Prefer `bw_web_fetch` over curl or a shell HTTP client for reading web pages — it renders JavaScript " +
64
68
  "and carries the user's login state, so it can read pages an anonymous request cannot.",
65
69
  ],
66
70
  parameters: Type.Object({
@@ -92,7 +96,7 @@ export default function (pi: ExtensionAPI) {
92
96
  if (ctx.hasUI) ctx.ui.setStatus("browserwright", "");
93
97
 
94
98
  if (!result.ok) {
95
- throw new ToolFailure(renderFailure(result, url, { tool: "web_fetch", alternatives: fetchNames }));
99
+ throw new ToolFailure(renderFailure(result, url, { tool: "bw_web_fetch", alternatives: fetchNames }));
96
100
  }
97
101
 
98
102
  return {
@@ -113,20 +117,20 @@ export default function (pi: ExtensionAPI) {
113
117
  },
114
118
  });
115
119
 
116
- // ---- web_search --------------------------------------------------------
120
+ // ---- bw_web_search -----------------------------------------------------
117
121
 
118
122
  pi.registerTool({
119
- name: "web_search",
123
+ name: "bw_web_search",
120
124
  label: "Search the Web",
121
125
  description:
122
126
  "Search the web and return ranked results as title, URL, snippet and date. " +
123
127
  `Providers in order: ${config.order.search.join(" → ")}. ` +
124
128
  "Also returns the engine's own AI Overview, knowledge panel, 'people also ask' and " +
125
129
  "related searches when that query triggered them. Returns links, never page bodies — " +
126
- "call web_fetch on the ones worth reading.",
130
+ "call bw_web_fetch on the ones worth reading.",
127
131
  promptSnippet: "Search the web and get back ranked links",
128
132
  promptGuidelines: [
129
- "`web_search` returns links, not page contents. After searching, call `web_fetch` on the one or two " +
133
+ "`bw_web_search` returns links, not page contents. After searching, call `bw_web_fetch` on the one or two " +
130
134
  "results actually worth reading rather than fetching all of them.",
131
135
  ],
132
136
  parameters: Type.Object({
@@ -141,7 +145,7 @@ export default function (pi: ExtensionAPI) {
141
145
  }),
142
146
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
143
147
  const query = params.query.trim();
144
- if (!query) throw new ToolFailure("web_search needs a non-empty query");
148
+ if (!query) throw new ToolFailure("bw_web_search needs a non-empty query");
145
149
  const setStatus = statusReporter(ctx, onUpdate);
146
150
 
147
151
  const result = await runChain<SearchPayload>({
@@ -167,7 +171,7 @@ export default function (pi: ExtensionAPI) {
167
171
  if (ctx.hasUI) ctx.ui.setStatus("browserwright", "");
168
172
 
169
173
  if (!result.ok) {
170
- throw new ToolFailure(renderFailure(result, query, { tool: "web_search", alternatives: searchNames }));
174
+ throw new ToolFailure(renderFailure(result, query, { tool: "bw_web_search", alternatives: searchNames }));
171
175
  }
172
176
 
173
177
  return {
@@ -188,11 +192,13 @@ export default function (pi: ExtensionAPI) {
188
192
  },
189
193
  });
190
194
 
191
- // ---- /browserwright ----------------------------------------------------
195
+ // ---- /bw ---------------------------------------------------------------
196
+ // Named `/bw` (not `/browserwright`) so it cannot be confused with the
197
+ // `browserwright` skill, which pi exposes as `/skill:browserwright`.
192
198
 
193
- pi.registerCommand("browserwright", {
199
+ pi.registerCommand("bw", {
194
200
  description:
195
- "Inspect providers (/browserwright list) or probe one against real URLs (/browserwright probe <provider>)",
201
+ "Inspect providers (/bw list) or probe one against real URLs (/bw probe <provider>)",
196
202
  handler: async (args, ctx) => {
197
203
  const [subcommand, target] = args.trim().split(/\s+/);
198
204
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@browserwright/pi",
3
- "version": "0.12.0",
4
- "description": "web_fetch and web_search for the pi coding agent, driving the user's real browser through browserwright",
3
+ "version": "0.14.0",
4
+ "description": "bw_web_fetch and bw_web_search for the pi coding agent, driving the user's real browser through browserwright",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",
7
7
  "keywords": [
package/probe-run.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Headless probe runner — the same code path as `/webfetch probe`, without pi's
2
+ * Headless probe runner — the same code path as `/bw probe`, without pi's
3
3
  * confirmation dialog. Use it when you want the evidence matrix captured to a
4
4
  * file rather than rendered into a session.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The `web_search` rung: drive a real search engine in the user's own Chrome.
2
+ * The `bw_web_search` rung: drive a real search engine in the user's own Chrome.
3
3
  *
4
4
  * This is a `kind: "module"` provider rather than a `kind: "command"` one
5
5
  * because a search is not one shot at a subprocess. It is: mint a session,
package/verify.ts CHANGED
@@ -41,7 +41,7 @@ if (url !== undefined) {
41
41
  console.log(
42
42
  chained.ok
43
43
  ? `${renderSuccess(chained, { url, maxBytes: config.maxBytes, maxLines: config.maxLines }).slice(0, 600)}\n`
44
- : `${renderFailure(chained, url, { tool: "web_fetch", alternatives: [...providersForRole(providers, "fetch").keys()] })}\n`,
44
+ : `${renderFailure(chained, url, { tool: "bw_web_fetch", alternatives: [...providersForRole(providers, "fetch").keys()] })}\n`,
45
45
  );
46
46
 
47
47
  console.log("── each fetch provider in isolation ──");
@@ -84,7 +84,7 @@ if (query !== undefined) {
84
84
  searched.ok
85
85
  ? renderResults(searched, query)
86
86
  : renderFailure(searched, query, {
87
- tool: "web_search",
87
+ tool: "bw_web_search",
88
88
  alternatives: [...providersForRole(providers, "search").keys()],
89
89
  }),
90
90
  );