Skip to content

[STG-2422] feat(cli): named contexts (local name→ID map)#2284

Open
shrey150 wants to merge 8 commits into
mainfrom
shrey/cli-named-contexts
Open

[STG-2422] feat(cli): named contexts (local name→ID map)#2284
shrey150 wants to merge 8 commits into
mainfrom
shrey/cli-named-contexts

Conversation

@shrey150

@shrey150 shrey150 commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

What & why

Browserbase contexts are identified only by an opaque ID and the platform has no server-side list endpoint, so reusing a context today means remembering (or copy-pasting) a UUID. This is also the single most-used feature of the popular community Browserbase skill on ClawHub (jamesfincher/browserbase), which keeps a local map of named contexts.

This PR ports that ergonomic into the CLI as a thin, client-side name→ID map — no API change.

  • browse cloud contexts create --name github → creates the context and saves a local alias
  • browse cloud contexts list → shows your saved names (new command; the API has no list, so this reflects names saved on this device)
  • contexts get|update|delete and sessions create --context-id now accept a saved name or a raw ID (a resolver passes unknown refs through unchanged, so raw IDs still work everywhere)
  • contexts delete prunes the local alias for the deleted context

The map lives at (XDG_CONFIG_HOME||~/.config)/browserbase/contexts.json (honoring BROWSERBASE_CONFIG_DIR), next to the CLI's existing state via the shared resolveConfigDir() helper. The file is written 0600. It stores only the same IDs the API already returns, and a missing or corrupt file degrades to "no saved contexts" rather than erroring.

Linear: STG-2422

E2E Test Matrix

Run against live Browserbase with the local build (node bin/run.js), BROWSERBASE_CONFIG_DIR pointed at a throwaway dir. Signed URLs redacted.

Command / flow Observed output Confidence / sufficiency
contexts create --name e2e-smoke {"id":"45ed525f-…","uploadUrl":"<redacted>",…,"name":"e2e-smoke"} Proves a real context is created and the name is echoed back.
cat contexts.json {"version":1,"contexts":{"e2e-smoke":{"id":"45ed525f-…","createdAt":"2026-06-27T00:40:51Z"}}} Proves the local name→ID map is persisted with the API-returned ID.
contexts get e2e-smoke (by name) {"id":"45ed525f-…","projectId":"2d228d57-…",…} Proves name→ID resolution on get hits the real /v1/contexts/<id>.
contexts list --format table Name ID Created
prod-login d98a30da 2026-06-27 00:41Z
Proves the new list command renders the saved map.
sessions create --context-id prod-login --persist (by name) session 27f9087a-… returned with contextId == d98a30da-…MATCHES name→id: True Key flow: a real session attaches to the right context purely from the name.
contexts delete prod-login (by name) {"ok":true,"id":"d98a30da-…","removedAliases":["prod-login"]} then contexts listNo saved contexts. Proves API delete + local alias prune.
contexts delete <raw-uuid> {"ok":true,"id":"45ed525f-…"} (no removedAliases) Proves raw IDs still work and orphan cleanup; no alias pruned when none matched.
pnpm test:cli Test Files 21 passed (21) · Tests 310 passed (310) Full suite green, incl. new contexts-store unit tests + contexts-named CLI-level e2e (fake server) + updated surface test.
pnpm lint format + eslint + tsc --noEmit all clean Types, style, lint.

Notes

  • Bump: browse: patch (matches how the CLI bumps; browse is in the changeset ignore list but still gets release-impacting patches by convention).
  • No new dependencies. Pure client-side; composes with the existing config-dir convention.

🤖 Generated with Claude Code


Summary by cubic

Add named contexts to the CLI so you can reuse a Browserbase context by name instead of copying IDs. Implements Linear STG-2422 with a local name→ID map, typo hints, raw‑ID compatibility, and a new contexts add command; no API changes.

  • New Features

    • browse cloud contexts create --name <name> saves a local alias and returns the name; browse cloud contexts add <name> <id> names an existing context (trims the ID, rejects empty; use --force to overwrite).
    • browse cloud contexts list shows saved names on this device (--json returns { "contexts": [...] }).
    • browse cloud contexts get|update|delete and browse cloud sessions create --context-id accept a saved name or a raw ID. Unknown names close to a saved one fail early with a “did you mean?” hint; unknown refs otherwise pass through so non-UUID IDs still work. contexts delete prunes aliases for the deleted ID and includes removedAliases (best effort).
  • Notes

    • Aliases live at (XDG_CONFIG_HOME||~/.config)/browserbase/contexts.json (honors BROWSERBASE_CONFIG_DIR), written 0600 via atomic write; missing/corrupt files behave as empty. The map is prototype-safe, rejects UUID-shaped names, and sanitizes malformed entries on read; names must start alphanumeric, allow letters/digits/._-, max 64, and duplicates are blocked unless --force.

Written for commit 00fdd03. Summary will update on new commits.

Review in cubic

