chrome-relay 0.5.20 → 0.5.22

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.20" : "0.0.0-dev";
1104
+ var CHROME_RELAY_VERSION = true ? "0.5.22" : "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");
@@ -1254,18 +1255,52 @@ function getDefaultAllowedOrigins() {
1254
1255
  function formatKnownExtensionIds() {
1255
1256
  return KNOWN_EXTENSION_IDS.map(([label, id]) => `${label}: ${id}`).join(", ");
1256
1257
  }
1257
- function getChromeManifestDir() {
1258
+ function getChromiumBrowserTargets() {
1259
+ const home = os.homedir();
1258
1260
  if (process.platform === "darwin") {
1259
- return path.join(
1260
- os.homedir(),
1261
- "Library/Application Support/Google/Chrome/NativeMessagingHosts"
1262
- );
1261
+ const appSupport = path.join(home, "Library/Application Support");
1262
+ return [
1263
+ { label: "Google Chrome", installRoot: path.join(appSupport, "Google/Chrome"), manifestDir: path.join(appSupport, "Google/Chrome/NativeMessagingHosts") },
1264
+ { label: "Google Chrome Canary", installRoot: path.join(appSupport, "Google/Chrome Canary"), manifestDir: path.join(appSupport, "Google/Chrome Canary/NativeMessagingHosts") },
1265
+ { label: "Chromium", installRoot: path.join(appSupport, "Chromium"), manifestDir: path.join(appSupport, "Chromium/NativeMessagingHosts") },
1266
+ { label: "Microsoft Edge", installRoot: path.join(appSupport, "Microsoft Edge"), manifestDir: path.join(appSupport, "Microsoft Edge/NativeMessagingHosts") },
1267
+ { label: "Brave", installRoot: path.join(appSupport, "BraveSoftware/Brave-Browser"), manifestDir: path.join(appSupport, "BraveSoftware/Brave-Browser/NativeMessagingHosts") },
1268
+ { label: "Vivaldi", installRoot: path.join(appSupport, "Vivaldi"), manifestDir: path.join(appSupport, "Vivaldi/NativeMessagingHosts") },
1269
+ { label: "Arc", installRoot: path.join(appSupport, "Arc/User Data"), manifestDir: path.join(appSupport, "Arc/User Data/NativeMessagingHosts") },
1270
+ { label: "Opera", installRoot: path.join(appSupport, "com.operasoftware.Opera"), manifestDir: path.join(appSupport, "com.operasoftware.Opera/NativeMessagingHosts") }
1271
+ ];
1263
1272
  }
1264
1273
  if (process.platform === "linux") {
1265
- return path.join(os.homedir(), ".config/google-chrome/NativeMessagingHosts");
1274
+ const config = path.join(home, ".config");
1275
+ return [
1276
+ { label: "Google Chrome", installRoot: path.join(config, "google-chrome"), manifestDir: path.join(config, "google-chrome/NativeMessagingHosts") },
1277
+ { label: "Chromium", installRoot: path.join(config, "chromium"), manifestDir: path.join(config, "chromium/NativeMessagingHosts") },
1278
+ { label: "Microsoft Edge", installRoot: path.join(config, "microsoft-edge"), manifestDir: path.join(config, "microsoft-edge/NativeMessagingHosts") },
1279
+ { label: "Brave", installRoot: path.join(config, "BraveSoftware/Brave-Browser"), manifestDir: path.join(config, "BraveSoftware/Brave-Browser/NativeMessagingHosts") },
1280
+ { label: "Vivaldi", installRoot: path.join(config, "vivaldi"), manifestDir: path.join(config, "vivaldi/NativeMessagingHosts") },
1281
+ { label: "Opera", installRoot: path.join(config, "opera"), manifestDir: path.join(config, "opera/NativeMessagingHosts") }
1282
+ ];
1266
1283
  }
1267
1284
  throw new Error(`Unsupported platform for install: ${process.platform}`);
1268
1285
  }
1286
+ async function pathExists(p) {
1287
+ try {
1288
+ await stat(p);
1289
+ return true;
1290
+ } catch {
1291
+ return false;
1292
+ }
1293
+ }
1294
+ async function getInstalledBrowsers() {
1295
+ const all = getChromiumBrowserTargets();
1296
+ const installed = [];
1297
+ for (const target of all) {
1298
+ if (await pathExists(target.installRoot)) {
1299
+ installed.push(target);
1300
+ }
1301
+ }
1302
+ return installed;
1303
+ }
1269
1304
  function getDistDir() {
1270
1305
  return path.dirname(fileURLToPath(import.meta.url));
1271
1306
  }
@@ -1279,10 +1314,7 @@ exec "${process.execPath}" "${hostPath}"
1279
1314
  await chmod(wrapperPath, 493);
1280
1315
  return wrapperPath;
1281
1316
  }
1282
- async function writeManifest(wrapperPath) {
1283
- const manifestDir = getChromeManifestDir();
1284
- await mkdir(manifestDir, { recursive: true });
1285
- const manifestPath = path.join(manifestDir, `${NATIVE_HOST_NAME}.json`);
1317
+ async function writeManifestsForBrowsers(wrapperPath, browsers) {
1286
1318
  const manifest = {
1287
1319
  name: NATIVE_HOST_NAME,
1288
1320
  description: "Native host for Chrome Relay",
@@ -1290,32 +1322,104 @@ async function writeManifest(wrapperPath) {
1290
1322
  type: "stdio",
1291
1323
  allowed_origins: getDefaultAllowedOrigins()
1292
1324
  };
1293
- await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}
1294
- `, "utf8");
1295
- return manifestPath;
1325
+ const body = `${JSON.stringify(manifest, null, 2)}
1326
+ `;
1327
+ const written = [];
1328
+ for (const target of browsers) {
1329
+ await mkdir(target.manifestDir, { recursive: true });
1330
+ const manifestPath = path.join(target.manifestDir, `${NATIVE_HOST_NAME}.json`);
1331
+ await writeFile(manifestPath, body, "utf8");
1332
+ written.push({ browser: target.label, manifestPath });
1333
+ }
1334
+ return written;
1335
+ }
1336
+ function killStaleNativeHosts() {
1337
+ if (process.platform !== "darwin" && process.platform !== "linux") {
1338
+ return { killed: 0 };
1339
+ }
1340
+ const ps = spawnSync("ps", ["-A", "-o", "pid=,command="], { encoding: "utf8" });
1341
+ if (ps.status !== 0 || !ps.stdout) return { killed: 0 };
1342
+ let killed = 0;
1343
+ for (const raw of ps.stdout.split("\n")) {
1344
+ const line = raw.trim();
1345
+ if (!line) continue;
1346
+ if (!line.includes("chrome-relay") || !line.includes("native-host.js")) continue;
1347
+ const m = line.match(/^(\d+)\s/);
1348
+ if (!m) continue;
1349
+ const pid = Number.parseInt(m[1], 10);
1350
+ if (pid === process.pid) continue;
1351
+ try {
1352
+ process.kill(pid, "SIGTERM");
1353
+ killed++;
1354
+ } catch {
1355
+ }
1356
+ }
1357
+ return { killed };
1296
1358
  }
1297
1359
  async function runInstall() {
1298
1360
  const distDir = getDistDir();
1299
1361
  const hostPath = path.join(distDir, "native-host.js");
1300
1362
  const wrapperPath = await writeWrapperScript(hostPath);
1301
- const manifestPath = await writeManifest(wrapperPath);
1363
+ const installed = await getInstalledBrowsers();
1364
+ if (installed.length === 0) {
1365
+ const all = getChromiumBrowserTargets();
1366
+ const fallback = all.find((t) => t.label === "Google Chrome");
1367
+ if (fallback) installed.push(fallback);
1368
+ }
1369
+ const writtenManifests = await writeManifestsForBrowsers(wrapperPath, installed);
1370
+ const { killed } = killStaleNativeHosts();
1302
1371
  console.log(`Installed Chrome Relay native host.`);
1303
1372
  console.log(`Wrapper: ${wrapperPath}`);
1304
- console.log(`Manifest: ${manifestPath}`);
1373
+ console.log(`Manifests written:`);
1374
+ for (const m of writtenManifests) {
1375
+ console.log(` \u2022 ${m.browser}: ${m.manifestPath}`);
1376
+ }
1305
1377
  console.log(`Local bridge port: ${DEFAULT_HTTP_PORT}`);
1306
1378
  console.log(`Allowed extension IDs: ${formatKnownExtensionIds()}`);
1379
+ if (killed > 0) {
1380
+ console.log(`Reaped ${killed} stale native-host process${killed === 1 ? "" : "es"}; browsers will respawn from the new manifest.`);
1381
+ }
1307
1382
  }
1308
1383
  async function runDoctor() {
1309
1384
  try {
1310
1385
  const wrapperPath = path.join(APP_DIR, "run-host.sh");
1311
- const manifestPath = path.join(getChromeManifestDir(), `${NATIVE_HOST_NAME}.json`);
1312
1386
  await stat(wrapperPath);
1313
- await stat(manifestPath);
1314
- const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
1315
- const allowedOrigins = Array.isArray(manifest.allowed_origins) ? manifest.allowed_origins : [];
1316
- const missingOrigins = getDefaultAllowedOrigins().filter(
1317
- (origin) => !allowedOrigins.includes(origin)
1318
- );
1387
+ console.log(`Wrapper present: yes`);
1388
+ const installed = await getInstalledBrowsers();
1389
+ if (installed.length === 0) {
1390
+ console.log(`No Chromium-based browsers detected.`);
1391
+ console.log(`Tip: install Chrome / Arc / Brave / Edge / Chromium / Vivaldi / Opera then re-run "chrome-relay install".`);
1392
+ return false;
1393
+ }
1394
+ const required = getDefaultAllowedOrigins();
1395
+ let allHealthy = true;
1396
+ console.log(`Detected browsers (${installed.length}):`);
1397
+ for (const target of installed) {
1398
+ const manifestPath = path.join(target.manifestDir, `${NATIVE_HOST_NAME}.json`);
1399
+ const exists = await pathExists(manifestPath);
1400
+ if (!exists) {
1401
+ allHealthy = false;
1402
+ console.log(` \u2022 ${target.label}: manifest MISSING (${manifestPath})`);
1403
+ continue;
1404
+ }
1405
+ try {
1406
+ const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
1407
+ const allowedOrigins = Array.isArray(manifest.allowed_origins) ? manifest.allowed_origins : [];
1408
+ const missingOrigins = required.filter((o) => !allowedOrigins.includes(o));
1409
+ if (missingOrigins.length > 0) {
1410
+ allHealthy = false;
1411
+ console.log(` \u2022 ${target.label}: manifest present but missing origins: ${missingOrigins.join(", ")}`);
1412
+ } else {
1413
+ console.log(` \u2022 ${target.label}: ok`);
1414
+ }
1415
+ } catch (e) {
1416
+ allHealthy = false;
1417
+ console.log(` \u2022 ${target.label}: manifest unreadable (${e instanceof Error ? e.message : String(e)})`);
1418
+ }
1419
+ }
1420
+ if (!allHealthy) {
1421
+ console.log(`Tip: run "chrome-relay install" to refresh manifests for every detected browser.`);
1422
+ }
1319
1423
  let serverReachable = false;
1320
1424
  try {
1321
1425
  const response = await fetch(`http://127.0.0.1:${DEFAULT_HTTP_PORT}/ping`);
@@ -1323,19 +1427,12 @@ async function runDoctor() {
1323
1427
  } catch {
1324
1428
  serverReachable = false;
1325
1429
  }
1326
- console.log(`Wrapper present: yes`);
1327
- console.log(`Manifest present: yes`);
1328
1430
  console.log(`Allowed extension IDs: ${formatKnownExtensionIds()}`);
1329
- console.log(`Allowed origins: ${(manifest.allowed_origins ?? ["missing"]).join(", ")}`);
1330
- if (missingOrigins.length > 0) {
1331
- console.log(`Manifest missing origins: ${missingOrigins.join(", ")}`);
1332
- console.log(`Tip: run "chrome-relay install" to refresh the native host manifest.`);
1333
- }
1334
1431
  console.log(`Local bridge reachable: ${serverReachable ? "yes" : "no"}`);
1335
1432
  if (!serverReachable) {
1336
- console.log(`Tip: load the extension so it can launch the native host.`);
1433
+ console.log(`Tip: load the extension in one of the detected browsers so it can launch the native host.`);
1337
1434
  }
1338
- return true;
1435
+ return allHealthy;
1339
1436
  } catch (error) {
1340
1437
  console.error(error instanceof Error ? error.message : String(error));
1341
1438
  return false;
@@ -1344,6 +1441,17 @@ async function runDoctor() {
1344
1441
 
1345
1442
  // src/release-notes.ts
1346
1443
  var RELEASE_NOTES = {
1444
+ "0.5.22": [
1445
+ "Multi-browser install. `chrome-relay install` now writes the native-messaging manifest into every detected Chromium-fork browser's NativeMessagingHosts dir, not just Google Chrome's. Detected: Chrome, Chrome Canary, Chromium, Edge, Brave, Vivaldi, Arc, Opera (macOS + Linux paths). Detection is parent-dir existence \u2014 we never speculatively create profile dirs for browsers that aren't installed.",
1446
+ "Why this matters: the extension installs fine via Chrome Web Store in any Chromium fork, but the bridge silently failed because the host manifest was only at Chrome's path. Arc + Brave users hit `connectNative()` errors with no obvious cause.",
1447
+ "`chrome-relay doctor` now reports per-browser manifest status, so when a refresh is needed the failure points at the specific browser whose manifest drifted.",
1448
+ "Fallback: if no Chromium browser is detected on the machine, we still drop the manifest at Chrome's path so a later Chrome install picks it up."
1449
+ ],
1450
+ "0.5.21": [
1451
+ "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.",
1452
+ "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.",
1453
+ "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."
1454
+ ],
1347
1455
  "0.5.20": [
1348
1456
  "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.",
1349
1457
  "`--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.",
@@ -1511,7 +1619,7 @@ function registerInstallUpdate(program) {
1511
1619
  });
1512
1620
  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) => {
1513
1621
  const fromVersion = CHROME_RELAY_VERSION;
1514
- const { spawnSync } = await import("child_process");
1622
+ const { spawnSync: spawnSync2 } = await import("child_process");
1515
1623
  const out = {
1516
1624
  updatedFrom: fromVersion,
1517
1625
  updatedTo: fromVersion,
@@ -1531,7 +1639,7 @@ function registerInstallUpdate(program) {
1531
1639
  };
1532
1640
  process.stderr.write(`[chrome-relay] updating from ${fromVersion} via ${pm}...
1533
1641
  `);
1534
- const install = spawnSync(cmd[0], cmd[1], { stdio: "inherit" });
1642
+ const install = spawnSync2(cmd[0], cmd[1], { stdio: "inherit" });
1535
1643
  out.install.status = install.status;
1536
1644
  if (install.status !== 0) {
1537
1645
  process.stderr.write(`[chrome-relay] install failed (${pm} exited ${install.status}). Try manually: ${cmd[0]} ${cmd[1].join(" ")}
@@ -1543,15 +1651,22 @@ function registerInstallUpdate(program) {
1543
1651
  process.stdout.write(JSON.stringify(out, null, 2) + "\n");
1544
1652
  process.exit(1);
1545
1653
  }
1546
- const which = spawnSync("which", ["chrome-relay"]);
1654
+ const which = spawnSync2("which", ["chrome-relay"]);
1547
1655
  const newBin = which.stdout?.toString().trim();
1548
1656
  if (which.status === 0 && newBin) {
1549
- const versionOut = spawnSync(newBin, ["--version"]);
1657
+ const versionOut = spawnSync2(newBin, ["--version"]);
1550
1658
  const newVersion = (versionOut.stdout?.toString() ?? "").trim();
1551
1659
  out.binary.path = newBin;
1552
1660
  if (newVersion && newVersion !== fromVersion) {
1553
1661
  out.updatedTo = newVersion;
1554
- const rn = spawnSync(newBin, ["release-notes", "--since", fromVersion]);
1662
+ const install2 = spawnSync2(newBin, ["install"], { stdio: "inherit" });
1663
+ if (install2.status !== 0) {
1664
+ out.warnings.push({
1665
+ code: "install_refresh_failed",
1666
+ message: `Update installed the new package but \`${newBin} install\` exited ${install2.status}. Run it manually to refresh the native host manifest.`
1667
+ });
1668
+ }
1669
+ const rn = spawnSync2(newBin, ["release-notes", "--since", fromVersion]);
1555
1670
  try {
1556
1671
  const parsed = JSON.parse(rn.stdout?.toString() ?? "");
1557
1672
  if (Array.isArray(parsed.changes)) {
@@ -1940,8 +2055,8 @@ Notes:
1940
2055
  }
1941
2056
  if (opts.gif || opts.mp4) {
1942
2057
  const fps = typeof opts.fps === "number" ? opts.fps : 15;
1943
- const { spawnSync } = await import("child_process");
1944
- const which = spawnSync("which", ["ffmpeg"]);
2058
+ const { spawnSync: spawnSync2 } = await import("child_process");
2059
+ const which = spawnSync2("which", ["ffmpeg"]);
1945
2060
  if (which.status !== 0) {
1946
2061
  if (opts.allowMissingFfmpeg) {
1947
2062
  process.stderr.write("[chrome-relay] ffmpeg not on PATH \u2014 skipping --gif/--mp4 (allow-missing-ffmpeg).\n");
@@ -1962,7 +2077,7 @@ Notes:
1962
2077
  }
1963
2078
  if (opts.gif) {
1964
2079
  const gifOut = `${opts.out.replace(/\/$/, "")}.gif`;
1965
- const r = spawnSync("ffmpeg", [
2080
+ const r = spawnSync2("ffmpeg", [
1966
2081
  "-y",
1967
2082
  "-framerate",
1968
2083
  String(fps),
@@ -1979,7 +2094,7 @@ Notes:
1979
2094
  }
1980
2095
  if (opts.mp4) {
1981
2096
  const mp4Out = `${opts.out.replace(/\/$/, "")}.mp4`;
1982
- const r = spawnSync("ffmpeg", [
2097
+ const r = spawnSync2("ffmpeg", [
1983
2098
  "-y",
1984
2099
  "-framerate",
1985
2100
  String(fps),
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- var CHROME_RELAY_VERSION = true ? "0.5.20" : "0.0.0-dev";
2
+ var CHROME_RELAY_VERSION = true ? "0.5.22" : "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.20" : "0.0.0-dev";
59
+ var CHROME_RELAY_VERSION = true ? "0.5.22" : "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.20",
3
+ "version": "0.5.22",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",