Skip to content

Commit c372551

Browse files
feat(cli): add browse clipboard commands
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>
1 parent 840aac8 commit c372551

12 files changed

Lines changed: 300 additions & 0 deletions

File tree

.changeset/browse-clipboard-cli.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"browse": minor
3+
---
4+
5+
Add `browse clipboard` commands (read, write, paste, copy, cut, clear) so the browse CLI exposes the SDK clipboard API for agent and terminal workflows.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { BrowseCommand } from "../../base.js";
2+
import {
3+
driverCommandFlags,
4+
runDriverCommandFromFlags,
5+
} from "../../lib/driver/command-cli.js";
6+
7+
export default class ClipboardClear extends BrowseCommand {
8+
static override description =
9+
"Clear the browser clipboard for the active page.";
10+
11+
static override examples = [
12+
"browse clipboard clear",
13+
"browse clipboard clear --session research",
14+
];
15+
16+
static override flags = {
17+
...driverCommandFlags,
18+
};
19+
20+
async run(): Promise<void> {
21+
const { flags } = await this.parse(ClipboardClear);
22+
await runDriverCommandFromFlags("clipboard.clear", {}, flags);
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { BrowseCommand } from "../../base.js";
2+
import {
3+
driverCommandFlags,
4+
runDriverCommandFromFlags,
5+
} from "../../lib/driver/command-cli.js";
6+
7+
export default class ClipboardCopy extends BrowseCommand {
8+
static override description =
9+
"Copy the current selection to the browser clipboard on the active page.";
10+
11+
static override examples = [
12+
"browse clipboard copy",
13+
"browse clipboard copy --session research",
14+
];
15+
16+
static override flags = {
17+
...driverCommandFlags,
18+
};
19+
20+
async run(): Promise<void> {
21+
const { flags } = await this.parse(ClipboardCopy);
22+
await runDriverCommandFromFlags("clipboard.copy", {}, flags);
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { BrowseCommand } from "../../base.js";
2+
import {
3+
driverCommandFlags,
4+
runDriverCommandFromFlags,
5+
} from "../../lib/driver/command-cli.js";
6+
7+
export default class ClipboardCut extends BrowseCommand {
8+
static override description =
9+
"Cut the current selection to the browser clipboard on the active page.";
10+
11+
static override examples = [
12+
"browse clipboard cut",
13+
"browse clipboard cut --session research",
14+
];
15+
16+
static override flags = {
17+
...driverCommandFlags,
18+
};
19+
20+
async run(): Promise<void> {
21+
const { flags } = await this.parse(ClipboardCut);
22+
await runDriverCommandFromFlags("clipboard.cut", {}, flags);
23+
}
24+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Flags } from "@oclif/core";
2+
3+
import { BrowseCommand } from "../../base.js";
4+
import {
5+
driverCommandFlags,
6+
runDriverCommandFromFlags,
7+
} from "../../lib/driver/command-cli.js";
8+
9+
export default class ClipboardPaste extends BrowseCommand {
10+
static override description =
11+
"Paste clipboard text into the focused field on the active page.";
12+
13+
static override examples = [
14+
"browse clipboard paste",
15+
"browse clipboard paste --shortcut Control+V",
16+
];
17+
18+
static override flags = {
19+
...driverCommandFlags,
20+
shortcut: Flags.string({
21+
description: "Keyboard shortcut to trigger paste.",
22+
helpValue: "<shortcut>",
23+
options: ["ControlOrMeta+V", "Meta+V", "Control+V"],
24+
}),
25+
};
26+
27+
async run(): Promise<void> {
28+
const { flags } = await this.parse(ClipboardPaste);
29+
await runDriverCommandFromFlags(
30+
"clipboard.paste",
31+
{ shortcut: flags.shortcut },
32+
flags,
33+
);
34+
}
35+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { BrowseCommand } from "../../base.js";
2+
import {
3+
driverCommandFlags,
4+
runDriverCommandFromFlags,
5+
} from "../../lib/driver/command-cli.js";
6+
7+
export default class ClipboardRead extends BrowseCommand {
8+
static override description =
9+
"Read text from the browser clipboard for the active page.";
10+
11+
static override examples = [
12+
"browse clipboard read",
13+
"browse clipboard read --session research",
14+
];
15+
16+
static override flags = {
17+
...driverCommandFlags,
18+
};
19+
20+
async run(): Promise<void> {
21+
const { flags } = await this.parse(ClipboardRead);
22+
await runDriverCommandFromFlags("clipboard.read", {}, flags);
23+
}
24+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Args } from "@oclif/core";
2+
3+
import { BrowseCommand } from "../../base.js";
4+
import {
5+
driverCommandFlags,
6+
runDriverCommandFromFlags,
7+
} from "../../lib/driver/command-cli.js";
8+
9+
export default class ClipboardWrite extends BrowseCommand {
10+
static override description =
11+
"Write text to the browser clipboard for the active page.";
12+
13+
static override examples = [
14+
"browse clipboard write 'hello world'",
15+
"browse clipboard write 'seed text' --session research",
16+
];
17+
18+
static override args = {
19+
text: Args.string({
20+
description: "Text to write to the clipboard.",
21+
required: true,
22+
}),
23+
};
24+
25+
static override flags = {
26+
...driverCommandFlags,
27+
};
28+
29+
async run(): Promise<void> {
30+
const { args, flags } = await this.parse(ClipboardWrite);
31+
await runDriverCommandFromFlags(
32+
"clipboard.write",
33+
{ text: args.text },
34+
flags,
35+
);
36+
}
37+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { z } from "zod";
2+
3+
import type { DriverCommandHandlers } from "./types.js";
4+
5+
const writeParamsSchema = z.object({
6+
text: z.string(),
7+
});
8+
9+
const pasteParamsSchema = z.object({
10+
shortcut: z.enum(["ControlOrMeta+V", "Meta+V", "Control+V"]).optional(),
11+
});
12+
13+
export const clipboardHandlers: DriverCommandHandlers = {
14+
async "clipboard.read"(manager) {
15+
const context = await manager.browserContext();
16+
const text = await context.clipboard.readText();
17+
return { text };
18+
},
19+
20+
async "clipboard.write"(manager, params) {
21+
const { text } = writeParamsSchema.parse(params);
22+
const context = await manager.browserContext();
23+
await context.clipboard.writeText(text);
24+
return { ok: true };
25+
},
26+
27+
async "clipboard.clear"(manager) {
28+
const context = await manager.browserContext();
29+
await context.clipboard.clear();
30+
return { ok: true };
31+
},
32+
33+
async "clipboard.paste"(manager, params) {
34+
const { shortcut } = pasteParamsSchema.parse(params ?? {});
35+
const context = await manager.browserContext();
36+
await context.clipboard.paste(shortcut ? { shortcut } : undefined);
37+
return { ok: true };
38+
},
39+
40+
async "clipboard.copy"(manager) {
41+
const context = await manager.browserContext();
42+
await context.clipboard.copy();
43+
return { ok: true };
44+
},
45+
46+
async "clipboard.cut"(manager) {
47+
const context = await manager.browserContext();
48+
await context.clipboard.cut();
49+
return { ok: true };
50+
},
51+
};

packages/cli/src/lib/driver/commands/registry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { DriverSessionManager } from "../session-manager.js";
2+
import { clipboardHandlers } from "./clipboard.js";
23
import { elementsHandlers } from "./elements.js";
34
import { keyboardHandlers } from "./keyboard.js";
45
import { mouseHandlers } from "./mouse.js";
@@ -12,6 +13,7 @@ import type { DriverCommandHandlers, DriverCommandName } from "./types.js";
1213

1314
const handlers: DriverCommandHandlers = {
1415
...navigationHandlers,
16+
...clipboardHandlers,
1517
...elementsHandlers,
1618
...keyboardHandlers,
1719
...mouseHandlers,

packages/cli/src/lib/driver/commands/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import { z } from "zod";
44
export const DRIVER_COMMAND_NAMES = [
55
"back",
66
"click",
7+
"clipboard.clear",
8+
"clipboard.copy",
9+
"clipboard.cut",
10+
"clipboard.paste",
11+
"clipboard.read",
12+
"clipboard.write",
713
"cursor",
814
"eval",
915
"fill",

0 commit comments

Comments
 (0)