browse 0.8.3 → 0.8.4

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
@@ -6,7 +6,7 @@
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/browse.svg?style=flat-square&color=000000)](https://www.npmjs.com/package/browse)
8
8
  [![npm downloads](https://img.shields.io/npm/dm/browse.svg?style=flat-square&color=000000)](https://www.npmjs.com/package/browse)
9
- [![license](https://img.shields.io/npm/l/browse.svg?style=flat-square&color=000000)](https://github.com/browserbase/stagehand/blob/main/packages/cli/LICENSE)
9
+ [![license](https://img.shields.io/badge/license-MIT-000000?style=flat-square)](https://github.com/browserbase/stagehand/blob/main/packages/cli/LICENSE)
10
10
 
11
11
  ```bash
12
12
  npm install -g browse
@@ -22,7 +22,7 @@ npm install -g browse
22
22
 
23
23
  ## Why browse
24
24
 
25
- - **Browser interactions** — Navigate tricky, complex websites with `browse click`, `browse mouse scroll`, `browse type`, `browse select`, and 20+ more DOM commands.
25
+ - **Browser interactions** — Navigate tricky, complex websites with `browse click`, `browse mouse scroll`, `browse type`, `browse select`, and 30+ more DOM commands.
26
26
  - **Open web skills catalog** — `browse` is the official CLI for [browse.sh](https://browse.sh), the largest open web catalog. Run `browse skills add apartments.com` and your agent learns how to use that site and its APIs.
27
27
  - **Rich debugging** — Arm your agents with network, console, and other web telemetry.
28
28
  - **Cloud features** — Optionally use Browserbase cloud: load cookies via saved [Contexts](https://docs.browserbase.com/platform/browser/core-features/contexts), use [Verified Browsers](https://www.browserbase.com/verified), and call the [Fetch and Search APIs](https://www.browserbase.com/search).
@@ -41,36 +41,154 @@ browse screenshot --path page.png
41
41
  browse stop
42
42
  ```
43
43
 
44
- ## Driver commands
44
+ ## How it works
45
45
 
46
- Drive a real browser from the terminal — locally, over CDP, or on a remote Browserbase session.
46
+ `browse` runs a lightweight per-session daemon. The first command starts it, and subsequent commands reuse the same browser — so cookies, tabs, and snapshot refs persist between invocations. Run multiple isolated browsers at once with `--session <name>` (or the `BROWSE_SESSION` env var), and shut a session down with `browse stop`.
47
+
48
+ ### Browser targets
49
+
50
+ Every driver command accepts the same flags to pick where the browser runs. Mix and match per command:
51
+
52
+ | Flag | Target |
53
+ |------|--------|
54
+ | _(default)_ | Managed local browser, or remote when `BROWSERBASE_API_KEY` is set |
55
+ | `--local` | Managed local browser (add `--headed` / `--headless`) |
56
+ | `--remote` | Remote Browserbase session (uses `BROWSERBASE_API_KEY`) |
57
+ | `--auto-connect` | Auto-discover and attach to a local Chrome with remote debugging enabled |
58
+ | `--cdp <url\|port>` | Attach directly to a CDP endpoint (port, `http(s)://`, or `ws(s)://`) |
59
+ | `--target-id <id>` | Select a specific CDP target when attaching to an existing browser |
47
60
 
48
61
  ```bash
49
- browse open https://example.com
62
+ browse open https://example.com # default target
50
63
  browse open https://example.com --local --headed
51
64
  browse open https://example.com --remote
52
65
  browse open https://example.com --auto-connect
53
66
  browse open https://example.com --cdp 9222
54
67
  browse open https://example.com --cdp ws://127.0.0.1:9222/devtools/browser/<id> --target-id <target-id>
55
- browse snapshot --compact
56
- browse click @0-12
57
- browse fill @0-8 "hello"
58
- browse mouse click 240 320
59
- browse get title
60
- browse screenshot --path page.png
61
- browse tab list
62
- browse tab switch <target-id>
63
- browse network on
64
- browse cdp 9222 --pretty
65
- browse status
66
- browse stop
67
68
  ```
68
69
 
69
- Use `--local`, `--remote`, `--auto-connect`, or `--cdp <url|port>` per command to choose the browser target. Use `--target-id <id>` with `--cdp` when attaching to a specific CDP target. Driver commands use `BROWSERBASE_API_KEY` for remote Browserbase sessions.
70
+ Run `browse doctor` to diagnose session and browser-connection prerequisites for any target.
71
+
72
+ ## Commands
73
+
74
+ ### Navigation
75
+
76
+ ```bash
77
+ browse open <url> # Open a URL (--wait load|domcontentloaded|networkidle, --timeout <ms>)
78
+ browse reload # Reload the active page
79
+ browse back # Navigate backward
80
+ browse forward # Navigate forward
81
+ ```
82
+
83
+ ### Snapshot & refs
84
+
85
+ The accessibility snapshot is the recommended way for agents to discover elements. It prints a tree of refs like `@0-12` that the element commands accept directly.
86
+
87
+ ```bash
88
+ browse snapshot # Accessibility snapshot + cached refs
89
+ browse snapshot --compact # Tree only, no ref maps
90
+ browse snapshot --filter submit # Filter lines by text or /regex/, keeping ancestors
91
+ browse snapshot --max-depth 4 # Trim output deeper than this depth
92
+ browse refs # Show refs cached from the last snapshot
93
+ ```
94
+
95
+ ### Element actions
96
+
97
+ Targets accept a snapshot ref (`@0-12`), an XPath, or a CSS selector.
98
+
99
+ ```bash
100
+ browse click @0-12 # Click (also accepts selectors)
101
+ browse fill @0-8 "hello" # Fill an input (--press-enter to submit)
102
+ browse select @0-9 "CA" # Select an option (--value for <option value>)
103
+ browse type "hello world" # Type at the current focus (--delay, --mistakes)
104
+ browse press Enter # Press a key (alias: browse key) e.g. Meta+K, Escape
105
+ browse upload @0-4 ./resume.pdf # Upload file(s) (repeat --file for more)
106
+ browse highlight @0-12 # Highlight an element (--duration <ms>)
107
+ ```
108
+
109
+ ### Mouse (raw coordinates)
110
+
111
+ Use these when you need pixel coordinates instead of a ref. Add `--return-xpath` to get the XPath under the cursor.
112
+
113
+ ```bash
114
+ browse mouse click 240 320 # Click coordinates (--button, --click-count)
115
+ browse mouse hover 240 320 # Move the mouse
116
+ browse mouse scroll 400 500 0 600 # Scroll from a point by (dx, dy)
117
+ browse mouse drag 100 100 400 400 # Drag between two points (--steps, --delay)
118
+ ```
119
+
120
+ ### Page info & state
121
+
122
+ ```bash
123
+ browse get url # Read page data / element state:
124
+ browse get title # url, title, text, html, value, box,
125
+ browse get text @0-12 # visible, checked, markdown
126
+ browse get markdown body
127
+ browse get box 'button[type=submit]'
128
+ browse is visible @0-12 # Check element state: visible, checked
129
+ browse eval 'document.title' # Evaluate JavaScript in the page
130
+ browse viewport 1280 720 # Set viewport size (--scale for device pixel ratio)
131
+ browse cursor # Enable a visible cursor overlay
132
+ browse screenshot --path page.png # Screenshot (--full-page, --type, --quality, --clip)
133
+ ```
134
+
135
+ ### Waiting
136
+
137
+ ```bash
138
+ browse wait load # Wait for a load state
139
+ browse wait load networkidle --timeout 45000
140
+ browse wait selector @0-12 --state visible # visible|hidden|attached|detached
141
+ browse wait timeout 1000 # Wait a fixed number of ms
142
+ ```
143
+
144
+ ### Tabs
145
+
146
+ ```bash
147
+ browse tab list # List tabs (with stable targetIds)
148
+ browse tab new [url] # Open a new tab and make it active
149
+ browse tab switch <target-id> # Switch active tab (index or targetId)
150
+ browse tab close [target-id] # Close a tab (defaults to the active tab)
151
+ ```
152
+
153
+ Prefer the `targetId` from `browse tab list` over the index for stable agent workflows.
154
+
155
+ ### Network capture
156
+
157
+ Capture request/response traffic for the active session to a local directory.
158
+
159
+ ```bash
160
+ browse network on # Start capturing
161
+ browse network off # Stop capturing
162
+ browse network path # Print the capture directory
163
+ browse network clear # Clear captured requests
164
+ ```
70
165
 
71
166
  > [!NOTE]
72
167
  > `browse network on` writes request/response headers and bodies to a local owner-only capture directory. These files can include cookies, authorization headers, and other secrets — use network capture only on trusted machines and run `browse network clear` when done.
73
168
 
169
+ ### Session & daemon
170
+
171
+ ```bash
172
+ browse status # Show daemon status for a session
173
+ browse stop # Stop the daemon (--force to kill an unresponsive browser)
174
+ browse doctor # Diagnose session and browser-connection prerequisites
175
+ browse cdp 9222 # Attach to a CDP endpoint and stream DevTools events (--domain, --pretty)
176
+ ```
177
+
178
+ ### Global flags
179
+
180
+ These apply across driver commands:
181
+
182
+ | Flag | Description |
183
+ |------|-------------|
184
+ | `-s, --session <name>` | Named browser session (or `BROWSE_SESSION` env var) |
185
+ | `--local` / `--remote` | Choose a managed local or remote Browserbase browser |
186
+ | `--headed` / `--headless` | Window visibility for managed local sessions |
187
+ | `--auto-connect` | Attach to a local Chrome with remote debugging enabled |
188
+ | `--cdp <url\|port>` | Attach directly to a CDP endpoint |
189
+ | `--target-id <id>` | Select a specific CDP target |
190
+ | `--json` | Emit machine-readable JSON (available on most commands) |
191
+
74
192
  ## Open web skills catalog
75
193
 
76
194
  Use [browse.sh](https://browse.sh), the largest open-source catalog of skills to reliably perform any task on the internet. Find a specialized skill to navigate `apartments.com`, for example, and drastically reduce your agent's time and token costs.
@@ -78,6 +196,7 @@ Use [browse.sh](https://browse.sh), the largest open-source catalog of skills to
78
196
  ```bash
79
197
  browse skills install # install the bundled browse CLI skill
80
198
  browse skills list # list the public Browse.sh catalog
199
+ browse skills list --all # include every catalog entry
81
200
  browse skills find reviews # search by slug, domain, title, tag…
82
201
  browse skills find yelp.com/extract-reviews
83
202
  browse skills add yelp.com/extract-reviews # install a catalog skill
@@ -86,23 +205,37 @@ browse skills add mcdonalds.order.online/order-delivery-42q71n
86
205
 
87
206
  ## Browserbase cloud commands
88
207
 
89
- Manage projects, sessions, contexts, and extensions, or call the Fetch and Search APIs directly.
208
+ Manage projects, sessions, contexts, and extensions, or call the Fetch and Search APIs directly. These commands use `BROWSERBASE_API_KEY`.
90
209
 
91
210
  ```bash
211
+ # Projects
92
212
  browse cloud projects list
93
213
  browse cloud projects get <project-id>
94
214
  browse cloud projects usage <project-id>
95
- browse cloud sessions list
215
+
216
+ # Sessions
217
+ browse cloud sessions list # --limit, --status, --json
96
218
  browse cloud sessions get <session-id>
97
- browse cloud sessions create
98
- browse cloud sessions debug <session-id>
219
+ browse cloud sessions create # --proxies, --verified, --region, --solve-captchas…
220
+ browse cloud sessions update <session-id> --status REQUEST_RELEASE
221
+ browse cloud sessions debug <session-id> # live debugger URLs
99
222
  browse cloud sessions logs <session-id>
100
- browse cloud sessions downloads get <session-id>
101
- browse cloud sessions uploads create <session-id> <file>
223
+ browse cloud sessions downloads get <session-id> # --output ./downloads.zip
224
+ browse cloud sessions uploads create <session-id> ./file.pdf
225
+
226
+ # Contexts
102
227
  browse cloud contexts create
103
228
  browse cloud contexts get <context-id>
104
- browse cloud extensions upload <file>
105
- browse cloud fetch <url>
229
+ browse cloud contexts update <context-id> # refresh the upload URL
230
+ browse cloud contexts delete <context-id>
231
+
232
+ # Extensions
233
+ browse cloud extensions upload ./extension.zip
234
+ browse cloud extensions get <extension-id>
235
+ browse cloud extensions delete <extension-id>
236
+
237
+ # Fetch & Search APIs
238
+ browse cloud fetch <url> # markdown by default
106
239
  browse cloud search <query>
107
240
  ```
108
241
 
@@ -113,14 +246,24 @@ browse cloud search <query>
113
246
  Browserbase [Functions](https://docs.browserbase.com/platform/runtime/overview) let you deploy browser agents and automation scripts directly onto Browserbase's infrastructure. Build locally, test instantly, and deploy as APIs.
114
247
 
115
248
  ```bash
116
- browse functions init my-function
117
- browse functions dev index.ts
118
- browse functions publish index.ts
119
- browse functions publish index.ts --dry-run
249
+ browse functions init my-function # scaffold a new project (--package-manager)
250
+ browse functions dev index.ts # local development server (--port, --verbose)
251
+ browse functions publish index.ts # package and upload (--dry-run to preview)
120
252
  browse functions invoke <function-id> --params '{"url":"https://example.com"}'
121
253
  browse functions invoke --check-status <invocation-id>
122
254
  ```
123
255
 
256
+ ## Templates
257
+
258
+ Discover and scaffold ready-to-run Browserbase example projects.
259
+
260
+ ```bash
261
+ browse templates list # --tag, --source, --wide, --json
262
+ browse templates find amazon # search by slug, title, category, or tag
263
+ browse templates clone google-trends-keywords
264
+ browse templates clone amazon-product-scraping --language python ./my-scraper
265
+ ```
266
+
124
267
  ## Configuration
125
268
 
126
269
  Set your Browserbase API key to enable remote sessions and cloud commands:
@@ -131,6 +274,11 @@ export BROWSERBASE_API_KEY=bb_live_...
131
274
 
132
275
  Local driver commands (`--local`) work without an API key.
133
276
 
277
+ | Variable | Description |
278
+ |----------|-------------|
279
+ | `BROWSERBASE_API_KEY` | Enables `--remote` sessions and all `browse cloud` / `functions` commands |
280
+ | `BROWSE_SESSION` | Default session name (alternative to `-s, --session`) |
281
+
134
282
  ## Links
135
283
 
136
284
  - [browse.sh](https://browse.sh) — open web skills catalog
@@ -1,5 +1,6 @@
1
1
  import { Args } from "@oclif/core";
2
2
  import { BrowseCommand } from "../../base.js";
3
+ import { fail } from "../../lib/errors.js";
3
4
  import { installSkill } from "../../lib/skills/install.js";
4
5
  export default class SkillsAdd extends BrowseCommand {
5
6
  static description = "Install a browser automation skill.";
@@ -8,16 +9,18 @@ export default class SkillsAdd extends BrowseCommand {
8
9
  "browse skills add mcdonalds.order.online/order-delivery-42q71n",
9
10
  ];
10
11
  static args = {
12
+ // Intentionally optional so we can emit actionable guidance instead of
13
+ // oclif's bare "Missing 1 required arg" error.
11
14
  skill: Args.string({
12
- required: true,
15
+ required: false,
13
16
  description: "Skill id in the form <domain>/<task>.",
14
17
  }),
15
18
  };
16
19
  async run() {
17
20
  const { args } = await this.parse(SkillsAdd);
18
- const exitCode = await installSkill(args.skill);
19
- if (exitCode !== 0) {
20
- this.exit(exitCode);
21
+ if (!args.skill) {
22
+ fail("Missing skill id. Pass a skill in the form <domain>/<task>, e.g. `browse skills add yelp.com/extract-reviews`. Run `browse skills find <query>` to discover skills.", 2, { resultCode: "invalid_skill_id" });
21
23
  }
24
+ await installSkill(args.skill);
22
25
  }
23
26
  }
@@ -4,9 +4,6 @@ export default class SkillsInstall extends BrowseCommand {
4
4
  static description = "Install the bundled browse CLI skill.";
5
5
  static examples = ["browse skills install"];
6
6
  async run() {
7
- const exitCode = await installBundledCliSkill();
8
- if (exitCode !== 0) {
9
- this.exit(exitCode);
10
- }
7
+ await installBundledCliSkill();
11
8
  }
12
9
  }
@@ -11,14 +11,19 @@ const generatedSkillSuffixPattern = /-[A-Za-z0-9]{6}$/;
11
11
  const domainPattern = /^[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$/;
12
12
  const taskPattern = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
13
13
  const maxSkillFiles = 100;
14
+ const maxCapturedOutputBytes = 2048;
14
15
  export function parseSkillId(rawSkillId) {
15
16
  const parts = rawSkillId.split("/");
16
17
  if (parts.length !== 2) {
17
- fail("Skill must be in the form <domain>/<task>.");
18
+ fail("Skill must be in the form <domain>/<task>.", 1, {
19
+ resultCode: "invalid_skill_id",
20
+ });
18
21
  }
19
22
  const [domain, task] = parts;
20
23
  if (!domain || !task) {
21
- fail("Skill must be in the form <domain>/<task>.");
24
+ fail("Skill must be in the form <domain>/<task>.", 1, {
25
+ resultCode: "invalid_skill_id",
26
+ });
22
27
  }
23
28
  if (rawSkillId.includes("\\") ||
24
29
  domain === "." ||
@@ -27,7 +32,9 @@ export function parseSkillId(rawSkillId) {
27
32
  task === ".." ||
28
33
  !domainPattern.test(domain) ||
29
34
  !taskPattern.test(task)) {
30
- fail(`Invalid skill id "${rawSkillId}". Use <domain>/<task>.`);
35
+ fail(`Invalid skill id "${rawSkillId}". Use <domain>/<task>.`, 1, {
36
+ resultCode: "invalid_skill_id",
37
+ });
31
38
  }
32
39
  return {
33
40
  domain,
@@ -42,20 +49,26 @@ export async function installSkill(rawSkillId) {
42
49
  const skillId = parseSkillId(rawSkillId);
43
50
  const npxPath = await findExecutable("npx");
44
51
  if (!npxPath) {
45
- fail("`npx` is not installed. Install Node.js from https://nodejs.org, then rerun `browse skills add`.");
52
+ fail("`npx` is not installed. Install Node.js from https://nodejs.org, then rerun `browse skills add`.", 1, { resultCode: "npx_missing" });
46
53
  }
47
54
  const files = await fetchSkillFiles(skillId);
48
- if (files) {
49
- const result = await downloadBlobSkill(skillId, files);
55
+ if (files.status === "found") {
56
+ const result = await downloadBlobSkill(skillId, files.files);
50
57
  process.stdout.write(`Downloaded ${result.fileCount} skill file${result.fileCount === 1 ? "" : "s"} to ${result.installPath}\n`);
51
- return await spawnPassthrough(npxPath, [
58
+ await runSkillsInstall(npxPath, [
52
59
  "--yes",
53
60
  "skills",
54
61
  "add",
55
62
  result.installPath,
56
63
  ]);
64
+ return;
65
+ }
66
+ if (files.status === "not_found") {
67
+ fail(`Skill "${skillId.id}" not found in the catalog. Run \`browse skills find ${skillId.domain}\` to discover available skills, or \`browse skills list\` to browse them.`, 1, { resultCode: "skill_not_found" });
57
68
  }
58
- return await spawnPassthrough(npxPath, [
69
+ // Suffix-shaped (generated) ids, or a temporarily unavailable catalog, may
70
+ // still resolve through the browse.sh GitHub repo.
71
+ await runSkillsInstall(npxPath, [
59
72
  "--yes",
60
73
  "skills",
61
74
  "add",
@@ -67,9 +80,9 @@ export async function installSkill(rawSkillId) {
67
80
  export async function installBundledCliSkill() {
68
81
  const npxPath = await findExecutable("npx");
69
82
  if (!npxPath) {
70
- fail("`npx` is not installed. Install Node.js from https://nodejs.org, then rerun `browse skills install`.");
83
+ fail("`npx` is not installed. Install Node.js from https://nodejs.org, then rerun `browse skills install`.", 1, { resultCode: "npx_missing" });
71
84
  }
72
- return await spawnPassthrough(npxPath, [
85
+ await runSkillsInstall(npxPath, [
73
86
  "--yes",
74
87
  "skills",
75
88
  "add",
@@ -80,10 +93,30 @@ export async function installBundledCliSkill() {
80
93
  "*",
81
94
  ]);
82
95
  }
96
+ // Runs `npx skills add ...` with live passthrough, but fails with a diagnostic
97
+ // message (including a tail of the child's output) when the child exits nonzero
98
+ // so the failure is recorded in telemetry instead of an opaque exit code.
99
+ async function runSkillsInstall(npxPath, args) {
100
+ const result = await spawnPassthrough(npxPath, args);
101
+ if (result.exitCode === 0) {
102
+ return;
103
+ }
104
+ const detail = result.output.trim();
105
+ const reason = detail
106
+ ? `: ${detail}`
107
+ : " (see the output above for details).";
108
+ fail(`Could not install skill${reason}`, result.exitCode || 1, {
109
+ resultCode: "skill_install_failed",
110
+ });
111
+ }
83
112
  export async function downloadBlobSkill(skillId, files) {
84
- const filesToDownload = files ?? (await fetchSkillFiles(skillId));
113
+ let filesToDownload = files;
85
114
  if (!filesToDownload) {
86
- fail(`Skill ${skillId.id} was not found as a generated skill.`);
115
+ const resolved = await fetchSkillFiles(skillId);
116
+ if (resolved.status !== "found") {
117
+ fail(`Skill ${skillId.id} was not found as a generated skill.`);
118
+ }
119
+ filesToDownload = resolved.files;
87
120
  }
88
121
  const installPath = localSkillPath(skillId);
89
122
  const parentDir = dirname(installPath);
@@ -121,19 +154,29 @@ function packageRoot() {
121
154
  async function fetchSkillFiles(skillId) {
122
155
  const apiResult = await fetchSkillFilesFromApi(skillId);
123
156
  if (apiResult.status === "found") {
124
- return apiResult.files;
157
+ return { status: "found", files: apiResult.files };
125
158
  }
126
159
  if (apiResult.status === "unavailable" &&
127
160
  isBlobSkillId(skillId) &&
128
161
  (await directBlobSkillExists(skillId))) {
129
- return [
130
- {
131
- path: "SKILL.md",
132
- url: skillBlobUrl(skillId, "SKILL.md"),
133
- },
134
- ];
162
+ return {
163
+ status: "found",
164
+ files: [
165
+ {
166
+ path: "SKILL.md",
167
+ url: skillBlobUrl(skillId, "SKILL.md"),
168
+ },
169
+ ],
170
+ };
171
+ }
172
+ // Suffix-shaped ids correspond to generated catalog skills that live in the
173
+ // browse.sh repo even when the file API can't serve them directly, so keep
174
+ // the GitHub clone fallback for those. A plain 404 for a non-generated id is
175
+ // a definitive miss and should fail cleanly instead of cloning the repo.
176
+ if (apiResult.status === "not_found" && !isBlobSkillId(skillId)) {
177
+ return { status: "not_found" };
135
178
  }
136
- return null;
179
+ return { status: "fallback" };
137
180
  }
138
181
  async function fetchSkillFilesFromApi(skillId) {
139
182
  const url = skillFilesApiUrl(skillId);
@@ -298,19 +341,45 @@ async function findExecutable(command) {
298
341
  }
299
342
  return null;
300
343
  }
344
+ // Like `stdio: "inherit"` for the human watching, but the child's stdout/stderr
345
+ // are also buffered (tail only) so a nonzero exit can surface a real reason to
346
+ // telemetry instead of a bare exit code.
301
347
  async function spawnPassthrough(command, args) {
302
- return await new Promise((resolvePromise, reject) => {
348
+ return await new Promise((resolvePromise) => {
303
349
  const child = spawn(command, args, {
304
- stdio: "inherit",
350
+ stdio: ["inherit", "pipe", "pipe"],
305
351
  shell: shouldUseWindowsShell(command),
306
352
  });
307
- child.on("error", reject);
308
- child.on("close", (exitCode, signal) => {
309
- if (signal) {
310
- resolvePromise(1);
311
- return;
353
+ let captured = "";
354
+ const capture = (chunk) => {
355
+ captured += chunk.toString();
356
+ if (captured.length > maxCapturedOutputBytes) {
357
+ captured = captured.slice(-maxCapturedOutputBytes);
312
358
  }
313
- resolvePromise(exitCode ?? 0);
359
+ };
360
+ child.stdout?.on("data", (chunk) => {
361
+ process.stdout.write(chunk);
362
+ capture(chunk);
363
+ });
364
+ child.stderr?.on("data", (chunk) => {
365
+ process.stderr.write(chunk);
366
+ capture(chunk);
367
+ });
368
+ // Resolve (not reject) on spawn errors so the failure is classified as
369
+ // `skill_install_failed` by runSkillsInstall instead of escaping as an
370
+ // unclassified runtime error.
371
+ child.on("error", (error) => {
372
+ const message = error instanceof Error ? error.message : String(error);
373
+ resolvePromise({
374
+ exitCode: 1,
375
+ output: captured ? `${captured}\n${message}` : message,
376
+ });
377
+ });
378
+ child.on("close", (exitCode, signal) => {
379
+ resolvePromise({
380
+ exitCode: signal ? 1 : (exitCode ?? 0),
381
+ output: captured,
382
+ });
314
383
  });
315
384
  });
316
385
  }
@@ -1,7 +1,11 @@
1
1
  import { fail } from "../errors.js";
2
2
  const defaultTemplatesApiUrl = "https://www.browserbase.com/api/templates";
3
3
  export async function listTemplates(options = {}) {
4
- const payload = await requestTemplatesJson(templatesApiUrl(undefined, options));
4
+ const payload = await requestTemplatesJson(
5
+ // The CLI always requests the full template catalog. The templates API defaults to
6
+ // returning only `playgroundRunnable` templates — a website-playground concept that
7
+ // does not apply to the CLI — so we always opt out via `scope=all`.
8
+ templatesApiUrl(undefined, { ...options, scope: "all" }));
5
9
  return parseTemplatesResponse(payload);
6
10
  }
7
11
  export async function getTemplate(slug) {
@@ -3459,7 +3459,7 @@
3459
3459
  "skill": {
3460
3460
  "description": "Skill id in the form <domain>/<task>.",
3461
3461
  "name": "skill",
3462
- "required": true
3462
+ "required": false
3463
3463
  }
3464
3464
  },
3465
3465
  "description": "Install a browser automation skill.",
@@ -5266,5 +5266,5 @@
5266
5266
  ]
5267
5267
  }
5268
5268
  },
5269
- "version": "0.8.3"
5269
+ "version": "0.8.4"
5270
5270
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browse",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "Unified Browserbase CLI for browser automation and cloud APIs.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -109,6 +109,8 @@ browse get title
109
109
  browse get text body
110
110
  browse get html "#main"
111
111
  browse get value "#email"
112
+ browse get markdown body # page/element content as markdown
113
+ browse eval "document.title" # run JavaScript in the active page
112
114
  browse screenshot # print base64 JSON
113
115
  browse screenshot --path page.png
114
116
  ```
@@ -134,6 +136,7 @@ browse mouse hover 240 320
134
136
  browse mouse drag 80 80 310 100
135
137
  browse mouse scroll 500 300 0 600
136
138
  browse viewport 1280 720
139
+ browse cursor # show a visible cursor overlay
137
140
  ```
138
141
 
139
142
  Tabs, network, and CDP:
@@ -144,6 +147,7 @@ browse tab new https://example.com
144
147
  browse tab switch <target-id>
145
148
  browse tab close <target-id> # refuses to close the last tab
146
149
  browse network on
150
+ browse network off
147
151
  browse network path
148
152
  browse network clear
149
153
  browse cdp 9222 --pretty