chrome-relay 0.2.3 → 0.2.4
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/README.md +8 -0
- package/dist/cli.js +261 -120
- package/dist/native-host.js +0 -0
- package/package.json +18 -10
package/README.md
CHANGED
|
@@ -12,6 +12,14 @@ chrome-relay doctor
|
|
|
12
12
|
|
|
13
13
|
Then load the Chrome Relay extension in Chrome.
|
|
14
14
|
|
|
15
|
+
The native host installer allowlists the published Chrome Web Store extension ID:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
cpdiapbifblhlcpnmlmfpgfjlacebokb
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`chrome-relay doctor` prints the supported extension IDs and warns if the native-host manifest is stale.
|
|
22
|
+
|
|
15
23
|
## Usage
|
|
16
24
|
|
|
17
25
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/
|
|
3
|
+
// src/program.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { writeFileSync } from "fs";
|
|
6
6
|
|
|
@@ -27,6 +27,20 @@ var DEFAULT_EXTENSION_IDS = [
|
|
|
27
27
|
|
|
28
28
|
// src/install/install.ts
|
|
29
29
|
var APP_DIR = path.join(os.homedir(), ".chrome-relay");
|
|
30
|
+
var KNOWN_EXTENSION_IDS = [
|
|
31
|
+
["Chrome Web Store", CHROME_WEB_STORE_EXTENSION_ID],
|
|
32
|
+
["legacy dev", LEGACY_DEV_EXTENSION_ID],
|
|
33
|
+
["local unpacked", LOCAL_UNPACKED_EXTENSION_ID]
|
|
34
|
+
];
|
|
35
|
+
function allowedOrigin(extensionId) {
|
|
36
|
+
return `chrome-extension://${extensionId}/`;
|
|
37
|
+
}
|
|
38
|
+
function getDefaultAllowedOrigins() {
|
|
39
|
+
return DEFAULT_EXTENSION_IDS.map(allowedOrigin);
|
|
40
|
+
}
|
|
41
|
+
function formatKnownExtensionIds() {
|
|
42
|
+
return KNOWN_EXTENSION_IDS.map(([label, id]) => `${label}: ${id}`).join(", ");
|
|
43
|
+
}
|
|
30
44
|
function getChromeManifestDir() {
|
|
31
45
|
if (process.platform === "darwin") {
|
|
32
46
|
return path.join(
|
|
@@ -61,7 +75,7 @@ async function writeManifest(wrapperPath) {
|
|
|
61
75
|
description: "Native host for Chrome Relay",
|
|
62
76
|
path: wrapperPath,
|
|
63
77
|
type: "stdio",
|
|
64
|
-
allowed_origins:
|
|
78
|
+
allowed_origins: getDefaultAllowedOrigins()
|
|
65
79
|
};
|
|
66
80
|
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
67
81
|
`, "utf8");
|
|
@@ -76,6 +90,7 @@ async function runInstall() {
|
|
|
76
90
|
console.log(`Wrapper: ${wrapperPath}`);
|
|
77
91
|
console.log(`Manifest: ${manifestPath}`);
|
|
78
92
|
console.log(`Local bridge port: ${DEFAULT_HTTP_PORT}`);
|
|
93
|
+
console.log(`Allowed extension IDs: ${formatKnownExtensionIds()}`);
|
|
79
94
|
}
|
|
80
95
|
async function runDoctor() {
|
|
81
96
|
try {
|
|
@@ -84,6 +99,10 @@ async function runDoctor() {
|
|
|
84
99
|
await stat(wrapperPath);
|
|
85
100
|
await stat(manifestPath);
|
|
86
101
|
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
102
|
+
const allowedOrigins = Array.isArray(manifest.allowed_origins) ? manifest.allowed_origins : [];
|
|
103
|
+
const missingOrigins = getDefaultAllowedOrigins().filter(
|
|
104
|
+
(origin) => !allowedOrigins.includes(origin)
|
|
105
|
+
);
|
|
87
106
|
let serverReachable = false;
|
|
88
107
|
try {
|
|
89
108
|
const response = await fetch(`http://127.0.0.1:${DEFAULT_HTTP_PORT}/ping`);
|
|
@@ -93,7 +112,12 @@ async function runDoctor() {
|
|
|
93
112
|
}
|
|
94
113
|
console.log(`Wrapper present: yes`);
|
|
95
114
|
console.log(`Manifest present: yes`);
|
|
115
|
+
console.log(`Allowed extension IDs: ${formatKnownExtensionIds()}`);
|
|
96
116
|
console.log(`Allowed origins: ${(manifest.allowed_origins ?? ["missing"]).join(", ")}`);
|
|
117
|
+
if (missingOrigins.length > 0) {
|
|
118
|
+
console.log(`Manifest missing origins: ${missingOrigins.join(", ")}`);
|
|
119
|
+
console.log(`Tip: run "chrome-relay install" to refresh the native host manifest.`);
|
|
120
|
+
}
|
|
97
121
|
console.log(`Local bridge reachable: ${serverReachable ? "yes" : "no"}`);
|
|
98
122
|
if (!serverReachable) {
|
|
99
123
|
console.log(`Tip: load the extension so it can launch the native host.`);
|
|
@@ -127,11 +151,12 @@ async function callTool(name, args) {
|
|
|
127
151
|
return payload.data;
|
|
128
152
|
}
|
|
129
153
|
|
|
130
|
-
// src/
|
|
131
|
-
|
|
132
|
-
program
|
|
133
|
-
"
|
|
134
|
-
|
|
154
|
+
// src/program.ts
|
|
155
|
+
function buildProgram() {
|
|
156
|
+
const program = new Command();
|
|
157
|
+
program.name("chrome-relay").description("Connect your local Chrome browser to coding agents through a local bridge.").version(CHROME_RELAY_VERSION).showHelpAfterError().addHelpText(
|
|
158
|
+
"after",
|
|
159
|
+
`
|
|
135
160
|
|
|
136
161
|
Common agent flow:
|
|
137
162
|
chrome-relay tabs
|
|
@@ -139,140 +164,256 @@ Common agent flow:
|
|
|
139
164
|
chrome-relay read --tab <tabId> -i
|
|
140
165
|
chrome-relay click --tab <tabId> "<selector>"
|
|
141
166
|
chrome-relay fill --tab <tabId> "<selector>" "value"
|
|
167
|
+
chrome-relay type --tab <tabId> -s "<selector>" "text into rich editor"
|
|
168
|
+
chrome-relay keys --tab <tabId> Enter
|
|
169
|
+
chrome-relay js --tab <tabId> "return document.title"
|
|
142
170
|
chrome-relay screenshot --tab <tabId> -o evidence.png
|
|
143
171
|
|
|
144
172
|
Notes:
|
|
145
173
|
navigate takes a URL. Use --tab to target an existing tab.
|
|
146
|
-
|
|
174
|
+
Tools attach via CDP and run on backgrounded tabs without stealing focus.
|
|
147
175
|
`
|
|
148
|
-
);
|
|
149
|
-
program.command("install").description("Install and register the local Chrome Relay host.").action(async () => {
|
|
150
|
-
|
|
151
|
-
});
|
|
152
|
-
program.command("doctor").description("Validate the local Chrome Relay installation.").action(async () => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
});
|
|
156
|
-
async function run(name, args) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
176
|
+
);
|
|
177
|
+
program.command("install").description("Install and register the local Chrome Relay host.").action(async () => {
|
|
178
|
+
await runInstall();
|
|
179
|
+
});
|
|
180
|
+
program.command("doctor").description("Validate the local Chrome Relay installation.").action(async () => {
|
|
181
|
+
const ok = await runDoctor();
|
|
182
|
+
process.exit(ok ? 0 : 1);
|
|
183
|
+
});
|
|
184
|
+
async function run(name, args) {
|
|
185
|
+
try {
|
|
186
|
+
const result = await callTool(name, args);
|
|
187
|
+
if (typeof result === "string") {
|
|
188
|
+
process.stdout.write(result + "\n");
|
|
189
|
+
} else {
|
|
190
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
process.stderr.write(
|
|
194
|
+
(error instanceof Error ? error.message : String(error)) + "\n"
|
|
195
|
+
);
|
|
196
|
+
process.exit(1);
|
|
163
197
|
}
|
|
164
|
-
} catch (error) {
|
|
165
|
-
process.stderr.write(
|
|
166
|
-
(error instanceof Error ? error.message : String(error)) + "\n"
|
|
167
|
-
);
|
|
168
|
-
process.exit(1);
|
|
169
198
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
`
|
|
199
|
+
function tabOpt(cmd) {
|
|
200
|
+
return cmd.option("-t, --tab <id>", "target tab ID", (v) => Number(v));
|
|
201
|
+
}
|
|
202
|
+
program.command("tabs").description("List open Chrome windows and tabs.").action(async () => {
|
|
203
|
+
await run("get_windows_and_tabs", {});
|
|
204
|
+
});
|
|
205
|
+
tabOpt(
|
|
206
|
+
program.command("navigate <url>").description("Navigate a tab to a URL. Use --tab <id> to target an existing tab.").option("--new", "open in a new tab").option("--inactive", "do not activate the tab").addHelpText(
|
|
207
|
+
"after",
|
|
208
|
+
`
|
|
181
209
|
|
|
182
210
|
Examples:
|
|
183
211
|
chrome-relay navigate "https://example.com"
|
|
184
212
|
chrome-relay navigate --tab 123456789 "https://example.com"
|
|
185
213
|
chrome-relay navigate "https://example.com" --new --inactive
|
|
186
214
|
`
|
|
187
|
-
|
|
188
|
-
).action(async (url, opts) => {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
215
|
+
)
|
|
216
|
+
).action(async (url, opts) => {
|
|
217
|
+
if (/^\d+$/.test(url)) {
|
|
218
|
+
process.stderr.write(
|
|
219
|
+
`navigate expects a URL, but "${url}" looks like a tab ID.
|
|
192
220
|
Use "chrome-relay switch ${url}" to activate that tab, or "chrome-relay navigate --tab ${url} https://example.com" to navigate it.
|
|
193
221
|
`
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
});
|
|
203
|
-
tabOpt(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
222
|
+
);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
const args = { url };
|
|
226
|
+
if (opts.tab !== void 0) args.tabId = opts.tab;
|
|
227
|
+
if (opts.new) args.newTab = true;
|
|
228
|
+
if (opts.inactive) args.active = false;
|
|
229
|
+
await run("chrome_navigate", args);
|
|
230
|
+
});
|
|
231
|
+
tabOpt(
|
|
232
|
+
program.command("screenshot").description("Capture a screenshot of any tab without activating it.").option("--full", "capture beyond the viewport (full page)").option("--bbox <rect>", "capture a region: 'x,y,width,height' (pixels)").option("--selector <css>", "capture the bounding box of a CSS selector").option("--padding <px>", "pixels of padding around --selector region", (v) => Number(v)).option("-o, --out <path>", "save image to path (base64 PNG decoded)").addHelpText(
|
|
233
|
+
"after",
|
|
234
|
+
`
|
|
207
235
|
|
|
208
236
|
Examples:
|
|
209
237
|
chrome-relay screenshot -o active-tab.png
|
|
210
238
|
chrome-relay screenshot --tab 123456789 -o evidence.png
|
|
239
|
+
chrome-relay screenshot --tab 123456789 --full -o full-page.png
|
|
240
|
+
chrome-relay screenshot --tab 123456789 --bbox 0,0,1280,80 -o header.png
|
|
241
|
+
chrome-relay screenshot --tab 123456789 --selector "header" -o header.png
|
|
242
|
+
chrome-relay screenshot --tab 123456789 --selector ".card:nth-child(3)" --padding 8 -o card.png
|
|
211
243
|
|
|
212
|
-
|
|
244
|
+
Region screenshots (--bbox / --selector) are ~10x cheaper in tokens than a
|
|
245
|
+
full-tab screenshot when an agent only needs to see one component.
|
|
213
246
|
`
|
|
214
|
-
|
|
215
|
-
).action(async (opts) => {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (opts.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
247
|
+
)
|
|
248
|
+
).action(async (opts) => {
|
|
249
|
+
const args = {};
|
|
250
|
+
if (opts.tab !== void 0) args.tabId = opts.tab;
|
|
251
|
+
if (opts.full) args.fullPage = true;
|
|
252
|
+
if (opts.bbox) args.bbox = opts.bbox;
|
|
253
|
+
if (opts.selector) args.selector = opts.selector;
|
|
254
|
+
if (typeof opts.padding === "number") args.padding = opts.padding;
|
|
255
|
+
try {
|
|
256
|
+
const result = await callTool("chrome_screenshot", args);
|
|
257
|
+
if (opts.out && result && typeof result === "object") {
|
|
258
|
+
const data = result.dataUrl ?? result.data;
|
|
259
|
+
if (typeof data === "string") {
|
|
260
|
+
const b64 = data.includes(",") ? data.split(",")[1] : data;
|
|
261
|
+
writeFileSync(opts.out, Buffer.from(b64, "base64"));
|
|
262
|
+
process.stdout.write(`Saved screenshot to ${opts.out}
|
|
227
263
|
`);
|
|
228
|
-
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
229
266
|
}
|
|
267
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
268
|
+
} catch (error) {
|
|
269
|
+
process.stderr.write(
|
|
270
|
+
(error instanceof Error ? error.message : String(error)) + "\n"
|
|
271
|
+
);
|
|
272
|
+
process.exit(1);
|
|
230
273
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
});
|
|
247
|
-
tabOpt(
|
|
248
|
-
|
|
249
|
-
).action(async (selector, opts) => {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
});
|
|
254
|
-
tabOpt(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
program.command("
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
274
|
+
});
|
|
275
|
+
tabOpt(
|
|
276
|
+
program.command("read").description("Extract page structure and interactive elements.").option("-i, --interactive", "return only interactive elements")
|
|
277
|
+
).action(async (opts) => {
|
|
278
|
+
const args = {};
|
|
279
|
+
if (opts.tab !== void 0) args.tabId = opts.tab;
|
|
280
|
+
if (opts.interactive) args.interactiveOnly = true;
|
|
281
|
+
await run("chrome_read_page", args);
|
|
282
|
+
});
|
|
283
|
+
tabOpt(
|
|
284
|
+
program.command("click <selector>").description("Click an element by CSS selector.")
|
|
285
|
+
).action(async (selector, opts) => {
|
|
286
|
+
const args = { selector };
|
|
287
|
+
if (opts.tab !== void 0) args.tabId = opts.tab;
|
|
288
|
+
await run("chrome_click_element", args);
|
|
289
|
+
});
|
|
290
|
+
tabOpt(
|
|
291
|
+
program.command("fill <selector> <value>").description("Fill an input or textarea.")
|
|
292
|
+
).action(async (selector, value, opts) => {
|
|
293
|
+
const args = { selector, value };
|
|
294
|
+
if (opts.tab !== void 0) args.tabId = opts.tab;
|
|
295
|
+
await run("chrome_fill_or_select", args);
|
|
296
|
+
});
|
|
297
|
+
tabOpt(
|
|
298
|
+
program.command("keys <keys>").description("Press a single key or chord via trusted CDP input (e.g. Enter, Cmd+K).").addHelpText(
|
|
299
|
+
"after",
|
|
300
|
+
`
|
|
301
|
+
|
|
302
|
+
Examples:
|
|
303
|
+
chrome-relay keys Enter
|
|
304
|
+
chrome-relay keys Tab
|
|
305
|
+
chrome-relay keys Cmd+K
|
|
306
|
+
chrome-relay keys Shift+ArrowDown
|
|
307
|
+
|
|
308
|
+
For typing text into a field, use \`chrome-relay type\` instead.
|
|
309
|
+
`
|
|
310
|
+
)
|
|
311
|
+
).action(async (keys, opts) => {
|
|
312
|
+
const args = { keys };
|
|
313
|
+
if (opts.tab !== void 0) args.tabId = opts.tab;
|
|
314
|
+
await run("chrome_keyboard", args);
|
|
315
|
+
});
|
|
316
|
+
tabOpt(
|
|
317
|
+
program.command("type <text>").description("Insert text via trusted CDP input. Works in contenteditable / Draft.js / Lexical.").option("-s, --selector <selector>", "focus this element first").addHelpText(
|
|
318
|
+
"after",
|
|
319
|
+
`
|
|
320
|
+
|
|
321
|
+
Examples:
|
|
322
|
+
chrome-relay type --selector "[data-testid=tweetTextarea_0]" "hello world"
|
|
323
|
+
chrome-relay type "appended into already-focused element"
|
|
324
|
+
|
|
325
|
+
When to pick which:
|
|
326
|
+
fill \u2014 plain <input>, <textarea>, <select>, React-controlled inputs (atomic write).
|
|
327
|
+
type \u2014 contenteditable, Draft.js, Lexical, ProseMirror (trusted text commit).
|
|
328
|
+
keys \u2014 Enter, Tab, Esc, arrows, modifier chords (single key press).
|
|
329
|
+
js \u2014 anything else.
|
|
330
|
+
`
|
|
331
|
+
)
|
|
332
|
+
).action(async (text, opts) => {
|
|
333
|
+
const args = { text };
|
|
334
|
+
if (opts.tab !== void 0) args.tabId = opts.tab;
|
|
335
|
+
if (opts.selector) args.selector = opts.selector;
|
|
336
|
+
await run("chrome_type", args);
|
|
337
|
+
});
|
|
338
|
+
tabOpt(
|
|
339
|
+
program.command("js <code>").description("Evaluate JavaScript in the page MAIN world. Use `return` for the value.").option("--timeout-ms <ms>", "execution timeout in milliseconds (default 15000)", (v) => Number(v)).addHelpText(
|
|
340
|
+
"after",
|
|
341
|
+
`
|
|
342
|
+
|
|
343
|
+
Examples:
|
|
344
|
+
chrome-relay js "return document.title"
|
|
345
|
+
chrome-relay js "return await fetch('/api/me').then(r => r.json())"
|
|
346
|
+
chrome-relay js --tab 12345 "return document.querySelectorAll('article').length"
|
|
347
|
+
|
|
348
|
+
Notes:
|
|
349
|
+
Code is wrapped in an async IIFE. Top-level await works. Use \`return\` to send a value back.
|
|
350
|
+
Returned value is JSON-serialized. DOM nodes and functions become {}.
|
|
351
|
+
Runs in MAIN world: page globals, framework internals, and shadow roots are reachable.
|
|
352
|
+
`
|
|
353
|
+
)
|
|
354
|
+
).action(async (code, opts) => {
|
|
355
|
+
const args = { code };
|
|
356
|
+
if (opts.tab !== void 0) args.tabId = opts.tab;
|
|
357
|
+
if (typeof opts.timeoutMs === "number") args.timeoutMs = opts.timeoutMs;
|
|
358
|
+
await run("chrome_evaluate", args);
|
|
359
|
+
});
|
|
360
|
+
program.command("switch <tabId>").description("Activate a tab by ID.").action(async (tabId) => {
|
|
361
|
+
await run("chrome_switch_tab", { tabId: Number(tabId) });
|
|
362
|
+
});
|
|
363
|
+
program.command("close <tabIds...>").description("Close one or more tabs by ID.").action(async (tabIds) => {
|
|
364
|
+
await run("chrome_close_tabs", { tabIds: tabIds.map(Number) });
|
|
365
|
+
});
|
|
366
|
+
program.command("call <tool> [json]").description("Call any Chrome Relay tool with raw JSON args.").action(async (tool, json) => {
|
|
367
|
+
const args = json ? JSON.parse(json) : {};
|
|
368
|
+
await run(tool, args);
|
|
369
|
+
});
|
|
370
|
+
const viewport = program.command("viewport").description("Emulate device viewport, DPR, mobile flag, touch, and user agent.").addHelpText(
|
|
371
|
+
"after",
|
|
372
|
+
`
|
|
373
|
+
|
|
374
|
+
Examples:
|
|
375
|
+
chrome-relay viewport preset iphone-14 --tab 123
|
|
376
|
+
chrome-relay viewport preset desktop-1440 --tab 123
|
|
377
|
+
chrome-relay viewport set --tab 123 --width 414 --height 896 --mobile --dpr 3
|
|
378
|
+
chrome-relay viewport clear --tab 123
|
|
379
|
+
chrome-relay viewport list
|
|
380
|
+
|
|
381
|
+
Notes:
|
|
382
|
+
The override survives navigations within the tab but is wiped when the
|
|
383
|
+
debugger detaches (e.g. another extension takes over). Closing the tab
|
|
384
|
+
clears it. Re-run after detach if the page snaps back to its default size.
|
|
385
|
+
`
|
|
386
|
+
);
|
|
387
|
+
tabOpt(
|
|
388
|
+
viewport.command("set").description("Apply explicit viewport dimensions.").requiredOption("--width <px>", "viewport width in CSS pixels", (v) => Number(v)).requiredOption("--height <px>", "viewport height in CSS pixels", (v) => Number(v)).option("--dpr <ratio>", "device pixel ratio (1, 2, 3...)", (v) => Number(v)).option("--mobile", "set the mobile flag (affects meta viewport interpretation)").option("--touch", "enable touch event emulation").option("--user-agent <ua>", "override the User-Agent header")
|
|
389
|
+
).action(async (opts) => {
|
|
390
|
+
const args = { action: "set", width: opts.width, height: opts.height };
|
|
391
|
+
if (opts.tab !== void 0) args.tabId = opts.tab;
|
|
392
|
+
if (opts.dpr !== void 0) args.dpr = opts.dpr;
|
|
393
|
+
if (opts.mobile) args.mobile = true;
|
|
394
|
+
if (opts.touch) args.hasTouch = true;
|
|
395
|
+
if (opts.userAgent) args.userAgent = opts.userAgent;
|
|
396
|
+
await run("chrome_viewport", args);
|
|
397
|
+
});
|
|
398
|
+
tabOpt(
|
|
399
|
+
viewport.command("preset <name>").description("Apply a named device preset (iphone-14, pixel-7, desktop-1440, etc).")
|
|
400
|
+
).action(async (name, opts) => {
|
|
401
|
+
const args = { action: "preset", name };
|
|
402
|
+
if (opts.tab !== void 0) args.tabId = opts.tab;
|
|
403
|
+
await run("chrome_viewport", args);
|
|
404
|
+
});
|
|
405
|
+
tabOpt(
|
|
406
|
+
viewport.command("clear").description("Drop the viewport override and return the tab to its native size.")
|
|
407
|
+
).action(async (opts) => {
|
|
408
|
+
const args = { action: "clear" };
|
|
409
|
+
if (opts.tab !== void 0) args.tabId = opts.tab;
|
|
410
|
+
await run("chrome_viewport", args);
|
|
411
|
+
});
|
|
412
|
+
viewport.command("list").description("List available presets.").action(async () => {
|
|
413
|
+
await run("chrome_viewport", { action: "list" });
|
|
414
|
+
});
|
|
415
|
+
return program;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/cli.ts
|
|
419
|
+
buildProgram().parseAsync(process.argv);
|
package/dist/native-host.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-relay",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,13 +10,14 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist"
|
|
12
12
|
],
|
|
13
|
-
"scripts": {
|
|
14
|
-
"build": "tsup",
|
|
15
|
-
"lint": "tsc -p tsconfig.json --noEmit",
|
|
16
|
-
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
17
|
-
},
|
|
18
13
|
"description": "Connect your local Chrome browser to coding agents through a local bridge.",
|
|
19
|
-
"keywords": [
|
|
14
|
+
"keywords": [
|
|
15
|
+
"chrome",
|
|
16
|
+
"browser",
|
|
17
|
+
"automation",
|
|
18
|
+
"agents",
|
|
19
|
+
"native-messaging"
|
|
20
|
+
],
|
|
20
21
|
"license": "MIT",
|
|
21
22
|
"dependencies": {
|
|
22
23
|
"chalk": "^5.4.1",
|
|
@@ -24,7 +25,14 @@
|
|
|
24
25
|
"fastify": "^5.3.2"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
27
|
-
"
|
|
28
|
-
"
|
|
28
|
+
"tsup": "^8.4.0",
|
|
29
|
+
"vitest": "^3.0.0",
|
|
30
|
+
"@chrome-relay/protocol": "0.2.4"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup",
|
|
34
|
+
"lint": "tsc -p tsconfig.json --noEmit",
|
|
35
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
36
|
+
"test": "vitest run"
|
|
29
37
|
}
|
|
30
|
-
}
|
|
38
|
+
}
|