chrome-relay 0.5.0 → 0.5.2

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/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import { Command } from "commander";
5
5
  import { writeFileSync } from "fs";
6
6
 
7
7
  // src/index.ts
8
- var CHROME_RELAY_VERSION = true ? "0.5.0" : "0.0.0-dev";
8
+ var CHROME_RELAY_VERSION = true ? "0.5.2" : "0.0.0-dev";
9
9
 
10
10
  // src/install/install.ts
11
11
  import os from "os";
@@ -130,7 +130,14 @@ async function runDoctor() {
130
130
  }
131
131
 
132
132
  // src/client/call.ts
133
- async function callTool(name, args) {
133
+ var noticePrinted = false;
134
+ function emitNoticeOnce(notice) {
135
+ if (noticePrinted) return;
136
+ noticePrinted = true;
137
+ process.stderr.write(`[chrome-relay] ${notice}
138
+ `);
139
+ }
140
+ async function callToolWithMeta(name, args) {
134
141
  const response = await fetch(`http://127.0.0.1:${DEFAULT_HTTP_PORT}/call`, {
135
142
  method: "POST",
136
143
  headers: {
@@ -143,12 +150,58 @@ async function callTool(name, args) {
143
150
  });
144
151
  const payload = await response.json().catch(() => null);
145
152
  if (!response.ok) {
153
+ if (payload?.notice) emitNoticeOnce(payload.notice);
146
154
  throw new Error(payload?.error || `Bridge request failed with ${response.status}`);
147
155
  }
148
156
  if (!payload?.ok) {
157
+ if (payload?.notice) emitNoticeOnce(payload.notice);
149
158
  throw new Error(payload?.error || "Bridge call failed.");
150
159
  }
151
- return payload.data;
160
+ if (payload.notice) emitNoticeOnce(payload.notice);
161
+ return { data: payload.data, notice: payload.notice };
162
+ }
163
+ async function callTool(name, args) {
164
+ const { data } = await callToolWithMeta(name, args);
165
+ return data;
166
+ }
167
+
168
+ // src/release-notes.ts
169
+ var RELEASE_NOTES = {
170
+ "0.5.2": [
171
+ "Strict input parsers (code-quality-hardening PR 0). Invalid console levels, network status filters, tab-group colors, and tab-id lists now throw instead of being silently dropped \u2014 an agent that asks for `errors` (typo of `error`) gets a precise error rather than all levels back.",
172
+ "Affected tools: chrome_console (levels), chrome_network (status), chrome_group (color, tabIds), chrome_screencast (format, action), chrome_network (action).",
173
+ "Parsers moved to apps/extension/src/browser/parsers.ts (pure module, no chrome runtime imports) so they're directly unit-testable. 24 new tests cover the strict paths."
174
+ ],
175
+ "0.5.1": [
176
+ "Tool results now carry a `notice` field when the CLI is older than the connected extension \u2014 agents (or humans) get a structured nudge to run `chrome-relay update`.",
177
+ "New subcommand: `chrome-relay update` \u2014 installs the latest CLI via your package manager and prints what changed.",
178
+ "New subcommand: `chrome-relay release-notes --since <version>` \u2014 query the same change log without updating."
179
+ ],
180
+ "0.5.0": [
181
+ "New tool: `chrome_hover` \u2014 `Input.dispatchMouseEvent mouseMoved` at a selector or x,y. Fires :hover, :focus-within, tooltips, dropdown openers without clicking.",
182
+ "New tool: `chrome_screencast` \u2014 paint-driven CDP recording. Catches CSS transitions, fade-ins, and animation mid-states that screenshot polling misses. Requires the tab to be active.",
183
+ "`chrome-relay screencast {start,stop}` CLI with default-on SHA-256 dedupe (--no-dedupe to keep raw frames) and --gif/--mp4 ffmpeg post-step.",
184
+ "JPEG default quality bumped 60 \u2192 80 for max precision. See docs/recording.md."
185
+ ],
186
+ "0.4.0": [
187
+ "BREAKING: `chrome_group` repurposed for Chrome's native tab-groups (the colored, collapsible folders). Old isolation-window semantics moved to `chrome_workspace`.",
188
+ "New CLI: `chrome-relay workspace {create,list,close}` for parallel agent isolation (named background windows).",
189
+ "New CLI: `chrome-relay group {create,list,close,add,remove}` for visual tab-grouping inside a single window."
190
+ ]
191
+ };
192
+ function compareSemver(a, b) {
193
+ const pa = a.split(".").map((n) => Number(n) || 0);
194
+ const pb = b.split(".").map((n) => Number(n) || 0);
195
+ for (let i = 0; i < 3; i++) {
196
+ const ai = pa[i] ?? 0;
197
+ const bi = pb[i] ?? 0;
198
+ if (ai > bi) return 1;
199
+ if (ai < bi) return -1;
200
+ }
201
+ return 0;
202
+ }
203
+ function listReleaseNotesSince(since) {
204
+ return Object.keys(RELEASE_NOTES).filter((v) => compareSemver(v, since) > 0).sort((a, b) => compareSemver(a, b)).map((version) => ({ version, bullets: RELEASE_NOTES[version] }));
152
205
  }
153
206
 
154
207
  // src/program.ts
@@ -181,6 +234,43 @@ Notes:
181
234
  const ok = await runDoctor();
182
235
  process.exit(ok ? 0 : 1);
183
236
  });
237
+ program.command("update").description("Update chrome-relay CLI to the latest version and print what changed (agent-readable JSON).").option("--dry-run", "skip the install; just show what changed since the current version").action(async (opts) => {
238
+ const fromVersion = CHROME_RELAY_VERSION;
239
+ const { spawnSync } = await import("child_process");
240
+ if (!opts.dryRun) {
241
+ const argv0 = process.argv[1] ?? "";
242
+ const pm = /[\\/](pnpm|\.pnpm)[\\/]/.test(argv0) ? "pnpm" : /[\\/]bun[\\/]/.test(argv0) ? "bun" : "npm";
243
+ const cmd = pm === "pnpm" ? ["pnpm", ["add", "-g", "chrome-relay@latest"]] : pm === "bun" ? ["bun", ["add", "-g", "chrome-relay@latest"]] : ["npm", ["install", "-g", "chrome-relay@latest"]];
244
+ process.stderr.write(`[chrome-relay] updating from ${fromVersion} via ${pm}...
245
+ `);
246
+ const install = spawnSync(cmd[0], cmd[1], { stdio: "inherit" });
247
+ if (install.status !== 0) {
248
+ process.stderr.write(`[chrome-relay] install failed (${pm} exited ${install.status}). Try manually: ${pm} ${cmd[1].join(" ")}
249
+ `);
250
+ process.exit(1);
251
+ }
252
+ const which = spawnSync("which", ["chrome-relay"]);
253
+ const newBin = which.stdout?.toString().trim();
254
+ if (which.status === 0 && newBin && newBin !== argv0) {
255
+ spawnSync(newBin, ["release-notes", "--since", fromVersion], { stdio: "inherit" });
256
+ return;
257
+ }
258
+ }
259
+ const changes = listReleaseNotesSince(fromVersion);
260
+ process.stdout.write(JSON.stringify({
261
+ updatedFrom: fromVersion,
262
+ updatedTo: CHROME_RELAY_VERSION,
263
+ changes
264
+ }, null, 2) + "\n");
265
+ });
266
+ program.command("release-notes").description("Print release notes since a version (no install). JSON output for agents.").option("--since <version>", "show release notes for versions newer than this", "0.0.0").action((opts) => {
267
+ const changes = listReleaseNotesSince(opts.since);
268
+ process.stdout.write(JSON.stringify({
269
+ currentVersion: CHROME_RELAY_VERSION,
270
+ since: opts.since,
271
+ changes
272
+ }, null, 2) + "\n");
273
+ });
184
274
  async function run(name, args) {
185
275
  try {
186
276
  const result = await callTool(name, args);
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- var CHROME_RELAY_VERSION = true ? "0.5.0" : "0.0.0-dev";
2
+ var CHROME_RELAY_VERSION = true ? "0.5.2" : "0.0.0-dev";
3
3
  export {
4
4
  CHROME_RELAY_VERSION
5
5
  };
@@ -9,7 +9,29 @@ import Fastify from "fastify";
9
9
  // ../protocol/dist/index.js
10
10
  var DEFAULT_HTTP_PORT = 12122;
11
11
 
12
+ // src/index.ts
13
+ var CHROME_RELAY_VERSION = true ? "0.5.2" : "0.0.0-dev";
14
+
15
+ // src/release-notes.ts
16
+ function compareSemver(a, b) {
17
+ const pa = a.split(".").map((n) => Number(n) || 0);
18
+ const pb = b.split(".").map((n) => Number(n) || 0);
19
+ for (let i = 0; i < 3; i++) {
20
+ const ai = pa[i] ?? 0;
21
+ const bi = pb[i] ?? 0;
22
+ if (ai > bi) return 1;
23
+ if (ai < bi) return -1;
24
+ }
25
+ return 0;
26
+ }
27
+
12
28
  // src/http/server.ts
29
+ function buildOutdatedNotice(bridge2) {
30
+ const extVersion = bridge2.getExtensionVersion();
31
+ if (!extVersion) return void 0;
32
+ if (compareSemver(CHROME_RELAY_VERSION, extVersion) >= 0) return void 0;
33
+ return `cli-outdated: ${CHROME_RELAY_VERSION} < extension ${extVersion}; run \`chrome-relay update\``;
34
+ }
13
35
  var RelayHttpServer = class {
14
36
  constructor(bridge2, port = DEFAULT_HTTP_PORT) {
15
37
  this.bridge = bridge2;
@@ -19,7 +41,12 @@ var RelayHttpServer = class {
19
41
  port;
20
42
  app = Fastify({ logger: false });
21
43
  async start() {
22
- this.app.get("/ping", async () => ({ ok: true, port: this.port }));
44
+ this.app.get("/ping", async () => ({
45
+ ok: true,
46
+ port: this.port,
47
+ cliVersion: CHROME_RELAY_VERSION,
48
+ extensionVersion: this.bridge.getExtensionVersion() ?? null
49
+ }));
23
50
  this.app.post("/call", async (request, reply) => {
24
51
  if (request.headers.origin) {
25
52
  reply.code(403).send({ error: "Browser-origin bridge requests are not accepted." });
@@ -35,12 +62,16 @@ var RelayHttpServer = class {
35
62
  body.name,
36
63
  body.args ?? {}
37
64
  );
38
- reply.send({ ok: true, data });
65
+ const notice = buildOutdatedNotice(this.bridge);
66
+ reply.send(notice ? { ok: true, data, notice } : { ok: true, data });
39
67
  } catch (error) {
40
- reply.code(500).send({
68
+ const notice = buildOutdatedNotice(this.bridge);
69
+ const body2 = {
41
70
  ok: false,
42
71
  error: error instanceof Error ? error.message : String(error)
43
- });
72
+ };
73
+ if (notice) body2.notice = notice;
74
+ reply.code(500).send(body2);
44
75
  }
45
76
  });
46
77
  await this.app.listen({ port: this.port, host: "127.0.0.1" });
@@ -60,6 +91,12 @@ var ExtensionBridge = class {
60
91
  pending = /* @__PURE__ */ new Map();
61
92
  readyWaiters = /* @__PURE__ */ new Set();
62
93
  ready = false;
94
+ // Extension version captured from `bridge.ready`. Read by the HTTP server
95
+ // to compute the cli-outdated notice on each tool call.
96
+ extensionVersion;
97
+ getExtensionVersion() {
98
+ return this.extensionVersion;
99
+ }
63
100
  handleMessage(message) {
64
101
  if (message.type === "bridge.ready") {
65
102
  this.handleReady(message);
@@ -79,8 +116,9 @@ var ExtensionBridge = class {
79
116
  pending.resolve(true);
80
117
  }
81
118
  }
82
- handleReady(_message) {
119
+ handleReady(message) {
83
120
  this.ready = true;
121
+ this.extensionVersion = message.payload?.version;
84
122
  for (const notify of this.readyWaiters) {
85
123
  notify();
86
124
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-relay",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",