@changeset-bot

changeset-bot Bot commented Jun 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 00fdd03

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment thread packages/cli/src/commands/cloud/contexts/list.ts Outdated
Comment thread packages/cli/src/commands/cloud/contexts/list.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 issues found and verified against the latest diff

Confidence score: 3/5

  • In packages/cli/src/commands/cloud/contexts/delete.ts, remote deletion can succeed but local alias cleanup can still throw, so users may see a failure after the context is already gone and retry into confusing states — make local cleanup non-blocking (or clearly report partial success) before merging.
  • In packages/cli/src/lib/cloud/contexts-store.ts, alias data from contexts.json is trusted without shape validation, so malformed entries can break name resolution/cleanup instead of degrading gracefully — validate and discard invalid alias entries as "no saved contexts."
  • In packages/cli/src/commands/cloud/contexts/create.ts, allowing ID-shaped alias names can shadow real context IDs and break raw-ID workflows — block ID-like names (or add explicit disambiguation) before merge.
  • In packages/cli/src/lib/cloud/contexts-store.ts, store writes are last-writer-wins and existing contexts.json files may retain permissive permissions, which can cause lost updates and leave alias data more exposed than intended — add concurrency-safe write/merge handling and enforce chmod 0o600 on existing files, then tighten packages/cli/tests/contexts-store.test.ts to assert mode bits.

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread packages/cli/src/commands/cloud/contexts/delete.ts Outdated
Comment thread packages/cli/src/lib/cloud/contexts-store.ts
Comment thread packages/cli/src/lib/cloud/contexts-store.ts
Comment thread packages/cli/src/lib/cloud/contexts-store.ts Outdated
Comment thread packages/cli/src/commands/cloud/contexts/create.ts
Comment thread packages/cli/tests/contexts-store.test.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread packages/cli/src/lib/cloud/contexts-store.ts
@shubh24

shubh24 commented Jun 27, 2026

Copy link
Copy Markdown

Tested this end-to-end — ✅ good to approve

Built the cloud command subtree from this branch and ran the full lifecycle against live Browserbase (throwaway BROWSERBASE_CONFIG_DIR, both test contexts cleaned up after). Every flow in the PR's matrix reproduced exactly.

Verified hands-on (live API)

Flow Result
contexts create --name e2e-login ✅ creates context, echoes "name", saves alias
contexts.json on disk 0600 perms, correct {version, contexts} shape
duplicate --name ✅ blocked with actionable message
contexts get e2e-login (by name) ✅ resolves name→id, hits real /v1/contexts/<id>
contexts list --format table ✅ clean Name / ID / Created table; --json{contexts:[...]}
sessions create --context-id prod-x --persist (by name) ✅ session contextId == prod-x's id — the key flow
contexts delete prod-x (by name) removedAliases:["prod-x"]
contexts delete <raw-uuid> ✅ deletes and prunes the alias by id
empty list, invalid names (leading dot / space), corrupt file ✅ all degrade gracefully
contexts-store + contexts-named test files ✅ 10/10 pass

Name validation, the 0600 file mode, corrupt-file → "no saved contexts" fallback, raw-ID passthrough everywhere, and delete-prunes-by-id all behave as described. Nicely scoped, honest, client-side only.

Non-blocking CLI-design suggestions (for a follow-up, not merge blockers)

  1. No way to name an existing context. --name only works at create time. If you get an ID from a teammate or another device, there's no contexts add <name> <id> / contexts alias — you'd hand-edit contexts.json. Probably the most valuable fast-follow.
  2. Typo'd name → cryptic error. The resolver silently passes unknown refs through as raw IDs (documented tradeoff), so contexts get my-loginn returns 400 Invalid Context ID instead of "no saved context named 'my-loginn'." A "did you mean?" hint on get/update/delete/--context-id when a ref is neither a UUID nor in the store would be friendlier.
  3. Local map can drift from server. Deleting a context via the dashboard leaves a stale alias; list still shows it and get <name> then 404s. No prune/reconcile. Minor — list could also label output as "N contexts named on this device" so it isn't mistaken for "all my contexts."
  4. Dead field: ContextAlias.projectId is defined but never populated. Wire it up or drop it.

Recommend merging; items 1 & 2 are worth a follow-up.

— tested with Claude Code

@shrey150

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough end-to-end pass @shubh24 🙏 Folded in two of your four right away (d8740e3a2):

  • fix multi observe resolution, move dom settling to a method, eval passes #2 typo'd name → cryptic error — done. contexts get|update|delete and sessions create --context-id now resolve through a helper that fails with No saved context named "X". Did you mean: <closest>? (levenshtein over saved names) instead of sending a bogus id and getting 400 Invalid Context ID. A context ID (UUID) or a known name still resolves; only unknown names fail. Verified live: get my-loginnDid you mean: my-login?, no API call.
  • slimmed down observe with passing evals #4 dead projectId — dropped the field (it was never populated).

