[Claimed #2240] feat(cli): add browse clipboard commands#2241
[Claimed #2240] feat(cli): add browse clipboard commands#2241github-actions[bot] wants to merge 3 commits into
Conversation
Expose the SDK clipboard API through the browse driver and oclif so terminal and agent workflows can read, write, paste, copy, and clear the browser clipboard without raw CDP calls. Co-authored-by: Cursor <cursoragent@cursor.com>
🦋 Changeset detectedLatest commit: 2acce35 The changes in this PR will be included in the next version bump. This PR includes changesets to release 0 packagesWhen 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 |
|
|
||
| static override examples = [ | ||
| "browse clipboard clear", | ||
| "browse clipboard clear --session research", |
There was a problem hiding this comment.
Do we know whether or not the clipboard API is global to the machine, or if it's truly scoped by session (aka by tab/browser)? If it's global, then I don't think session flags work right? We need to derisk
There was a problem hiding this comment.
Derisked empirically with the local build (packages/cli, this branch). Answer: it depends on the target, and the session flags are correct for the targets that matter — but headed/--cdp targets are machine-global.
Implementation: context.clipboard runs navigator.clipboard.readText/writeText via Runtime.evaluate inside the target browser (plus Browser.grantPermissions); paste/copy/cut are synthesized keystrokes. So scope = whatever clipboard backend that Chromium instance has.
Measured (each row actually run):
| Target | Result |
|---|---|
| Browserbase remote, session 1 | write → read roundtrip ✓; local Mac clipboard untouched |
| Browserbase remote, session 2 | reads "" → isolated (separate VM per session) |
| Managed local headless, session A | write → read ✓; macOS pbpaste untouched |
| Managed local headless, session B | reads "" → isolated (headless Chromium uses an in-memory clipboard per instance) |
| Managed local headed, session 1 | write clobbered the real macOS clipboard (pbpaste showed it) |
| Managed local headed, session 2 | read the same value → machine-global, cross-session leak |
--cdp attach to a real browser behaves like headed by construction. Note clear is implemented as writeText(""), so on a headed/--cdp target it wipes the user's actual OS clipboard.
So: for the primary agent targets (--remote, managed headless local) the clipboard is effectively session-scoped and the flags work as expected. For headed local and --cdp it is global to the machine. Addressed in 2acce35: the help text claimed page-level scoping ("for the active page") — now describes session scoping accurately with an explicit per-target scope note on all six commands. Making headed local truly session-scoped would require core-level clipboard virtualization (intercepting navigator.clipboard per context) — happy to file a follow-up ticket if we want that, but it's a core change, not CLI.
There was a problem hiding this comment.
yeah I validated this explicitly: clipboard is shared across tabs in the same browse session, but isolated across different sessions (separate managed-local browser instances).
so for example
clip-a2: if you write A2, read => A2
clip-b2: read => empty
clip-b2: write B2, read => B2
clip-a2: read still => A2
So --session is the right scoping boundary here. If useful, I can add a brief note in docs/help text and clarify that “shared within session, isolated across sessions.”
There was a problem hiding this comment.
Matches my results — thanks for double-checking. The one exception to keep in mind is headed local / --cdp targets, where the browser uses the real OS clipboard (machine-global, leaks across sessions and apps). That caveat is now documented in the help text on all six commands as of 2acce35.
Derisked per review: context.clipboard drives navigator.clipboard in the target browser, so scope depends on the target — Browserbase remote and managed headless local sessions get an isolated per-browser clipboard, while headed local sessions and --cdp attachments share the machine's OS clipboard. Command descriptions previously claimed page-level scoping. Also adds unit tests for the zod validation paths (write without text, non-string text, unsupported paste shortcut, paste with no options) and registers a clipboard oclif topic description. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## Summary Fork-triggered `pull_request` workflow runs don't receive repo configuration variables or secrets, so `STAINLESS_ORG`/`STAINLESS_PROJECT` arrive empty and the Stainless action dies immediately with `Input required and not supplied: project` on every external-contributor PR. Seen on #2241 (claimed copy of #2240): the fork PR's failing `preview` check attached to the shared head SHA, leaving the claimed PR red through no fault of its own. This guards both jobs on `github.event.pull_request.head.repo.full_name == github.repository`: - `preview` — skips on fork PRs instead of failing. Claimed copies of external PRs run in-repo, so SDK preview coverage is preserved. - `merge` — same defect: a directly-merged fork PR would trigger a guaranteed-failing merge build (still no vars/secrets on the `closed` event). Skipping is strictly better than failing; the claim flow means merged external work is always in-repo anyway. The `full_name` comparison is used instead of `!head.repo.fork` so a deleted fork repo (`head.repo == null`) skips rather than runs-and-fails. Linear: [STG-2257](https://linear.app/browserbase/issue/STG-2257/fixci-skip-stainless-sdk-jobs-for-fork-prs) ## E2E Test Matrix | Command / flow | Observed output | Confidence / sufficiency | | --- | --- | --- | | Failing fork run: [actions/runs/27375420406](https://github.com/browserbase/stagehand/actions/runs/27375420406/job/80903332001) (triggered by fork PR #2240) | Env dump shows `STAINLESS_ORG:` and `STAINLESS_PROJECT:` empty; action exits with `Error: Input required and not supplied: project` | Proves the root cause is withheld vars on fork runs, not the spec or config — exactly the case the new guard skips | | `actionlint .github/workflows/stainless.yml` on this branch | Exit 0, no findings | Validates workflow schema and the new `if:` expression syntax | | `preview` check on **this PR** (in-repo branch) | See checks below — job runs (guard evaluates true for same-repo PRs) and builds SDK previews | Live proof the guard doesn't skip in-repo PRs; the fork-skip path can only be exercised by the next real fork PR | 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Skip Stainless CI jobs for fork-origin PRs to avoid failures from missing repo vars/secrets. Keeps external PRs green while coverage runs on in-repo “claimed” copies. Addresses Linear STG-2257. - **Bug Fixes** - Skip `preview` and `merge` unless `github.event.pull_request.head.repo.full_name == github.repository`; `merge` still requires closed+merged into `main`. - Use the `full_name` check to safely skip deleted forks (`head.repo` can be null). <sup>Written for commit 58c5891. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/browserbase/stagehand/pull/2243?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Mirrored from external contributor PR #2240 after approval by @shrey150.
Original author: @optimusbuilder
Original PR: #2240
Approved source head SHA:
c372551cfa7f177ce7c6a391f458aad094ecc06a@optimusbuilder, please continue any follow-up discussion on this mirrored PR. When the external PR gets new commits, this same internal PR will be marked stale until the latest external commit is approved and refreshed here.
Original description
##why
The SDK exposes context.clipboard, but the browse CLI had no way to use it — users had to fall back to raw CDP.
##what changed
-Six driver commands: clipboard.read, write, paste, copy, cut, clear
-Matching oclif commands: browse clipboard read|write|paste|copy|cut|clear
-Wired through DriverSessionManager.browserContext() → context.clipboard (no core changes)
paste supports --shortcut Control+V|Meta+V|ControlOrMeta+V
E2E Test Matrix
All rows ran against the local build of this branch (
packages/cli, commit2acce35c). Clipboard scoping derisk detailed in this thread.pnpm run build && pnpm exec vitest runbrowse open <url> --local --headless→clipboard write→clipboard read(session A){"ok": true}→{"text": "<written value>"}clipboard readin second headless session B{"text": ""}; macOSpbpasteuntouchedclipboard writein--headedsession →pbpaste; read in second headed session--cdptargets are machine-global (now stated in help text); not a regression, inherent tonavigator.clipboardbrowse open <url> --remote→clipboard write→clipboard read(Browserbase session 1){"ok": true}→{"text": "<written value>"}; local clipboard untouchedclipboard readin second--remotesession{"text": ""}evalfocus input →clipboard write→clipboard paste→evalinput value (headless local AND remote w/--shortcut Control+V)--shortcutflageval→clipboard copy→read; select →cut→read+ input value;clear→read""; clear:{"text": ""}Original test plan
pnpm exec vitest run tests/clipboard.test.ts tests/driver-commands.test.ts
Manual: write → read → paste on a focused input
Summary by cubic
Add clipboard support to the
browseCLI with commands to read, write, paste, copy, cut, and clear. Lets terminal and agent workflows use the SDK clipboard API without raw CDP.clipboard.read|write|paste|copy|cut|clear, routed viaDriverSessionManager.browserContext()tocontext.clipboard.oclifcommands:browse clipboardread|write|paste|copy|cut|clear;pasteaccepts--shortcut(Control+V,Meta+V,ControlOrMeta+V).vitest).Written for commit ebaaf0d. Summary will update on new commits.