Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cli-attach-function-secret.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"browse": minor
---

Add `browse functions secrets attach` to attach an existing project secret to a function by ID.
5 changes: 5 additions & 0 deletions .changeset/cli-create-secret.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"browse": minor
---

Add `browse cloud secrets create` with public-key lookup, local encryption, and secret input from stdin, a named environment variable, or a hidden prompt.
5 changes: 5 additions & 0 deletions .changeset/cli-detach-function-secret.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"browse": minor
---

Add `browse functions secrets detach` to remove a function-secret attachment without deleting the project secret.
5 changes: 5 additions & 0 deletions .changeset/cli-get-delete-secrets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"browse": minor
---

Add commands to retrieve project secret metadata and delete a project secret by ID.
5 changes: 5 additions & 0 deletions .changeset/cli-list-project-secrets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"browse": minor
---

Add `browse cloud secrets list` to list project secret metadata with pagination and date filters.
5 changes: 5 additions & 0 deletions .changeset/cli-update-secret.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"browse": minor
---

Add `browse cloud secrets update` to replace a secret value by ID with local encryption and stdin, environment variable, or hidden prompt input.
5 changes: 5 additions & 0 deletions .changeset/tidy-contexts-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"browse": patch
---

store Context names in Browserbase while retaining local name-to-ID lookup compatibility and preserving legacy aliases
8 changes: 7 additions & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ browse cloud sessions downloads get <session-id> # --output ./downloads.zip
browse cloud sessions uploads create <session-id> ./file.pdf

# Contexts
browse cloud contexts create
browse cloud contexts create --name github # name is stored in Browserbase
browse cloud contexts get <context-id>
browse cloud contexts update <context-id> # refresh the upload URL
browse cloud contexts delete <context-id>
Expand All @@ -242,6 +242,12 @@ browse cloud fetch <url> # markdown by default
browse cloud search <query>
```

Names cached by earlier Browse versions remain local aliases and continue to
resolve to their saved Context IDs. They do not need to match the Context's
Browserbase-managed name. `contexts create --name` never overwrites an existing
local alias; use `contexts add <name> <context-id> --force` only after explicitly
reconciling a legacy mapping.

`browse cloud fetch` returns markdown-formatted page content by default. Use `--format raw` for the original response body, or `--format json --schema <schema>` for structured extraction.

## Functions
Expand Down
11 changes: 10 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
"cloud": {
"description": "Manage Browserbase cloud resources and APIs."
},
"cloud:secrets": {
"description": "Create, list, retrieve, update, and delete project secrets."
},
"cloud:projects": {
"description": "Manage Browserbase projects."
},
Expand All @@ -54,6 +57,9 @@
"cloud:sessions:uploads": {
"description": "Upload files to Browserbase sessions."
},
"functions:secrets": {
"description": "Attach and detach project secrets from functions."
},
"functions": {
"description": "Develop, publish, and invoke Browserbase Functions."
},
Expand Down Expand Up @@ -102,8 +108,11 @@
"prepublishOnly": "pnpm build"
},
"dependencies": {
"@browserbasehq/sdk": "^2.14.0",
"@browserbasehq/sdk": "^2.17.0",
"@browserbasehq/stagehand": "workspace:*",
"@hpke/core": "^1.9.0",
"@hpke/dhkem-x25519": "^1.8.0",
"@inquirer/password": "^4.0.23",
"@oclif/core": "^4.11.0",
"@vercel/detect-agent": "^1.2.3",
"archiver": "^7.0.1",
Expand Down
17 changes: 11 additions & 6 deletions packages/cli/skills/browse/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,17 +249,22 @@ For remote sessions with context persistence:
browse cloud sessions create --context-id <context-id> --persist
```

Contexts persist cookies and local storage (logins) across sessions. Name a
context once with `--name` to save a local alias, then reuse the name anywhere a
context ID is accepted instead of memorizing the ID:
Contexts persist cookies and local storage (logins) across sessions. `--name`
stores the name on the Browserbase Context and caches its returned ID on this
device, so the name can also be reused anywhere the CLI accepts a context ID:

