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 +178 -30
- package/dist/commands/skills/add.js +7 -4
- package/dist/commands/skills/install.js +1 -4
- package/dist/lib/skills/install.js +97 -28
- package/dist/lib/templates/api.js +5 -1
- package/oclif.manifest.json +2 -2
- package/package.json +1 -1
- package/skills/browse/SKILL.md +4 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/browse)
|
|
8
8
|
[](https://www.npmjs.com/package/browse)
|
|
9
|
-
[](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
|
|
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
|
-
##
|
|
44
|
+
## How it works
|
|
45
45
|
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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>
|
|
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
|
|
105
|
-
browse cloud
|
|
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:
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
+
let filesToDownload = files;
|
|
85
114
|
if (!filesToDownload) {
|
|
86
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
353
|
+
let captured = "";
|
|
354
|
+
const capture = (chunk) => {
|
|
355
|
+
captured += chunk.toString();
|
|
356
|
+
if (captured.length > maxCapturedOutputBytes) {
|
|
357
|
+
captured = captured.slice(-maxCapturedOutputBytes);
|
|
312
358
|
}
|
|
313
|
-
|
|
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(
|
|
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) {
|
package/oclif.manifest.json
CHANGED
|
@@ -3459,7 +3459,7 @@
|
|
|
3459
3459
|
"skill": {
|
|
3460
3460
|
"description": "Skill id in the form <domain>/<task>.",
|
|
3461
3461
|
"name": "skill",
|
|
3462
|
-
"required":
|
|
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.
|
|
5269
|
+
"version": "0.8.4"
|
|
5270
5270
|
}
|
package/package.json
CHANGED
package/skills/browse/SKILL.md
CHANGED
|
@@ -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
|