The other two I'd like to take as fast-follows rather than grow this PR:

  • Pk/evals #1 name an existing context (contexts add <name> <id>) — agree it's the most valuable next step. It adds a new command to the surface, which Shrey deliberately scoped out of the first cut, so I'd rather land it as its own small PR. Tracking it.
  • prettier w/ formatting #3 drift / prune / "named on this device" labeling — reasonable; queuing as a follow-up.

Also added tests for the new resolver (unit: detailed resolution + did-you-mean ranking; CLI e2e: typo'd-name failure makes no API call) and switched the contract-test fixtures from ctx_123 to a UUID so resolution stays hermetic. Full suite 318 green, lint clean.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 10 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/cli/src/commands/cloud/sessions/create.ts
Comment thread packages/cli/src/lib/cloud/contexts-store.ts
@shrey150

Copy link
Copy Markdown
Contributor Author

Update on suggestion #1 @shubh24 — decided to fold it in rather than defer. Added browse cloud contexts add <name> <id> in b944a95c1: names a context you already have (a teammate's id, another device, or one created outside the CLI), local-only like list, with --force to repoint an existing name. Validates the name (same rule as create --name) and rejects an empty id.

Verified live: create (bare) → add team-login <id>get team-login resolves to the real context → dup rejected without --forcedelete team-login prunes. Suite now 323 green.

That leaves only #3 (drift/prune/"named on this device" labeling) as a follow-up.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 5 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/cli/src/commands/cloud/contexts/add.ts Outdated
shrey150 and others added 8 commits June 29, 2026 16:51
Reuse a Browserbase context by a memorable name instead of its opaque ID.
Browserbase contexts are ID-only with no server-side list, so a small local
map at (XDG_CONFIG_HOME||~/.config)/browserbase/contexts.json gives them names.

- `contexts create --name <name>` saves a name→ID alias
- `contexts list` shows saved names (new command)
- `contexts get|update|delete` and `sessions create --context-id` accept a
  saved name or a raw ID (resolver passes unknown refs through unchanged)
- `contexts delete` prunes the local alias
- store degrades to empty on a missing/corrupt file; alias file is 0600

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Drop the parenthetical from the `contexts list` description.
- Wrap list JSON in `{ contexts }` to match `templates list` / `skills list`
  machine-readable shape (was a bare array); update e2e assertion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- delete: prune local alias best-effort so a local cleanup failure can't
  turn a successful remote delete into a false-negative (P2)
- store: atomic temp-file + rename write — readers never see a half-written
  file, and the final file is always 0600 even over a pre-existing permissive
  contexts.json (P2 x2)
- store: sanitize entries on read; drop malformed ones instead of trusting
  them as ContextAlias (P2)
- create/store: reject UUID-shaped names so an alias can't shadow a raw
  context id (real ids are UUIDs, not ctx_-prefixed) (P2)
- tests: assert 0600 perm bits + pre-existing-file hardening + malformed-entry
  drop + UUID-name rejection

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
sanitizeContexts now also runs the name through isValidContextName, so a
hand-edited contexts.json can't smuggle in an id-shaped or otherwise invalid
name that would shadow a raw context id. Same rule as `create --name`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses review suggestions from @shubh24:

- Typo'd/unknown context name now fails with an actionable "No saved context
  named X. Did you mean: <closest>?" message (levenshtein) instead of letting
  it hit the API as a bogus id and returning a cryptic "Invalid Context ID".
  Applies to contexts get/update/delete and sessions create --context-id. A
  context id (UUID) or known name still resolves; only unknown names fail.
- Drop the unused `ContextAlias.projectId` field (was defined, never populated).
- Contract-test context fixtures use a UUID instead of `ctx_123` so resolution
  hits the id-passthrough path and stays hermetic vs the local store.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses cubic re-review:

- P1: resolution no longer rejects non-UUID raw ids. A ref that's neither a
  known name nor a UUID now only fails when it's *close* to a saved name (typo
  -> "did you mean?"); otherwise it passes through to the API, preserving
  raw-id compatibility for any id shape.
- P2: the in-memory contexts map is now a null-prototype object, so a ref like
  "toString"/"constructor"/"__proto__" can't read an inherited member as if it
  were a saved alias.

Tests: prototype-key resolution returns null; unknown non-UUID ref with no
close match passes through (unit + CLI e2e).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes the gap from @shubh24's review #1: you can now name a context you
already have (an id a teammate shared, or one created on another device /
outside the CLI) instead of only at create time. Local-only, mirrors the
alias-save path; --force repoints an existing name. Validates the name (same
rule as create --name) and rejects an empty id.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cubic: the empty-check trimmed the id but the raw value was saved, so a
whitespace-padded id would be stored and later resolved as a bogus context id.
Persist the normalized (trimmed) id and echo it back.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shrey150 shrey150 force-pushed the shrey/cli-named-contexts branch from ed83759 to 00fdd03 Compare June 29, 2026 23:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants