@browserbasehq/cli 0.3.1 → 0.4.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Browserbase, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,20 +1,181 @@
1
1
  # Browserbase CLI
2
2
 
3
- Browserbase's Bun-native development repo for the `bb` CLI.
3
+ The official CLI for [Browserbase](https://browserbase.com). Manage sessions, deploy serverless functions, browse the web, and interact with the full Browserbase platform from your terminal.
4
4
 
5
- ## Development
5
+ ## Installation
6
6
 
7
7
  ```bash
8
- bun install
9
- bun run check
10
- bun run cli -- --help
8
+ npm install -g @browserbasehq/cli
9
+ ```
10
+
11
+ Requires Node.js 18+.
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ # Set your API key
17
+ export BROWSERBASE_API_KEY=bb_live_...
18
+
19
+ # Fetch a webpage
20
+ bb fetch https://example.com
21
+
22
+ # Search the web
23
+ bb search "browserbase documentation"
24
+
25
+ # Browse interactively
26
+ bb browse https://example.com
27
+
28
+ # List your sessions
29
+ bb sessions list
11
30
  ```
12
31
 
13
- ## Build
32
+ ## Commands
33
+
34
+ ### `bb fetch <url>`
35
+
36
+ Retrieve webpage content without a full browser session.
14
37
 
15
38
  ```bash
16
- bun run build
39
+ bb fetch https://example.com
40
+ bb fetch https://example.com --output page.json
41
+ bb fetch https://example.com --proxies --allow-redirects
17
42
  ```
18
43
 
19
- The published artifact is Node-compatible JavaScript in `dist/`, even though local
20
- development stays Bun-native.
44
+ | Flag | Description |
45
+ |------|-------------|
46
+ | `--output <path>` | Write response to file |
47
+ | `--proxies` | Enable Browserbase proxy support |
48
+ | `--allow-redirects` | Follow HTTP redirects |
49
+ | `--allow-insecure-ssl` | Bypass TLS certificate verification |
50
+
51
+ ### `bb search <query>`
52
+
53
+ Search the web using the Browserbase Search API.
54
+
55
+ ```bash
56
+ bb search "web automation tools"
57
+ bb search "browserbase" --num-results 5
58
+ ```
59
+
60
+ | Flag | Description |
61
+ |------|-------------|
62
+ | `--num-results <n>` | Number of results (1-25, default 10) |
63
+ | `--output <path>` | Write results to file |
64
+
65
+ ### `bb browse [args...]`
66
+
67
+ Launch a browser session. Forwards to the `@browserbasehq/browse-cli` package (auto-installs on first use).
68
+
69
+ ```bash
70
+ bb browse https://example.com
71
+ bb browse --yes # skip install prompt
72
+ ```
73
+
74
+ ### `bb sessions`
75
+
76
+ Manage remote browser sessions.
77
+
78
+ ```bash
79
+ bb sessions list # List all sessions
80
+ bb sessions list --q "status=RUNNING" # Filter by metadata
81
+ bb sessions get <session-id> # Get session details
82
+ bb sessions create --body '{"projectId":"..."}'
83
+ bb sessions update <id> --status REQUEST_RELEASE
84
+ bb sessions debug <id> # Get debugger connection URLs
85
+ bb sessions logs <id> # View session logs
86
+ bb sessions recording <id> # Get rrweb recording events
87
+ bb sessions downloads get <id> # Download session artifacts (ZIP)
88
+ bb sessions uploads create <id> <file> # Upload a file to a session
89
+ ```
90
+
91
+ ### `bb functions`
92
+
93
+ Write and deploy serverless browser automation to the cloud.
94
+
95
+ ```bash
96
+ # Scaffold a new project
97
+ bb functions init my-function
98
+
99
+ # Run locally
100
+ bb functions dev handler.ts --port 3000
101
+
102
+ # Deploy to Browserbase
103
+ bb functions publish handler.ts
104
+ bb functions publish handler.ts --dry-run # preview without deploying
105
+
106
+ # Invoke a deployed function
107
+ bb functions invoke <function-id> --params '{"url":"https://example.com"}'
108
+ bb functions invoke --check-status <invocation-id>
109
+ ```
110
+
111
+ | Subcommand | Description |
112
+ |------------|-------------|
113
+ | `init [name]` | Scaffold a Functions project (`--package-manager npm\|pnpm`) |
114
+ | `dev <entry>` | Start local dev server (`--port`, `--host`, `--verbose`) |
115
+ | `publish <entry>` | Deploy function (`--dry-run`) |
116
+ | `invoke [id]` | Invoke a function (`--params`, `--no-wait`, `--check-status`) |
117
+
118
+ ### `bb projects`
119
+
120
+ Manage Browserbase projects.
121
+
122
+ ```bash
123
+ bb projects list
124
+ bb projects get <project-id>
125
+ bb projects usage <project-id>
126
+ ```
127
+
128
+ ### `bb contexts`
129
+
130
+ Persist browser state (cookies, localStorage) across sessions.
131
+
132
+ ```bash
133
+ bb contexts create --body '{"projectId":"..."}'
134
+ bb contexts get <context-id>
135
+ bb contexts update <context-id> # Refresh upload URL
136
+ bb contexts delete <context-id>
137
+ ```
138
+
139
+ ### `bb extensions`
140
+
141
+ Manage Chrome extensions for remote sessions.
142
+
143
+ ```bash
144
+ bb extensions upload extension.zip
145
+ bb extensions get <extension-id>
146
+ bb extensions delete <extension-id>
147
+ ```
148
+
149
+ ### `bb skills`
150
+
151
+ Install Browserbase agent skills for Claude Code.
152
+
153
+ ```bash
154
+ bb skills # Interactive install
155
+ bb skills install # Non-interactive install
156
+ bb skills --yes # Auto-accept prompts
157
+ ```
158
+
159
+ ## Configuration
160
+
161
+ | Environment Variable | Description |
162
+ |---------------------|-------------|
163
+ | `BROWSERBASE_API_KEY` | Your Browserbase API key (required) |
164
+ | `BROWSERBASE_PROJECT_ID` | Default project ID for Functions |
165
+ | `BROWSERBASE_BASE_URL` | Custom API base URL |
166
+
167
+ All environment variables can be overridden per-command with `--api-key`, `--project-id`, or `--base-url` flags.
168
+
169
+ ## Development
170
+
171
+ ```bash
172
+ bun install
173
+ bun run cli -- --help # Run locally
174
+ bun run check # Type-check
175
+ bun run build # Build to dist/
176
+ bun run test # Build + run tests
177
+ ```
178
+
179
+ ## License
180
+
181
+ This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.
@@ -19,6 +19,19 @@ async function installBrowseCli() {
19
19
  process.exitCode = exitCode;
20
20
  }
21
21
  }
22
+ function extractYesFlag(args) {
23
+ const remaining = [];
24
+ let yes = false;
25
+ for (const arg of args) {
26
+ if (arg === "--yes" || arg === "-y") {
27
+ yes = true;
28
+ }
29
+ else {
30
+ remaining.push(arg);
31
+ }
32
+ }
33
+ return { yes, remaining };
34
+ }
22
35
  export function attachBrowseCommand(program) {
23
36
  program
24
37
  .command("browse")
@@ -28,7 +41,15 @@ export function attachBrowseCommand(program) {
28
41
  .allowExcessArguments(true)
29
42
  .passThroughOptions()
30
43
  .helpOption(false)
44
+ .addHelpText("after", [
45
+ "",
46
+ "Examples:",
47
+ " bb browse --url https://example.com",
48
+ ' bb browse --url https://example.com --text "Click Login"',
49
+ " bb browse --yes (auto-install if not present)",
50
+ ].join("\n"))
31
51
  .action(async (args) => {
52
+ const { yes, remaining: forwardArgs } = extractYesFlag(args ?? []);
32
53
  let browsePath = await findExecutable("browse");
33
54
  if (!browsePath) {
34
55
  console.log([
@@ -39,18 +60,23 @@ export function attachBrowseCommand(program) {
39
60
  " npm install -g @browserbasehq/browse-cli",
40
61
  "",
41
62
  ].join("\n"));
42
- const rl = readline.createInterface({
43
- input: process.stdin,
44
- output: process.stdout,
45
- });
46
- try {
47
- const answer = await rl.question("Install now? [Y/n] ");
48
- if (answer.trim().toLowerCase() === "n") {
49
- return;
63
+ if (!yes) {
64
+ if (!process.stdin.isTTY) {
65
+ fail("Cannot prompt for install in non-interactive mode. Use --yes to auto-install.");
66
+ }
67
+ const rl = readline.createInterface({
68
+ input: process.stdin,
69
+ output: process.stdout,
70
+ });
71
+ try {
72
+ const answer = await rl.question("Install now? [Y/n] ");
73
+ if (answer.trim().toLowerCase() === "n") {
74
+ return;
75
+ }
76
+ }
77
+ finally {
78
+ rl.close();
50
79
  }
51
- }
52
- finally {
53
- rl.close();
54
80
  }
55
81
  await installBrowseCli();
56
82
  browsePath = await findExecutable("browse");
@@ -61,8 +87,8 @@ export function attachBrowseCommand(program) {
61
87
  ].join("\n"));
62
88
  }
63
89
  }
64
- const forwardArgs = args && args.length > 0 ? args : ["--help"];
65
- const exitCode = await spawnPassthrough(browsePath, forwardArgs);
90
+ const finalArgs = forwardArgs.length > 0 ? forwardArgs : ["--help"];
91
+ const exitCode = await spawnPassthrough(browsePath, finalArgs);
66
92
  if (exitCode !== 0) {
67
93
  process.exitCode = exitCode;
68
94
  }
@@ -1,4 +1,4 @@
1
- import { addCommonApiOptions, createBrowserbaseClient, outputJson, parseOptionalJsonObjectArg, requestBrowserbase, requestBrowserbaseJson, } from "../lib/command.js";
1
+ import { addCommonApiOptions, addExamples, createBrowserbaseClient, outputJson, resolveBody, requestBrowserbase, requestBrowserbaseJson, } from "../lib/command.js";
2
2
  export function attachContextsCommand(program) {
3
3
  const contexts = program
4
4
  .command("contexts")
@@ -7,30 +7,35 @@ export function attachContextsCommand(program) {
7
7
  contexts.action(() => {
8
8
  contexts.outputHelp();
9
9
  });
10
- addCommonApiOptions(contexts
10
+ addExamples(addCommonApiOptions(contexts
11
11
  .command("create")
12
12
  .description("Create a context.")
13
- .option("--body <body>", "Optional JSON request body.")).action(async (options) => {
13
+ .option("--body <body>", "Optional JSON request body.")
14
+ .option("--stdin", "Read JSON request body from stdin.")), [
15
+ "bb contexts create",
16
+ `bb contexts create --body '{"projectId":"proj_abc"}'`,
17
+ `echo '{"projectId":"proj_abc"}' | bb contexts create --stdin`,
18
+ ]).action(async (options) => {
14
19
  const client = createBrowserbaseClient(options);
15
- const body = parseOptionalJsonObjectArg(options.body, "body");
20
+ const body = await resolveBody(options);
16
21
  outputJson(await client.contexts.create(body));
17
22
  });
18
- addCommonApiOptions(contexts
23
+ addExamples(addCommonApiOptions(contexts
19
24
  .command("get <id>")
20
- .description("Get a context by ID.")).action(async (id, options) => {
25
+ .description("Get a context by ID.")), ["bb contexts get <context-id>"]).action(async (id, options) => {
21
26
  const client = createBrowserbaseClient(options);
22
27
  outputJson(await client.contexts.retrieve(id));
23
28
  });
24
- addCommonApiOptions(contexts
29
+ addExamples(addCommonApiOptions(contexts
25
30
  .command("update <id>")
26
- .description("Refresh the upload URL for a context.")).action(async (id, options) => {
31
+ .description("Refresh the upload URL for a context.")), ["bb contexts update <context-id>"]).action(async (id, options) => {
27
32
  outputJson(await requestBrowserbaseJson(options, `/v1/contexts/${id}`, {
28
33
  method: "PUT",
29
34
  }));
30
35
  });
31
- addCommonApiOptions(contexts
36
+ addExamples(addCommonApiOptions(contexts
32
37
  .command("delete <id>")
33
- .description("Delete a context.")).action(async (id, options) => {
38
+ .description("Delete a context.")), ["bb contexts delete <context-id>"]).action(async (id, options) => {
34
39
  await requestBrowserbase(options, `/v1/contexts/${id}`, {
35
40
  method: "DELETE",
36
41
  headers: {
@@ -1,4 +1,4 @@
1
- import { addCommonApiOptions, createBrowserbaseClient, outputJson, requestBrowserbase, resolveUploadableFile, } from "../lib/command.js";
1
+ import { addCommonApiOptions, addExamples, createBrowserbaseClient, outputJson, requestBrowserbase, resolveUploadableFile, } from "../lib/command.js";
2
2
  export function attachExtensionsCommand(program) {
3
3
  const extensions = program
4
4
  .command("extensions")
@@ -7,23 +7,23 @@ export function attachExtensionsCommand(program) {
7
7
  extensions.action(() => {
8
8
  extensions.outputHelp();
9
9
  });
10
- addCommonApiOptions(extensions
10
+ addExamples(addCommonApiOptions(extensions
11
11
  .command("upload <file>")
12
- .description("Upload an extension ZIP file.")).action(async (file, options) => {
12
+ .description("Upload an extension ZIP file.")), ["bb extensions upload ./extension.zip"]).action(async (file, options) => {
13
13
  const client = createBrowserbaseClient(options);
14
14
  outputJson(await client.extensions.create({
15
15
  file: await resolveUploadableFile(file, "extension"),
16
16
  }));
17
17
  });
18
- addCommonApiOptions(extensions
18
+ addExamples(addCommonApiOptions(extensions
19
19
  .command("get <id>")
20
- .description("Get an extension by ID.")).action(async (id, options) => {
20
+ .description("Get an extension by ID.")), ["bb extensions get <extension-id>"]).action(async (id, options) => {
21
21
  const client = createBrowserbaseClient(options);
22
22
  outputJson(await client.extensions.retrieve(id));
23
23
  });
24
- addCommonApiOptions(extensions
24
+ addExamples(addCommonApiOptions(extensions
25
25
  .command("delete <id>")
26
- .description("Delete an extension.")).action(async (id, options) => {
26
+ .description("Delete an extension.")), ["bb extensions delete <extension-id>"]).action(async (id, options) => {
27
27
  await requestBrowserbase(options, `/v1/extensions/${id}`, {
28
28
  method: "DELETE",
29
29
  headers: {
@@ -1,12 +1,16 @@
1
- import { addCommonApiOptions, createBrowserbaseClient, outputJson, writeOutputFile, } from "../lib/command.js";
1
+ import { addCommonApiOptions, addExamples, createBrowserbaseClient, outputJson, writeOutputFile, } from "../lib/command.js";
2
2
  export function attachFetchCommand(program) {
3
- addCommonApiOptions(program
3
+ addExamples(addCommonApiOptions(program
4
4
  .command("fetch <url>")
5
5
  .description("Retrieve webpage content without a full browser session using the lightweight Browserbase Fetch API.")
6
6
  .option("--allow-insecure-ssl", "Bypass TLS certificate verification.")
7
7
  .option("--allow-redirects", "Follow HTTP redirects.")
8
8
  .option("--proxies", "Enable Browserbase proxy support.")
9
- .option("--output <output>", "Write the fetched content to a file.")).action(async (url, options) => {
9
+ .option("--output <output>", "Write the fetched content to a file.")), [
10
+ "bb fetch https://www.google.com",
11
+ "bb fetch https://example.com --allow-insecure-ssl --output page.html",
12
+ "bb fetch https://example.com --allow-redirects --proxies",
13
+ ]).action(async (url, options) => {
10
14
  const client = createBrowserbaseClient(options);
11
15
  const result = await client.fetchAPI.create({
12
16
  url,
@@ -1,5 +1,5 @@
1
1
  import { Option } from "commander";
2
- import { addFunctionsApiOptions } from "../lib/command.js";
2
+ import { addExamples, addFunctionsApiOptions } from "../lib/command.js";
3
3
  import { initFunctionsProject } from "../lib/functions/init.js";
4
4
  import { invokeFunction } from "../lib/functions/invoke.js";
5
5
  import { startFunctionsDevServer } from "../lib/functions/dev.js";
@@ -13,23 +13,28 @@ export function attachFunctionsCommand(program) {
13
13
  functions.action(() => {
14
14
  functions.outputHelp();
15
15
  });
16
- functions
16
+ addExamples(functions
17
17
  .command("init [projectName]")
18
18
  .description("Initialize a new Browserbase Functions project.")
19
19
  .addOption(new Option("--package-manager <packageManager>", "Package manager to use.")
20
20
  .choices([...packageManagers])
21
- .default("pnpm"))
22
- .action(async (projectName, options) => {
21
+ .default("pnpm")), [
22
+ "bb functions init my-function",
23
+ "bb functions init my-function --package-manager npm",
24
+ ]).action(async (projectName, options) => {
23
25
  await initFunctionsProject({
24
26
  projectName: projectName ?? "my-browserbase-function",
25
27
  packageManager: options.packageManager ?? "pnpm",
26
28
  });
27
29
  });
28
- addFunctionsApiOptions(functions
30
+ addExamples(addFunctionsApiOptions(functions
29
31
  .command("dev <entrypoint>")
30
32
  .description("Run the local Browserbase Functions development server.")
31
33
  .option("--port <port>", "Port to listen on.", "14113")
32
- .option("--host <host>", "Host to bind to.", "127.0.0.1"), { includeProjectId: true, includeVerbose: true }).action(async (entrypoint, options) => {
34
+ .option("--host <host>", "Host to bind to.", "127.0.0.1"), { includeProjectId: true, includeVerbose: true }), [
35
+ "bb functions dev index.ts",
36
+ "bb functions dev index.ts --port 3000 --verbose",
37
+ ]).action(async (entrypoint, options) => {
33
38
  await startFunctionsDevServer({
34
39
  entrypoint,
35
40
  port: Number(options.port ?? "14113"),
@@ -40,10 +45,13 @@ export function attachFunctionsCommand(program) {
40
45
  verbose: options.verbose ?? false,
41
46
  });
42
47
  });
43
- addFunctionsApiOptions(functions
48
+ addExamples(addFunctionsApiOptions(functions
44
49
  .command("publish <entrypoint>")
45
50
  .description("Package and upload a Browserbase Function build.")
46
- .option("--dry-run", "Show what would be published without uploading."), { includeProjectId: true }).action(async (entrypoint, options) => {
51
+ .option("--dry-run", "Show what would be published without uploading."), { includeProjectId: true }), [
52
+ "bb functions publish index.ts --dry-run",
53
+ "bb functions publish index.ts",
54
+ ]).action(async (entrypoint, options) => {
47
55
  await publishFunction({
48
56
  entrypoint,
49
57
  apiKey: options.apiKey,
@@ -52,12 +60,16 @@ export function attachFunctionsCommand(program) {
52
60
  dryRun: options.dryRun ?? false,
53
61
  });
54
62
  });
55
- addFunctionsApiOptions(functions
63
+ addExamples(addFunctionsApiOptions(functions
56
64
  .command("invoke [functionId]")
57
65
  .description("Invoke a deployed Browserbase Function or check invocation status.")
58
66
  .option("--params <params>", "JSON params to pass to the function.")
59
67
  .option("--no-wait", "Return immediately after creating the invocation.")
60
- .option("--check-status <checkStatus>", "Invocation ID to inspect without creating a new invocation.")).action(async (functionId, options) => {
68
+ .option("--check-status <checkStatus>", "Invocation ID to inspect without creating a new invocation.")), [
69
+ `bb functions invoke <function-id> --params '{"url":"https://example.com"}'`,
70
+ "bb functions invoke <function-id> --no-wait",
71
+ "bb functions invoke --check-status <invocation-id>",
72
+ ]).action(async (functionId, options) => {
61
73
  await invokeFunction({
62
74
  functionId,
63
75
  params: options.params,
@@ -1,4 +1,4 @@
1
- import { addCommonApiOptions, createBrowserbaseClient, outputJson } from "../lib/command.js";
1
+ import { addCommonApiOptions, addExamples, createBrowserbaseClient, outputJson } from "../lib/command.js";
2
2
  export function attachProjectsCommand(program) {
3
3
  const projects = program
4
4
  .command("projects")
@@ -7,21 +7,21 @@ export function attachProjectsCommand(program) {
7
7
  projects.action(() => {
8
8
  projects.outputHelp();
9
9
  });
10
- addCommonApiOptions(projects
10
+ addExamples(addCommonApiOptions(projects
11
11
  .command("list")
12
- .description("List projects visible to the current API key.")).action(async (options) => {
12
+ .description("List projects visible to the current API key.")), ["bb projects list"]).action(async (options) => {
13
13
  const client = createBrowserbaseClient(options);
14
14
  outputJson(await client.projects.list());
15
15
  });
16
- addCommonApiOptions(projects
16
+ addExamples(addCommonApiOptions(projects
17
17
  .command("get <id>")
18
- .description("Get a project by ID.")).action(async (id, options) => {
18
+ .description("Get a project by ID.")), ["bb projects get <project-id>"]).action(async (id, options) => {
19
19
  const client = createBrowserbaseClient(options);
20
20
  outputJson(await client.projects.retrieve(id));
21
21
  });
22
- addCommonApiOptions(projects
22
+ addExamples(addCommonApiOptions(projects
23
23
  .command("usage <id>")
24
- .description("Get project usage.")).action(async (id, options) => {
24
+ .description("Get project usage.")), ["bb projects usage <project-id>"]).action(async (id, options) => {
25
25
  const client = createBrowserbaseClient(options);
26
26
  outputJson(await client.projects.usage(id));
27
27
  });
@@ -1,10 +1,14 @@
1
- import { addCommonApiOptions, outputJson, requestBrowserbaseJson, writeOutputFile, } from "../lib/command.js";
1
+ import { addCommonApiOptions, addExamples, outputJson, requestBrowserbaseJson, writeOutputFile, } from "../lib/command.js";
2
2
  export function attachSearchCommand(program) {
3
- addCommonApiOptions(program
3
+ addExamples(addCommonApiOptions(program
4
4
  .command("search <query>")
5
5
  .description("Search the web using the Browserbase Search API.")
6
6
  .option("--num-results <count>", "Number of results to return (1-25, default 10).")
7
- .option("--output <output>", "Write the search results as JSON to a file.")).action(async (query, options) => {
7
+ .option("--output <output>", "Write the search results as JSON to a file.")), [
8
+ `bb search "best restaurants in SF"`,
9
+ `bb search "web scraping tools" --num-results 5`,
10
+ `bb search "browserbase docs" --output results.json`,
11
+ ]).action(async (query, options) => {
8
12
  const numResults = options.numResults ? parseInt(options.numResults, 10) : undefined;
9
13
  const result = await requestBrowserbaseJson(options, "/v1/search", {
10
14
  method: "POST",
@@ -1,5 +1,5 @@
1
1
  import { Option } from "commander";
2
- import { addCommonApiOptions, createBrowserbaseClient, fail, outputJson, parseOptionalJsonObjectArg, resolveUploadableFile, writeBinaryOutput, } from "../lib/command.js";
2
+ import { addCommonApiOptions, addExamples, createBrowserbaseClient, fail, outputJson, resolveBody, resolveUploadableFile, writeBinaryOutput, } from "../lib/command.js";
3
3
  export function attachSessionsCommand(program) {
4
4
  const sessions = program
5
5
  .command("sessions")
@@ -8,57 +8,69 @@ export function attachSessionsCommand(program) {
8
8
  sessions.action(() => {
9
9
  sessions.outputHelp();
10
10
  });
11
- addCommonApiOptions(sessions
11
+ addExamples(addCommonApiOptions(sessions
12
12
  .command("list")
13
13
  .description("List sessions.")
14
- .option("--q <q>", `Session metadata query (e.g. "user_metadata['env']:'staging'").`)).action(async (options) => {
14
+ .option("--q <q>", `Session metadata query (e.g. "user_metadata['env']:'staging'").`)), [
15
+ "bb sessions list",
16
+ `bb sessions list --q "user_metadata['env']:'staging'"`,
17
+ ]).action(async (options) => {
15
18
  const client = createBrowserbaseClient(options);
16
19
  outputJson(await client.sessions.list(options.q ? { q: options.q } : {}));
17
20
  });
18
- addCommonApiOptions(sessions
21
+ addExamples(addCommonApiOptions(sessions
19
22
  .command("get <id>")
20
- .description("Get a session by ID.")).action(async (id, options) => {
23
+ .description("Get a session by ID.")), ["bb sessions get <session-id>"]).action(async (id, options) => {
21
24
  const client = createBrowserbaseClient(options);
22
25
  outputJson(await client.sessions.retrieve(id));
23
26
  });
24
- addCommonApiOptions(sessions
27
+ addExamples(addCommonApiOptions(sessions
25
28
  .command("create")
26
29
  .description("Create a session.")
27
- .option("--body <body>", "Optional JSON request body.")).action(async (options) => {
30
+ .option("--body <body>", "Optional JSON request body.")
31
+ .option("--stdin", "Read JSON request body from stdin.")), [
32
+ "bb sessions create",
33
+ `bb sessions create --body '{"proxies":true}'`,
34
+ `echo '{"proxies":true}' | bb sessions create --stdin`,
35
+ ]).action(async (options) => {
28
36
  const client = createBrowserbaseClient(options);
29
- const body = parseOptionalJsonObjectArg(options.body, "body");
37
+ const body = await resolveBody(options);
30
38
  outputJson(await client.sessions.create(body));
31
39
  });
32
- addCommonApiOptions(sessions
40
+ addExamples(addCommonApiOptions(sessions
33
41
  .command("update <id>")
34
42
  .description("Update a session.")
35
43
  .addOption(new Option("--status <status>", "Session status update.")
36
44
  .choices(["REQUEST_RELEASE"])
37
45
  .default("REQUEST_RELEASE"))
38
- .option("--body <body>", "Optional JSON request body. Merged with --status when provided.")).action(async (id, options) => {
46
+ .option("--body <body>", "Optional JSON request body. Merged with --status when provided.")
47
+ .option("--stdin", "Read JSON request body from stdin.")), [
48
+ "bb sessions update <session-id> --status REQUEST_RELEASE",
49
+ `bb sessions update <session-id> --body '{"status":"REQUEST_RELEASE"}'`,
50
+ ]).action(async (id, options) => {
39
51
  const client = createBrowserbaseClient(options);
40
52
  const sessionId = id || fail("Session ID is required.");
41
53
  const body = {
42
- ...parseOptionalJsonObjectArg(options.body, "body"),
54
+ ...(await resolveBody(options)),
43
55
  status: options.status ?? "REQUEST_RELEASE",
44
56
  };
45
57
  outputJson(await client.sessions.update(sessionId, body));
46
58
  });
47
- addCommonApiOptions(sessions
59
+ addExamples(addCommonApiOptions(sessions
48
60
  .command("debug <id>")
49
- .description("Get live debugger URLs for a session.")).action(async (id, options) => {
61
+ .description("Get live debugger URLs for a session.")), ["bb sessions debug <session-id>"]).action(async (id, options) => {
50
62
  const client = createBrowserbaseClient(options);
51
63
  outputJson(await client.sessions.debug(id));
52
64
  });
53
- addCommonApiOptions(sessions
65
+ addExamples(addCommonApiOptions(sessions
54
66
  .command("logs <id>")
55
- .description("Get session logs.")).action(async (id, options) => {
67
+ .description("Get session logs.")), ["bb sessions logs <session-id>"]).action(async (id, options) => {
56
68
  const client = createBrowserbaseClient(options);
57
69
  outputJson(await client.sessions.logs.list(id));
58
70
  });
59
- addCommonApiOptions(sessions
71
+ addExamples(addCommonApiOptions(sessions
60
72
  .command("recording <id>")
61
- .description("Get rrweb session recording events.")).action(async (id, options) => {
73
+ .description("Get rrweb session recording events.")), ["bb sessions recording <session-id>"]).action(async (id, options) => {
62
74
  const client = createBrowserbaseClient(options);
63
75
  outputJson(await client.sessions.recording.retrieve(id));
64
76
  });
@@ -69,10 +81,13 @@ export function attachSessionsCommand(program) {
69
81
  downloads.action(() => {
70
82
  downloads.outputHelp();
71
83
  });
72
- addCommonApiOptions(downloads
84
+ addExamples(addCommonApiOptions(downloads
73
85
  .command("get <id>")
74
86
  .description("Download session files as a ZIP archive.")
75
- .option("--output <output>", "Path to write the ZIP file.")).action(async (id, options) => {
87
+ .option("--output <output>", "Path to write the ZIP file.")), [
88
+ "bb sessions downloads get <session-id>",
89
+ "bb sessions downloads get <session-id> --output ./downloads.zip",
90
+ ]).action(async (id, options) => {
76
91
  const client = createBrowserbaseClient(options);
77
92
  const response = await client.sessions.downloads.list(id);
78
93
  const defaultPath = `${id}-downloads.zip`;
@@ -88,9 +103,9 @@ export function attachSessionsCommand(program) {
88
103
  uploads.action(() => {
89
104
  uploads.outputHelp();
90
105
  });
91
- addCommonApiOptions(uploads
106
+ addExamples(addCommonApiOptions(uploads
92
107
  .command("create <id> <file>")
93
- .description("Upload a file to a session.")).action(async (id, file, options) => {
108
+ .description("Upload a file to a session.")), ["bb sessions uploads create <session-id> ./file.pdf"]).action(async (id, file, options) => {
94
109
  const client = createBrowserbaseClient(options);
95
110
  outputJson(await client.sessions.uploads.create(id, {
96
111
  file: await resolveUploadableFile(file, "session upload"),
@@ -25,7 +25,15 @@ export function attachSkillsCommand(program) {
25
25
  const skills = program
26
26
  .command("skills")
27
27
  .description("Install Browserbase agent skills for Claude Code.")
28
- .action(async () => {
28
+ .option("-y, --yes", "Auto-accept installation prompts.")
29
+ .addHelpText("after", [
30
+ "",
31
+ "Examples:",
32
+ " bb skills (interactive install prompt)",
33
+ " bb skills --yes (auto-install without prompting)",
34
+ " bb skills install (non-interactive install)",
35
+ ].join("\n"))
36
+ .action(async (options) => {
29
37
  console.log([
30
38
  "",
31
39
  "Browserbase Skills for Claude Code",
@@ -38,18 +46,23 @@ export function attachSkillsCommand(program) {
38
46
  " Run: npx skills add browserbase/skills --yes --global",
39
47
  "",
40
48
  ].join("\n"));
41
- const rl = readline.createInterface({
42
- input: process.stdin,
43
- output: process.stdout,
44
- });
45
- try {
46
- const answer = await rl.question("Install skills? [Y/n] ");
47
- if (answer.trim().toLowerCase() === "n") {
48
- return;
49
+ if (!options.yes) {
50
+ if (!process.stdin.isTTY) {
51
+ fail("Cannot prompt for install in non-interactive mode. Use --yes to auto-install.");
52
+ }
53
+ const rl = readline.createInterface({
54
+ input: process.stdin,
55
+ output: process.stdout,
56
+ });
57
+ try {
58
+ const answer = await rl.question("Install skills? [Y/n] ");
59
+ if (answer.trim().toLowerCase() === "n") {
60
+ return;
61
+ }
62
+ }
63
+ finally {
64
+ rl.close();
49
65
  }
50
- }
51
- finally {
52
- rl.close();
53
66
  }
54
67
  await installSkills();
55
68
  });
@@ -2,6 +2,7 @@ import Browserbase from "@browserbasehq/sdk";
2
2
  import { createReadStream, constants } from "node:fs";
3
3
  import { access, mkdir, writeFile } from "node:fs/promises";
4
4
  import { dirname, resolve } from "node:path";
5
+ import { Readable } from "node:stream";
5
6
  const defaultBrowserbaseApiUrl = "https://api.browserbase.com";
6
7
  export class CommandFailure extends Error {
7
8
  exitCode;
@@ -151,3 +152,27 @@ export async function writeBinaryOutput(pathname, contents) {
151
152
  await mkdir(dirname(absolutePath), { recursive: true });
152
153
  await writeFile(absolutePath, contents);
153
154
  }
155
+ export function addExamples(command, examples) {
156
+ const text = "\nExamples:\n" + examples.map((e) => ` ${e}`).join("\n");
157
+ return command.addHelpText("after", text);
158
+ }
159
+ export async function readStdin() {
160
+ if (process.stdin.isTTY) {
161
+ fail("--stdin requires piped input. Example: echo '{\"key\":\"value\"}' | bb <command> --stdin");
162
+ }
163
+ const chunks = [];
164
+ for await (const chunk of Readable.toWeb(process.stdin)) {
165
+ chunks.push(Buffer.from(chunk));
166
+ }
167
+ return Buffer.concat(chunks).toString("utf8").trim();
168
+ }
169
+ export async function resolveBody(options) {
170
+ if (options.body && options.stdin) {
171
+ fail("Cannot use both --body and --stdin. Provide one or the other.");
172
+ }
173
+ if (options.stdin) {
174
+ const input = await readStdin();
175
+ return parseOptionalJsonObjectArg(input, "stdin");
176
+ }
177
+ return parseOptionalJsonObjectArg(options.body, "body");
178
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserbasehq/cli",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Browserbase CLI for platform APIs, functions, and browse passthrough.",
5
5
  "type": "module",
6
6
  "private": false,