chrome-relay 0.5.19 → 0.5.21

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
@@ -1101,7 +1101,7 @@ var init_dist = __esm({
1101
1101
  import { Command } from "commander";
1102
1102
 
1103
1103
  // src/index.ts
1104
- var CHROME_RELAY_VERSION = true ? "0.5.19" : "0.0.0-dev";
1104
+ var CHROME_RELAY_VERSION = true ? "0.5.21" : "0.0.0-dev";
1105
1105
 
1106
1106
  // src/commands/shared.ts
1107
1107
  init_dist();
@@ -1237,6 +1237,7 @@ function isToolName(name) {
1237
1237
  init_dist();
1238
1238
  import os from "os";
1239
1239
  import path from "path";
1240
+ import { spawnSync } from "child_process";
1240
1241
  import { chmod, mkdir, readFile, stat, writeFile } from "fs/promises";
1241
1242
  import { fileURLToPath } from "url";
1242
1243
  var APP_DIR = path.join(os.homedir(), ".chrome-relay");
@@ -1294,16 +1295,43 @@ async function writeManifest(wrapperPath) {
1294
1295
  `, "utf8");
1295
1296
  return manifestPath;
1296
1297
  }
1298
+ function killStaleNativeHosts() {
1299
+ if (process.platform !== "darwin" && process.platform !== "linux") {
1300
+ return { killed: 0 };
1301
+ }
1302
+ const ps = spawnSync("ps", ["-A", "-o", "pid=,command="], { encoding: "utf8" });
1303
+ if (ps.status !== 0 || !ps.stdout) return { killed: 0 };
1304
+ let killed = 0;
1305
+ for (const raw of ps.stdout.split("\n")) {
1306
+ const line = raw.trim();
1307
+ if (!line) continue;
1308
+ if (!line.includes("chrome-relay") || !line.includes("native-host.js")) continue;
1309
+ const m = line.match(/^(\d+)\s/);
1310
+ if (!m) continue;
1311
+ const pid = Number.parseInt(m[1], 10);
1312
+ if (pid === process.pid) continue;
1313
+ try {
1314
+ process.kill(pid, "SIGTERM");
1315
+ killed++;
1316
+ } catch {
1317
+ }
1318
+ }
1319
+ return { killed };
1320
+ }
1297
1321
  async function runInstall() {
1298
1322
  const distDir = getDistDir();
1299
1323
  const hostPath = path.join(distDir, "native-host.js");
1300
1324
  const wrapperPath = await writeWrapperScript(hostPath);
1301
1325
  const manifestPath = await writeManifest(wrapperPath);
1326
+ const { killed } = killStaleNativeHosts();
1302
1327
  console.log(`Installed Chrome Relay native host.`);
1303
1328
  console.log(`Wrapper: ${wrapperPath}`);
1304
1329
  console.log(`Manifest: ${manifestPath}`);
1305
1330
  console.log(`Local bridge port: ${DEFAULT_HTTP_PORT}`);
1306
1331
  console.log(`Allowed extension IDs: ${formatKnownExtensionIds()}`);
1332
+ if (killed > 0) {
1333
+ console.log(`Reaped ${killed} stale native-host process${killed === 1 ? "" : "es"}; Chrome will respawn from the new manifest.`);
1334
+ }
1307
1335
  }
1308
1336
  async function runDoctor() {
1309
1337
  try {
@@ -1344,6 +1372,18 @@ async function runDoctor() {
1344
1372
 
1345
1373
  // src/release-notes.ts
1346
1374
  var RELEASE_NOTES = {
1375
+ "0.5.21": [
1376
+ "Fix: `chrome-relay update` and `chrome-relay install` now SIGTERM any running native-host.js process before exiting, and `update` re-runs `install` from the freshly-installed binary. Chrome respawns the host from the new manifest on its next native-messaging request.",
1377
+ "Why this matters: Chrome's native messaging keeps the host process alive for the session. Pre-0.5.21, `chrome-relay update` refreshed the on-disk package but Chrome kept routing through the OLD host. The HTTP bridge served by that old host then reported its own embedded `CHROME_RELAY_VERSION`, which falsely tripped the cli-outdated nudge against the newer extension. Users running the very command the nudge told them to run found the nudge still firing afterwards \u2014 the worst kind of UX bug.",
1378
+ "Best-effort and silent on failure: kill is only attempted on darwin/linux, only matches `native-host.js` paths that also contain `chrome-relay`, and ignores individual kill errors (already gone, no permission). Won't kill the running CLI itself."
1379
+ ],
1380
+ "0.5.20": [
1381
+ "BREAKING \u2014 `chrome-relay navigate` no longer steals focus by default. Background is now the implicit behavior; agents pass `--active` when they actually want the user looking at the new tab. The whole product pitch is 'operate without stealing focus' \u2014 the default needed to match.",
1382
+ "`--inactive` flag removed entirely. It was the opt-in for the previous (wrong) default; now it'd be a no-op, and no-op flags are dead code. Agents that were passing `--inactive` should drop it (the behavior is now the default). Commander will reject the unknown flag \u2014 that's the right signal to update.",
1383
+ "Workspace `chrome.windows.create({focused: false})` was already correct from earlier work, so workspaces are unaffected.",
1384
+ 'Click now dispatches as a pointer event. Added `pointerType: "mouse"` to all three `Input.dispatchMouseEvent` calls in the CLICK handler. Without it, CDP only fires mouse events \u2014 modern UI libs (Radix, React-Aria, Headless UI) listen on `pointerdown` and silently ignore mouse-only clicks, so dropdowns / menu triggers / select widgets stayed closed even though the click registered. Discovered while dogfooding `chrome-relay click --x N --y N` against npm\'s token-creation form; verified by completing the full token-creation flow end-to-end (radio click, combobox open, submit) via chrome-relay tools.',
1385
+ "Tests: navigate test updated to assert no `active` field in default request, plus a new test verifying `--active` opts in to focus."
1386
+ ],
1347
1387
  "0.5.19": [
1348
1388
  "Coordinate click. `chrome-relay click --x N --y N --tab N` dispatches a trusted Input.dispatchMouseEvent at the given pixel coordinates \u2014 no selector required. The selector positional became optional; the protocol parser collapses click args into a discriminated union (`kind: 'selector'` | `kind: 'coords'`) and rejects partial coords (`--x` without `--y`) with `invalid_arguments`.",
1349
1389
  "Intentionally NOT shipping `click-text`. Once coord-click exists, finding text + clicking is fully composable with `js` (TreeWalker \u2192 getBoundingClientRect) \u2192 `click --x/--y`. Per the CLI philosophy, that's a smart wrapper, not a primitive. The two-step recipe lives in docs/clicking-strategies.md as the documented pattern.",
@@ -1504,7 +1544,7 @@ function registerInstallUpdate(program) {
1504
1544
  });
1505
1545
  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) => {
1506
1546
  const fromVersion = CHROME_RELAY_VERSION;
1507
- const { spawnSync } = await import("child_process");
1547
+ const { spawnSync: spawnSync2 } = await import("child_process");
1508
1548
  const out = {
1509
1549
  updatedFrom: fromVersion,
1510
1550
  updatedTo: fromVersion,
@@ -1524,7 +1564,7 @@ function registerInstallUpdate(program) {
1524
1564
  };
1525
1565
  process.stderr.write(`[chrome-relay] updating from ${fromVersion} via ${pm}...
1526
1566
  `);
1527
- const install = spawnSync(cmd[0], cmd[1], { stdio: "inherit" });
1567
+ const install = spawnSync2(cmd[0], cmd[1], { stdio: "inherit" });
1528
1568
  out.install.status = install.status;
1529
1569
  if (install.status !== 0) {
1530
1570
  process.stderr.write(`[chrome-relay] install failed (${pm} exited ${install.status}). Try manually: ${cmd[0]} ${cmd[1].join(" ")}
@@ -1536,15 +1576,22 @@ function registerInstallUpdate(program) {
1536
1576
  process.stdout.write(JSON.stringify(out, null, 2) + "\n");
1537
1577
  process.exit(1);
1538
1578
  }
1539
- const which = spawnSync("which", ["chrome-relay"]);
1579
+ const which = spawnSync2("which", ["chrome-relay"]);
1540
1580
  const newBin = which.stdout?.toString().trim();
1541
1581
  if (which.status === 0 && newBin) {
1542
- const versionOut = spawnSync(newBin, ["--version"]);
1582
+ const versionOut = spawnSync2(newBin, ["--version"]);
1543
1583
  const newVersion = (versionOut.stdout?.toString() ?? "").trim();
1544
1584
  out.binary.path = newBin;
1545
1585
  if (newVersion && newVersion !== fromVersion) {
1546
1586
  out.updatedTo = newVersion;
1547
- const rn = spawnSync(newBin, ["release-notes", "--since", fromVersion]);
1587
+ const install2 = spawnSync2(newBin, ["install"], { stdio: "inherit" });
1588
+ if (install2.status !== 0) {
1589
+ out.warnings.push({
1590
+ code: "install_refresh_failed",
1591
+ message: `Update installed the new package but \`${newBin} install\` exited ${install2.status}. Run it manually to refresh the native host manifest.`
1592
+ });
1593
+ }
1594
+ const rn = spawnSync2(newBin, ["release-notes", "--since", fromVersion]);
1548
1595
  try {
1549
1596
  const parsed = JSON.parse(rn.stdout?.toString() ?? "");
1550
1597
  if (Array.isArray(parsed.changes)) {
@@ -1597,14 +1644,19 @@ function registerNavigation(ctx) {
1597
1644
  await run("get_windows_and_tabs", {});
1598
1645
  });
1599
1646
  tabOpt(
1600
- 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(
1647
+ 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("--active", "activate the tab after navigating (default: background \u2014 no focus theft)").addHelpText(
1601
1648
  "after",
1602
1649
  `
1603
1650
 
1604
1651
  Examples:
1605
- chrome-relay navigate "https://example.com"
1606
- chrome-relay navigate --tab 123456789 "https://example.com"
1607
- chrome-relay navigate "https://example.com" --new --inactive
1652
+ chrome-relay navigate "https://example.com" # navigate current tab
1653
+ chrome-relay navigate --tab 123 "https://example.com" # navigate an existing tab
1654
+ chrome-relay navigate "https://example.com" --new # open in a new background tab
1655
+ chrome-relay navigate "https://example.com" --new --active # open new tab AND show it to the user
1656
+
1657
+ By default chrome-relay never steals focus \u2014 navigated tabs (new or
1658
+ existing) stay in whatever state they're in. Pass --active when you
1659
+ actually want the user looking at the page.
1608
1660
  `
1609
1661
  )
1610
1662
  ).action(async (url, opts) => {
@@ -1618,7 +1670,7 @@ Use "chrome-relay switch ${url}" to activate that tab, or "chrome-relay navigate
1618
1670
  }
1619
1671
  const extras = { url };
1620
1672
  if (opts.new) extras.newTab = true;
1621
- if (opts.inactive) extras.active = false;
1673
+ if (opts.active) extras.active = true;
1622
1674
  await run("chrome_navigate", withBase(opts, extras));
1623
1675
  });
1624
1676
  program.command("switch <tabId>").description("Activate a tab by ID.").action(async (tabId) => {
@@ -1928,8 +1980,8 @@ Notes:
1928
1980
  }
1929
1981
  if (opts.gif || opts.mp4) {
1930
1982
  const fps = typeof opts.fps === "number" ? opts.fps : 15;
1931
- const { spawnSync } = await import("child_process");
1932
- const which = spawnSync("which", ["ffmpeg"]);
1983
+ const { spawnSync: spawnSync2 } = await import("child_process");
1984
+ const which = spawnSync2("which", ["ffmpeg"]);
1933
1985
  if (which.status !== 0) {
1934
1986
  if (opts.allowMissingFfmpeg) {
1935
1987
  process.stderr.write("[chrome-relay] ffmpeg not on PATH \u2014 skipping --gif/--mp4 (allow-missing-ffmpeg).\n");
@@ -1950,7 +2002,7 @@ Notes:
1950
2002
  }
1951
2003
  if (opts.gif) {
1952
2004
  const gifOut = `${opts.out.replace(/\/$/, "")}.gif`;
1953
- const r = spawnSync("ffmpeg", [
2005
+ const r = spawnSync2("ffmpeg", [
1954
2006
  "-y",
1955
2007
  "-framerate",
1956
2008
  String(fps),
@@ -1967,7 +2019,7 @@ Notes:
1967
2019
  }
1968
2020
  if (opts.mp4) {
1969
2021
  const mp4Out = `${opts.out.replace(/\/$/, "")}.mp4`;
1970
- const r = spawnSync("ffmpeg", [
2022
+ const r = spawnSync2("ffmpeg", [
1971
2023
  "-y",
1972
2024
  "-framerate",
1973
2025
  String(fps),
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- var CHROME_RELAY_VERSION = true ? "0.5.19" : "0.0.0-dev";
2
+ var CHROME_RELAY_VERSION = true ? "0.5.21" : "0.0.0-dev";
3
3
  export {
4
4
  CHROME_RELAY_VERSION
5
5
  };
@@ -56,7 +56,7 @@ function toBridgeError(unknownErr, fallbackTool) {
56
56
  }
57
57
 
58
58
  // src/index.ts
59
- var CHROME_RELAY_VERSION = true ? "0.5.19" : "0.0.0-dev";
59
+ var CHROME_RELAY_VERSION = true ? "0.5.21" : "0.0.0-dev";
60
60
 
61
61
  // src/release-notes.ts
62
62
  function compareSemver(a, b) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-relay",
3
- "version": "0.5.19",
3
+ "version": "0.5.21",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",