chrome-relay 0.2.2 → 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 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,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/cli.ts
3
+ // src/program.ts
4
4
  import { Command } from "commander";
5
5
  import { writeFileSync } from "fs";
6
6
 
7
7
  // src/index.ts
8
- var CHROME_RELAY_VERSION = "0.2.2";
8
+ var CHROME_RELAY_VERSION = "0.2.3";
9
9
 
10
10
  // src/install/install.ts
11
11
  import os from "os";
@@ -16,10 +16,31 @@ import { fileURLToPath } from "url";
16
16
  // ../protocol/dist/index.js
17
17
  var NATIVE_HOST_NAME = "dev.chrome_relay.native_host";
18
18
  var DEFAULT_HTTP_PORT = 12122;
19
- var DEFAULT_EXTENSION_ID = "cdmmkpadhnpcfjljhgpdnnljhjafmhop";
19
+ var CHROME_WEB_STORE_EXTENSION_ID = "cpdiapbifblhlcpnmlmfpgfjlacebokb";
20
+ var LEGACY_DEV_EXTENSION_ID = "cdmmkpadhnpcfjljhgpdnnljhjafmhop";
21
+ var LOCAL_UNPACKED_EXTENSION_ID = "cleiodnaklknhhfopegimjelfibjmbkc";
22
+ var DEFAULT_EXTENSION_IDS = [
23
+ CHROME_WEB_STORE_EXTENSION_ID,
24
+ LEGACY_DEV_EXTENSION_ID,
25
+ LOCAL_UNPACKED_EXTENSION_ID
26
+ ];
20
27
 
21
28
  // src/install/install.ts