```bash
browse cloud contexts create --name github # saves github -> ctx_...
browse cloud contexts add github <context-id> # name a context you already have
browse cloud contexts create --name github # server-owned name + local ID cache
browse cloud contexts add github <context-id> # add a local alias for an existing ID
browse cloud sessions create --context-id github --persist
browse cloud contexts list # show saved names
browse cloud contexts list # show this device's cached names/aliases
```

Names saved by earlier CLI versions remain valid local aliases even when they
do not match the Browserbase-managed Context name. `contexts create --name`
will not overwrite one of those mappings. Reconcile deliberately with
`contexts add <name> <context-id> --force` when needed.

Use `--verified` when the task needs Browserbase Verified browser mode. To drive a Verified/proxied session directly, prefer `browse open <url> --remote --verified --proxies` over create-then-attach — it keeps the session identity so `browse status`/`browse doctor` can report it. Use `browse cloud sessions create` for session options the driver flags don't cover (region, keep-alive, contexts, full `--stdin` body).

Use `browse cloud fetch` when the user needs a simple HTTP fetch without browser interaction. It returns markdown-formatted page content by default; pass `--format raw` for the original response body or `--format json --schema <schema>` for structured extraction. Use `browse cloud search` when the user asks for web search results.
Expand Down
19 changes: 14 additions & 5 deletions packages/cli/src/commands/cloud/contexts/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { BrowseCommand } from "../../../base.js";

