@browserbasehq/cli 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -4,7 +4,6 @@ import { attachBrowseCommand } from "./commands/browse.js";
4
4
  const require = createRequire(import.meta.url);
5
5
  const { version } = require("../package.json");
6
6
  import { attachContextsCommand } from "./commands/contexts.js";
7
- import { attachDashboardCommand } from "./commands/dashboard.js";
8
7
  import { attachExtensionsCommand } from "./commands/extensions.js";
9
8
  import { attachFetchCommand } from "./commands/fetch.js";
10
9
  import { attachFunctionsCommand } from "./commands/functions.js";
@@ -12,6 +11,7 @@ import { attachSearchCommand } from "./commands/search.js";
12
11
  import { attachProjectsCommand } from "./commands/projects.js";
13
12
  import { attachSessionsCommand } from "./commands/sessions.js";
14
13
  import { attachSkillsCommand } from "./commands/skills.js";
14
+ import { attachBCommand } from "./commands/b.js";
15
15
  export function buildProgram() {
16
16
  const program = new Command();
17
17
  program
@@ -27,12 +27,12 @@ export function buildProgram() {
27
27
  attachFetchCommand(program);
28
28
  attachSearchCommand(program);
29
29
  attachSessionsCommand(program);
30
- attachDashboardCommand(program);
31
30
  attachFunctionsCommand(program);
32
31
  attachProjectsCommand(program);
33
32
  attachContextsCommand(program);
34
33
  attachExtensionsCommand(program);
35
34
  attachSkillsCommand(program);
35
+ attachBCommand(program);
36
36
  return program;
37
37
  }
38
38
  export async function run(argv) {
@@ -0,0 +1,19 @@
1
+ const B_LINES = [
2
+ "██████╗ ",
3
+ "██╔══██╗",
4
+ "██████╔╝",
5
+ "██╔══██╗",
6
+ "██████╔╝",
7
+ "╚═════╝ ",
8
+ ];
9
+ export function attachBCommand(program) {
10
+ program
11
+ .command("b [extra]", { hidden: true })
12
+ .description("🅱️")
13
+ .action((extra) => {
14
+ const count = 1 + (extra ? extra.length : 0);
15
+ for (const line of B_LINES) {
16
+ console.log(Array(count).fill(line).join(" "));
17
+ }
18
+ });
19
+ }
@@ -1,5 +1,24 @@
1
+ import * as readline from "node:readline/promises";
1
2
  import { fail } from "../lib/command.js";
2
3
  import { findExecutable, spawnPassthrough } from "../lib/process.js";
4
+ async function installBrowseCli() {
5
+ const npmPath = await findExecutable("npm");
6
+ if (!npmPath) {
7
+ fail([
8
+ "`npm` is not installed.",
9
+ "Install Node.js from https://nodejs.org to get npm,",
10
+ "then rerun `bb browse`.",
11
+ ].join("\n"));
12
+ }
13
+ const exitCode = await spawnPassthrough(npmPath, [
14
+ "install",
15
+ "-g",
16
+ "@browserbasehq/browse-cli",
17
+ ]);
18
+ if (exitCode !== 0) {
19
+ process.exitCode = exitCode;
20
+ }
21
+ }
3
22
  export function attachBrowseCommand(program) {
4
23
  program
5
24
  .command("browse")
@@ -8,18 +27,42 @@ export function attachBrowseCommand(program) {
8
27
  .allowUnknownOption(true)
9
28
  .allowExcessArguments(true)
10
29
  .passThroughOptions()
30
+ .helpOption(false)
11
31
  .action(async (args) => {
12
- const browsePath = await findExecutable("browse");
32
+ let browsePath = await findExecutable("browse");
13
33
  if (!browsePath) {
14
- fail([
34
+ console.log([
35
+ "",
15
36
  "`browse` is not installed.",
16
- "Install it with:",
17
- "npm install -g @browserbasehq/browse-cli",
37
+ "Automate web browser interactions using natural language via CLI commands.",
38
+ "",
39
+ " npm install -g @browserbasehq/browse-cli",
18
40
  "",
19
- "Then rerun your `bb browse ...` command.",
20
41
  ].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;
50
+ }
51
+ }
52
+ finally {
53
+ rl.close();
54
+ }
55
+ await installBrowseCli();
56
+ browsePath = await findExecutable("browse");
57
+ if (!browsePath) {
58
+ fail([
59
+ "Installation succeeded but `browse` was not found on PATH.",
60
+ "You may need to restart your shell, then rerun your command.",
61
+ ].join("\n"));
62
+ }
21
63
  }
22
- const exitCode = await spawnPassthrough(browsePath, args ?? []);
64
+ const forwardArgs = args && args.length > 0 ? args : ["--help"];
65
+ const exitCode = await spawnPassthrough(browsePath, forwardArgs);
23
66
  if (exitCode !== 0) {
24
67
  process.exitCode = exitCode;
25
68
  }
@@ -1,4 +1,4 @@
1
- import { addCommonApiOptions, createBrowserbaseClient, mergeProjectIdIntoBody, outputJson, parseOptionalJsonObjectArg, requestBrowserbase, requestBrowserbaseJson, resolveProjectId, } from "../lib/command.js";
1
+ import { addCommonApiOptions, createBrowserbaseClient, outputJson, parseOptionalJsonObjectArg, requestBrowserbase, requestBrowserbaseJson, } from "../lib/command.js";
2
2
  export function attachContextsCommand(program) {
3
3
  const contexts = program
4
4
  .command("contexts")
@@ -12,7 +12,7 @@ export function attachContextsCommand(program) {
12
12
  .description("Create a context.")
13
13
  .option("--body <body>", "Optional JSON request body.")).action(async (options) => {
14
14
  const client = createBrowserbaseClient(options);
15
- const body = mergeProjectIdIntoBody(resolveProjectId(options), parseOptionalJsonObjectArg(options.body, "body"));
15
+ const body = parseOptionalJsonObjectArg(options.body, "body");
16
16
  outputJson(await client.contexts.create(body));
17
17
  });
18
18
  addCommonApiOptions(contexts
@@ -16,20 +16,15 @@ export function attachFetchCommand(program) {
16
16
  });
17
17
  if (options.output) {
18
18
  await writeOutputFile(options.output, result.content);
19
- if (options.json) {
20
- outputJson({ ...result, outputPath: options.output });
21
- return;
22
- }
23
- console.log(`Saved fetched content to ${options.output}`);
19
+ outputJson({
20
+ ok: true,
21
+ outputPath: options.output,
22
+ contentType: result.contentType,
23
+ statusCode: result.statusCode,
24
+ sizeBytes: Buffer.byteLength(result.content, "utf8"),
25
+ });
24
26
  return;
25
27
  }
26
- if (options.json) {
27
- outputJson(result);
28
- return;
29
- }
30
- process.stdout.write(result.content);
31
- if (!result.content.endsWith("\n")) {
32
- process.stdout.write("\n");
33
- }
28
+ outputJson(result);
34
29
  });
35
30
  }
@@ -13,30 +13,15 @@ export function attachSearchCommand(program) {
13
13
  });
14
14
  if (options.output) {
15
15
  await writeOutputFile(options.output, JSON.stringify(result, null, 2));
16
- if (options.json) {
17
- outputJson({ ...result, outputPath: options.output });
18
- return;
19
- }
20
- console.log(`Saved search results to ${options.output}`);
16
+ outputJson({
17
+ ok: true,
18
+ outputPath: options.output,
19
+ requestId: result.requestId,
20
+ query: result.query,
21
+ resultCount: result.results.length,
22
+ });
21
23
  return;
22
24
  }
23
- if (options.json) {
24
- outputJson(result);
25
- return;
26
- }
27
- for (const [i, r] of result.results.entries()) {
28
- const num = i + 1;
29
- console.log(`${String(num)}. ${r.title}`);
30
- console.log(` ${r.url}`);
31
- const meta = [];
32
- if (r.author)
33
- meta.push(r.author);
34
- if (r.publishedDate)
35
- meta.push(r.publishedDate);
36
- if (meta.length > 0) {
37
- console.log(` ${meta.join(" · ")}`);
38
- }
39
- console.log();
40
- }
25
+ outputJson(result);
41
26
  });
42
27
  }
@@ -1,5 +1,5 @@
1
1
  import { Option } from "commander";
2
- import { addCommonApiOptions, createBrowserbaseClient, fail, mergeProjectIdIntoBody, outputJson, parseOptionalJsonObjectArg, resolveProjectId, resolveUploadableFile, writeBinaryOutput, } from "../lib/command.js";
2
+ import { addCommonApiOptions, createBrowserbaseClient, fail, outputJson, parseOptionalJsonObjectArg, resolveUploadableFile, writeBinaryOutput, } from "../lib/command.js";
3
3
  export function attachSessionsCommand(program) {
4
4
  const sessions = program
5
5
  .command("sessions")
@@ -11,7 +11,7 @@ export function attachSessionsCommand(program) {
11
11
  addCommonApiOptions(sessions
12
12
  .command("list")
13
13
  .description("List sessions.")
14
- .option("--q <q>", "Session metadata query.")).action(async (options) => {
14
+ .option("--q <q>", `Session metadata query (e.g. "user_metadata['env']:'staging'").`)).action(async (options) => {
15
15
  const client = createBrowserbaseClient(options);
16
16
  outputJson(await client.sessions.list(options.q ? { q: options.q } : {}));
17
17
  });
@@ -26,7 +26,7 @@ export function attachSessionsCommand(program) {
26
26
  .description("Create a session.")
27
27
  .option("--body <body>", "Optional JSON request body.")).action(async (options) => {
28
28
  const client = createBrowserbaseClient(options);
29
- const body = mergeProjectIdIntoBody(resolveProjectId(options), parseOptionalJsonObjectArg(options.body, "body"));
29
+ const body = parseOptionalJsonObjectArg(options.body, "body");
30
30
  outputJson(await client.sessions.create(body));
31
31
  });
32
32
  addCommonApiOptions(sessions
@@ -38,10 +38,10 @@ export function attachSessionsCommand(program) {
38
38
  .option("--body <body>", "Optional JSON request body. Merged with --status when provided.")).action(async (id, options) => {
39
39
  const client = createBrowserbaseClient(options);
40
40
  const sessionId = id || fail("Session ID is required.");
41
- const body = mergeProjectIdIntoBody(resolveProjectId(options), {
41
+ const body = {
42
42
  ...parseOptionalJsonObjectArg(options.body, "body"),
43
43
  status: options.status ?? "REQUEST_RELEASE",
44
- });
44
+ };
45
45
  outputJson(await client.sessions.update(sessionId, body));
46
46
  });
47
47
  addCommonApiOptions(sessions
@@ -17,10 +17,7 @@ export function fail(message, exitCode = 1) {
17
17
  export function addCommonApiOptions(command) {
18
18
  return command
19
19
  .option("--api-key <apiKey>", "Override the Browserbase API key.")
20
- .option("--project-id <projectId>", "Override the Browserbase project ID.")
21
- .option("--base-url <baseUrl>", "Override the Browserbase API base URL.")
22
- .option("--json", "Print JSON output.")
23
- .option("--verbose", "Enable verbose logging.");
20
+ .option("--base-url <baseUrl>", "Override the Browserbase API base URL.");
24
21
  }
25
22
  export function addFunctionsApiOptions(command, options = {}) {
26
23
  const { includeProjectId = false, includeVerbose = false } = options;
@@ -91,12 +88,6 @@ export function parseOptionalJsonValueArg(rawValue, label) {
91
88
  fail(`Invalid JSON for ${label}: ${error.message}`);
92
89
  }
93
90
  }
94
- export function mergeProjectIdIntoBody(projectId, body) {
95
- if (projectId && body.projectId === undefined) {
96
- return { ...body, projectId };
97
- }
98
- return body;
99
- }
100
91
  export function outputJson(value) {
101
92
  console.log(JSON.stringify(value, null, 2));
102
93
  }
@@ -64,7 +64,7 @@ export async function initFunctionsProject({ projectName, packageManager, }) {
64
64
  type: "module",
65
65
  scripts: {
66
66
  dev: "bb functions dev index.ts",
67
- publish: "bb functions publish index.ts",
67
+ deploy: "bb functions publish index.ts",
68
68
  },
69
69
  };
70
70
  await writeFile(join(projectRoot, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
@@ -90,6 +90,7 @@ export async function initFunctionsProject({ projectName, packageManager, }) {
90
90
  console.log(` cd ${projectName}`);
91
91
  console.log(" Edit .env with your Browserbase credentials");
92
92
  console.log(` ${packageManager === "pnpm" ? "pnpm" : "npm run"} dev`);
93
+ console.log(` ${packageManager === "pnpm" ? "pnpm run deploy" : "npm run deploy"}`);
93
94
  }
94
95
  function runPackageManager(packageManager, args, cwd) {
95
96
  const result = spawnSync(packageManager, args, {
@@ -1,7 +1,8 @@
1
1
  import archiver from "archiver";
2
2
  import ignore from "ignore";
3
- import { createWriteStream, existsSync, readFileSync } from "node:fs";
3
+ import { copyFileSync, createWriteStream, existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
4
4
  import { readFile, readdir, stat } from "node:fs/promises";
5
+ import { spawnSync } from "node:child_process";
5
6
  import { tmpdir } from "node:os";
6
7
  import { join, relative } from "node:path";
7
8
  import { randomUUID } from "node:crypto";
@@ -25,21 +26,24 @@ const defaultIgnorePatterns = [
25
26
  export async function publishFunction(options) {
26
27
  const entrypoint = await resolveEntrypoint(options.entrypoint);
27
28
  const config = resolveFunctionsProjectConfig(options);
28
- const archivePath = await createArchive(process.cwd(), options.dryRun);
29
- const metadata = {
30
- entrypoint: relative(process.cwd(), entrypoint),
31
- projectId: config.projectId,
32
- };
33
29
  if (options.dryRun) {
30
+ const { archivePath, entries } = await createArchive(process.cwd());
34
31
  console.log(JSON.stringify({
35
32
  dryRun: true,
36
33
  apiUrl: config.apiUrl,
37
34
  projectId: config.projectId,
38
- entrypoint: metadata.entrypoint,
35
+ entrypoint: relative(process.cwd(), entrypoint),
39
36
  archivePath,
37
+ files: entries,
40
38
  }, null, 2));
41
39
  return;
42
40
  }
41
+ ensureNpmLockfile(process.cwd());
42
+ const { archivePath, entries } = await createArchive(process.cwd());
43
+ const metadata = {
44
+ entrypoint: relative(process.cwd(), entrypoint),
45
+ projectId: config.projectId,
46
+ };
43
47
  const formData = new FormData();
44
48
  formData.append("metadata", JSON.stringify(metadata));
45
49
  formData.append("archive", new Blob([await readFile(archivePath)], {
@@ -69,13 +73,10 @@ export async function publishFunction(options) {
69
73
  process.exitCode = 1;
70
74
  }
71
75
  }
72
- async function createArchive(root, dryRun) {
76
+ async function createArchive(root) {
73
77
  const archivePath = join(tmpdir(), `browserbase-functions-${randomUUID()}.tar.gz`);
74
78
  const ignoreMatcher = await loadIgnoreMatcher(root);
75
79
  const entries = await listArchiveEntries(root, root, ignoreMatcher);
76
- if (dryRun) {
77
- console.log(JSON.stringify({ files: entries }, null, 2));
78
- }
79
80
  await new Promise((resolve, reject) => {
80
81
  const output = createWriteStream(archivePath);
81
82
  const archive = archiver("tar", {
@@ -97,7 +98,27 @@ async function createArchive(root, dryRun) {
97
98
  }
98
99
  archive.finalize().catch(reject);
99
100
  });
100
- return archivePath;
101
+ return { archivePath, entries };
102
+ }
103
+ function ensureNpmLockfile(root) {
104
+ if (existsSync(join(root, "package-lock.json"))) {
105
+ return;
106
+ }
107
+ // Generate in a temp directory to avoid conflicts with pnpm's node_modules
108
+ // structure, then copy the lockfile back.
109
+ const tmpDir = join(tmpdir(), `bb-lockgen-${randomUUID()}`);
110
+ mkdirSync(tmpDir, { recursive: true });
111
+ copyFileSync(join(root, "package.json"), join(tmpDir, "package.json"));
112
+ const result = spawnSync("npm", ["install", "--package-lock-only"], {
113
+ cwd: tmpDir,
114
+ stdio: "pipe",
115
+ });
116
+ if (result.status !== 0) {
117
+ rmSync(tmpDir, { recursive: true, force: true });
118
+ fail("Failed to generate package-lock.json. The build server requires an npm lockfile.");
119
+ }
120
+ copyFileSync(join(tmpDir, "package-lock.json"), join(root, "package-lock.json"));
121
+ rmSync(tmpDir, { recursive: true, force: true });
101
122
  }
102
123
  async function loadIgnoreMatcher(root) {
103
124
  const matcher = ignore();
@@ -1,7 +1,7 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { stat } from "node:fs/promises";
3
3
  import { extname, resolve } from "node:path";
4
- import { fail, outputJson, resolveApiKey, resolveProjectId } from "../command.js";
4
+ import { fail, resolveApiKey, resolveProjectId } from "../command.js";
5
5
  const defaultFunctionsApiUrl = "https://api.browserbase.com";
6
6
  export function resolveFunctionsApiConfig(args) {
7
7
  return {
@@ -103,14 +103,3 @@ export function ensureCommand(command) {
103
103
  fail(`${command} is required but was not found on PATH.`);
104
104
  }
105
105
  }
106
- export function printFunctionsJsonOrMessage(value, options = {}) {
107
- if (options.json) {
108
- outputJson(value);
109
- return;
110
- }
111
- if (options.message) {
112
- console.log(options.message);
113
- return;
114
- }
115
- outputJson(value);
116
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserbasehq/cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Browserbase CLI for platform APIs, functions, and browse passthrough.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -1,11 +0,0 @@
1
- import { openUrl } from "../lib/open.js";
2
- export function attachDashboardCommand(program) {
3
- program
4
- .command("dashboard")
5
- .description("Open the Browserbase web dashboard for your project.")
6
- .action(async () => {
7
- const url = "http://browserbase.com/overview";
8
- await openUrl(url);
9
- console.log(`Opened ${url}`);
10
- });
11
- }