22
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
+ }
23
44
  function getChromeManifestDir() {
24
45
  if (process.platform === "darwin") {
25
46
  return path.join(
@@ -54,7 +75,7 @@ async function writeManifest(wrapperPath) {
54
75
  description: "Native host for Chrome Relay",
55
76
  path: wrapperPath,
56
77
  type: "stdio",
57
- allowed_origins: [`chrome-extension://${DEFAULT_EXTENSION_ID}/`]
78
+ allowed_origins: getDefaultAllowedOrigins()
58
79
  };
59
80
  await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}
60
81
  `, "utf8");
@@ -69,6 +90,7 @@ async function runInstall() {
69
90
  console.log(`Wrapper: ${wrapperPath}`);
70
91
  console.log(`Manifest: ${manifestPath}`);
71
92
  console.log(`Local bridge port: ${DEFAULT_HTTP_PORT}`);
93
+ console.log(`Allowed extension IDs: ${formatKnownExtensionIds()}`);
72
94
  }
73
95
  async function runDoctor() {
74
96
  try {
@@ -77,6 +99,10 @@ async function runDoctor() {
77
99
  await stat(wrapperPath);
78
100
  await stat(manifestPath);
79
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
+ );
80
106
  let serverReachable = false;
81
107
  try {
82
108
  const response = await fetch(`http://127.0.0.1:${DEFAULT_HTTP_PORT}/ping`);
@@ -86,7 +112,12 @@ async function runDoctor() {
86
112
  }
87
113
  console.log(`Wrapper present: yes`);
88
114
  console.log(`Manifest present: yes`);
89
- console.log(`Allowed origin: ${manifest.allowed_origins?.[0] ?? "missing"}`);
115
+ console.log(`Allowed extension IDs: ${formatKnownExtensionIds()}`);
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
+ }
90
121
  console.log(`Local bridge reachable: ${serverReachable ? "yes" : "no"}`);
91
122
  if (!serverReachable) {
92
123
  console.log(`Tip: load the extension so it can launch the native host.`);
@@ -120,11 +151,12 @@ async function callTool(name, args) {
120
151
  return payload.data;
121
152
  }
122
153
 
123
- // src/cli.ts
124
- var program = new Command();
125
- program.name("chrome-relay").description("Connect your local Chrome browser to coding agents through a local bridge.").version(CHROME_RELAY_VERSION).showHelpAfterError().addHelpText(
126
- "after",
127
- `
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
+ `
128
160
 
129
161
  Common agent flow:
130
162
  chrome-relay tabs
@@ -132,140 +164,256 @@ Common agent flow:
132
164
  chrome-relay read --tab <tabId> -i
133
165
  chrome-relay click --tab <tabId> "<selector>"
134
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"
135
170
  chrome-relay screenshot --tab <tabId> -o evidence.png
136
171
 
137
172
  Notes:
138
173
  navigate takes a URL. Use --tab to target an existing tab.
139
- screenshot --tab <tabId> auto-activates that tab first because Chrome only screenshots visible tabs.
174
+ Tools attach via CDP and run on backgrounded tabs without stealing focus.
140
175
  `
141
- );
142
- program.command("install").description("Install and register the local Chrome Relay host.").action(async () => {
143
- await runInstall();
144
- });
145
- program.command("doctor").description("Validate the local Chrome Relay installation.").action(async () => {
146
- const ok = await runDoctor();
147
- process.exit(ok ? 0 : 1);
148
- });
149
- async function run(name, args) {
150
- try {
151
- const result = await callTool(name, args);
152
- if (typeof result === "string") {
153
- process.stdout.write(result + "\n");
154
- } else {
155
- process.stdout.write(JSON.stringify(result, null, 2) + "\n");
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);
156
197
  }
157
- } catch (error) {
158
- process.stderr.write(
159
- (error instanceof Error ? error.message : String(error)) + "\n"
160
- );
161
- process.exit(1);
162
198
  }
163
- }
164
- function tabOpt(cmd) {
165
- return cmd.option("-t, --tab <id>", "target tab ID", (v) => Number(v));
166
- }
167
- program.command("tabs").description("List open Chrome windows and tabs.").action(async () => {
168
- await run("get_windows_and_tabs", {});
169
- });
170
- tabOpt(
171
- 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(
172
- "after",
173
- `
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
+ `
174
209
 
175
210
  Examples:
176
211
  chrome-relay navigate "https://example.com"
177
212
  chrome-relay navigate --tab 123456789 "https://example.com"
178
213
  chrome-relay navigate "https://example.com" --new --inactive
179
214
  `
180
- )
181
- ).action(async (url, opts) => {
182
- if (/^\d+$/.test(url)) {
183
- process.stderr.write(
184
- `navigate expects a URL, but "${url}" looks like a tab ID.
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.
185
220
  Use "chrome-relay switch ${url}" to activate that tab, or "chrome-relay navigate --tab ${url} https://example.com" to navigate it.
186
221
  `
187
- );
188
- process.exit(1);
189
- }
190
- const args = { url };
191
- if (opts.tab !== void 0) args.tabId = opts.tab;
192
- if (opts.new) args.newTab = true;
193
- if (opts.inactive) args.active = false;
194
- await run("chrome_navigate", args);
195
- });
196
- tabOpt(
197
- program.command("screenshot").description("Capture a screenshot. With --tab, the tab is auto-activated first.").option("--full", "capture full page").option("-o, --out <path>", "save image to path (base64 PNG decoded)").addHelpText(
198
- "after",
199
- `
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
+ `
200
235
 
201
236
  Examples:
202
237
  chrome-relay screenshot -o active-tab.png
203
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
204
243
 
205
- Chrome can only capture visible tabs, so --tab will focus/activate the tab before capturing.
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.
206
246
  `
207
- )
208
- ).action(async (opts) => {
209
- const args = {};
210
- if (opts.tab !== void 0) args.tabId = opts.tab;
211
- if (opts.full) args.fullPage = true;
212
- try {
213
- const result = await callTool("chrome_screenshot", args);
214
- if (opts.out && result && typeof result === "object") {
215
- const data = result.dataUrl ?? result.data;
216
- if (typeof data === "string") {
217
- const b64 = data.includes(",") ? data.split(",")[1] : data;
218
- writeFileSync(opts.out, Buffer.from(b64, "base64"));
219
- process.stdout.write(`Saved screenshot to ${opts.out}
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}
220
263
  `);
221
- return;
264
+ return;
265
+ }
222
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);
223
273
  }
224
- process.stdout.write(JSON.stringify(result, null, 2) + "\n");
225
- } catch (error) {
226
- process.stderr.write(
227
- (error instanceof Error ? error.message : String(error)) + "\n"
228
- );
229
- process.exit(1);
230
- }
231
- });
232
- tabOpt(
233
- program.command("read").description("Extract page structure and interactive elements.").option("-i, --interactive", "return only interactive elements")
234
- ).action(async (opts) => {
235
- const args = {};
236
- if (opts.tab !== void 0) args.tabId = opts.tab;
237
- if (opts.interactive) args.interactiveOnly = true;
238
- await run("chrome_read_page", args);
239
- });
240
- tabOpt(
241
- program.command("click <selector>").description("Click an element by CSS selector.")
242
- ).action(async (selector, opts) => {
243
- const args = { selector };
244
- if (opts.tab !== void 0) args.tabId = opts.tab;
245
- await run("chrome_click_element", args);
246
- });
247
- tabOpt(
248
- program.command("fill <selector> <value>").description("Fill an input or textarea.")
249
- ).action(async (selector, value, opts) => {
250
- const args = { selector, value };
251
- if (opts.tab !== void 0) args.tabId = opts.tab;
252
- await run("chrome_fill_or_select", args);
253
- });
254
- tabOpt(
255
- program.command("keys <keys>").description("Send keyboard input (e.g. Enter, Meta+L).")
256
- ).action(async (keys, opts) => {
257
- const args = { keys };
258
- if (opts.tab !== void 0) args.tabId = opts.tab;
259
- await run("chrome_keyboard", args);
260
- });
261
- program.command("switch <tabId>").description("Activate a tab by ID.").action(async (tabId) => {
262
- await run("chrome_switch_tab", { tabId: Number(tabId) });
263
- });
264
- program.command("close <tabIds...>").description("Close one or more tabs by ID.").action(async (tabIds) => {
265
- await run("chrome_close_tabs", { tabIds: tabIds.map(Number) });
266
- });
267
- program.command("call <tool> [json]").description("Call any Chrome Relay tool with raw JSON args.").action(async (tool, json) => {
268
- const args = json ? JSON.parse(json) : {};
269
- await run(tool, args);
270
- });
271
- program.parseAsync(process.argv);
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/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- declare const CHROME_RELAY_VERSION = "0.2.2";
1
+ declare const CHROME_RELAY_VERSION = "0.2.3";
2
2
 
3
3
  export { CHROME_RELAY_VERSION };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- var CHROME_RELAY_VERSION = "0.2.2";
2
+ var CHROME_RELAY_VERSION = "0.2.3";
3
3
  export {
4
4
  CHROME_RELAY_VERSION
5
5
  };
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-relay",
3
- "version": "0.2.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": ["chrome", "browser", "automation", "agents", "native-messaging"],
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
- "@chrome-relay/protocol": "workspace:*",
28
- "tsup": "^8.4.0"
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
+ }