export default class ContextsCreate extends BrowseCommand {
static override description =
"Create a Browserbase context. Pass --name to save a local alias you can reuse instead of the context ID.";
"Create a Browserbase context. Pass --name to store a project-scoped name in Browserbase and cache its ID locally.";
static override examples = [
"browse cloud contexts create",
"browse cloud contexts create --name github",
Expand All @@ -30,7 +30,7 @@ export default class ContextsCreate extends BrowseCommand {
...apiCommonFlags,
name: Flags.string({
description:
"Save a local alias for the new context so you can reuse it by name.",
"Set the Context name in Browserbase and cache its ID for local name lookup.",
helpValue: "<name>",
}),
body: Flags.string({
Expand All @@ -50,17 +50,26 @@ export default class ContextsCreate extends BrowseCommand {
if (!isValidContextName(name)) {
fail(`Invalid context name "${name}". ${contextNameRequirement()}`);
}
if (await getContextAlias(name)) {
const existingAlias = await getContextAlias(name);
if (existingAlias) {
fail(
`A context named "${name}" already exists locally. Choose another name or remove it with "browse cloud contexts delete ${name}".`,
`A context named "${name}" already exists locally and maps to ${existingAlias.id}. ` +
"Existing local aliases are preserved because they may predate Browserbase-managed Context names. " +
"Choose another name, or reconcile the alias explicitly with " +
"`browse cloud contexts add <name> <context-id> --force`.",
);
}
}

await withBrowserbaseApi("contexts", async () => {
const client = createBrowserbaseClient(toApiOptions(flags));
const body = await resolveBody({ body: flags.body, stdin: flags.stdin });
const context = await client.contexts.create(body);
// Browserbase owns name uniqueness and canonical storage. The explicit
// flag takes precedence over a name supplied through --body/--stdin,
// matching the merge behavior of other cloud command flags.
const context = await client.contexts.create(
name === undefined ? body : { ...body, name },
);

if (name !== undefined && context.id) {
await saveContextAlias(name, {
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/commands/cloud/contexts/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {

export default class ContextsList extends BrowseCommand {
static override description =
"List Browserbase contexts you have saved locally with a name.";
"List Context name-to-ID mappings cached on this device.";
static override examples = [
"browse cloud contexts list",
"browse cloud contexts list --json",
Expand All @@ -37,7 +37,7 @@ export default class ContextsList extends BrowseCommand {

if (contexts.length === 0) {
console.log(
"No saved contexts. Create one with: browse cloud contexts create --name <name>",
"No cached contexts. Create one with: browse cloud contexts create --name <name>",
);
return;
}
Expand All @@ -54,7 +54,7 @@ function outputContextsTable(
contexts,
[
{
header: "Name",
header: "Local name",
maxWidth: 24,
value: (context) => context.name,
},
Expand Down
34 changes: 34 additions & 0 deletions packages/cli/src/commands/cloud/secrets/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Args } from "@oclif/core";
import { BrowseCommand } from "../../../base.js";
import { apiCommonFlags, toApiOptions } from "../../../lib/cloud/flags.js";
import { createSecret } from "../../../lib/secrets/api.js";
import { readSecretValue } from "../../../lib/secrets/input.js";
import { secretInputFlags } from "../../../lib/secrets/flags.js";
import { outputJson } from "../../../lib/output.js";

export default class SecretsCreate extends BrowseCommand {
static override description =
"Create a project secret. Encrypts the value locally with the project public key.";
static override examples = [
"browse cloud secrets create SERVICE_TOKEN",
"browse cloud secrets create SERVICE_TOKEN --env MY_SERVICE_TOKEN",
"browse cloud secrets create SERVICE_TOKEN --stdin < ./secret.txt",
];
static override args = {
key: Args.string({
description: "Name exposed in the function context.secrets object.",
required: true,
}),
};
static override flags = { ...apiCommonFlags, ...secretInputFlags };
async run(): Promise<void> {
const { args, flags } = await this.parse(SecretsCreate);
const options = toApiOptions(flags);
const value = await readSecretValue({ stdin: flags.stdin, env: flags.env });
try {
outputJson(await createSecret(options, args.key, value));
} finally {
value.fill(0);
}
}
}
25 changes: 25 additions & 0 deletions packages/cli/src/commands/cloud/secrets/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Args } from "@oclif/core";
import { BrowseCommand } from "../../../base.js";
import { apiCommonFlags, toApiOptions } from "../../../lib/cloud/flags.js";
import { deleteSecret } from "../../../lib/secrets/api.js";

export default class SecretsDelete extends BrowseCommand {
static override description = "Delete a project secret.";
static override examples = [
"browse cloud secrets delete <secretId>",
"browse cloud secrets delete d2c4f48f-38e9-4b82-a36a-2b373fd14a65",
];
static override args = {
secretId: Args.string({
description:
"Project secret ID (e.g. d2c4f48f-38e9-4b82-a36a-2b373fd14a65).",
required: true,
}),
};
static override flags = { ...apiCommonFlags };
async run(): Promise<void> {
const { args, flags } = await this.parse(SecretsDelete);
const options = toApiOptions(flags);
await deleteSecret(options, args.secretId);
}
}
27 changes: 27 additions & 0 deletions packages/cli/src/commands/cloud/secrets/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Args } from "@oclif/core";
import { BrowseCommand } from "../../../base.js";
import { apiCommonFlags, toApiOptions } from "../../../lib/cloud/flags.js";
import { getSecret } from "../../../lib/secrets/api.js";
import { outputJson } from "../../../lib/output.js";

export default class SecretsGet extends BrowseCommand {
static override description =
"Get project secret metadata. Does not return the secret value.";
static override examples = [
"browse cloud secrets get <secretId>",
"browse cloud secrets get d2c4f48f-38e9-4b82-a36a-2b373fd14a65",
];
static override args = {
secretId: Args.string({
description:
"Project secret ID (e.g. d2c4f48f-38e9-4b82-a36a-2b373fd14a65).",
required: true,
}),
};
static override flags = { ...apiCommonFlags };
async run(): Promise<void> {
const { args, flags } = await this.parse(SecretsGet);
const options = toApiOptions(flags);
outputJson(await getSecret(options, args.secretId));
}
}
25 changes: 25 additions & 0 deletions packages/cli/src/commands/cloud/secrets/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { BrowseCommand } from "../../../base.js";
import { apiCommonFlags, toApiOptions } from "../../../lib/cloud/flags.js";
import { listSecrets } from "../../../lib/secrets/api.js";
import {
listSecretsFlags,
toListSecretsOptions,
} from "../../../lib/secrets/flags.js";
import { outputJson } from "../../../lib/output.js";

export default class SecretsList extends BrowseCommand {
static override description =
"List project secret metadata with cursor pagination.";
static override examples = [
"browse cloud secrets list",
"browse cloud secrets list --start-at 2026-01-01T00:00:00Z",
"browse cloud secrets list --start-at 2026-01-01T00:00:00Z --end-at 2026-02-01T00:00:00Z",
"browse cloud secrets list --limit 10",
];
static override flags = { ...apiCommonFlags, ...listSecretsFlags };
async run(): Promise<void> {
const { flags } = await this.parse(SecretsList);
const options = toApiOptions(flags);
outputJson(await listSecrets(options, toListSecretsOptions(flags)));
}
}
36 changes: 36 additions & 0 deletions packages/cli/src/commands/cloud/secrets/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Args } from "@oclif/core";
import { BrowseCommand } from "../../../base.js";
import { apiCommonFlags, toApiOptions } from "../../../lib/cloud/flags.js";
import { updateSecret } from "../../../lib/secrets/api.js";
import { readSecretValue } from "../../../lib/secrets/input.js";
import { secretInputFlags } from "../../../lib/secrets/flags.js";
import { outputJson } from "../../../lib/output.js";

export default class SecretsUpdate extends BrowseCommand {
static override description =
"Replace a secret value, encrypting it locally with the current project public key.";
static override examples = [
"browse cloud secrets update <secretId>",
"browse cloud secrets update d2c4f48f-38e9-4b82-a36a-2b373fd14a65",
"browse cloud secrets update <secretId> --env MY_SERVICE_TOKEN",
"browse cloud secrets update <secretId> --stdin < ./secret.txt",
];
static override args = {
secretId: Args.string({
description:
"Project secret ID (e.g. d2c4f48f-38e9-4b82-a36a-2b373fd14a65).",
required: true,
}),
};
static override flags = { ...apiCommonFlags, ...secretInputFlags };
async run(): Promise<void> {
const { args, flags } = await this.parse(SecretsUpdate);
const options = toApiOptions(flags);
const value = await readSecretValue({ stdin: flags.stdin, env: flags.env });
try {
outputJson(await updateSecret(options, args.secretId, value));
} finally {
value.fill(0);
}
}
}
30 changes: 30 additions & 0 deletions packages/cli/src/commands/functions/secrets/attach.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Args } from "@oclif/core";
import { BrowseCommand } from "../../../base.js";
import { apiCommonFlags, toApiOptions } from "../../../lib/cloud/flags.js";
import { attachFunctionSecret } from "../../../lib/secrets/api.js";

export default class FunctionSecretsAttach extends BrowseCommand {
static override description =
"Attach an existing project secret to a function.";
static override examples = [
"browse functions secrets attach <functionId> <secretId>",
"browse functions secrets attach 7b6e1c42-8d93-4a15-b2f0-9c6d3e8a5041 d2c4f48f-38e9-4b82-a36a-2b373fd14a65",
];
static override args = {
functionId: Args.string({
description: "Function ID (e.g. 7b6e1c42-8d93-4a15-b2f0-9c6d3e8a5041).",
required: true,
}),
secretId: Args.string({
description:
"Project secret ID (e.g. d2c4f48f-38e9-4b82-a36a-2b373fd14a65).",
required: true,
}),
};
static override flags = { ...apiCommonFlags };
async run(): Promise<void> {
const { args, flags } = await this.parse(FunctionSecretsAttach);
const options = toApiOptions(flags);
await attachFunctionSecret(options, args.functionId, args.secretId);
}
}
Loading
Loading