github-router 0.3.73 → 0.3.74

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.
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 3,
3
3
  "name": "github-router browser bridge",
4
4
  "short_name": "gh-router-browser",
5
- "version": "0.3.73",
5
+ "version": "0.3.74",
6
6
  "description": "Bridge between Claude (via github-router /mcp) and the browser. Implements tab control, navigation, clicks, form fill, downloads, screenshots, devtools eval. Blocks navigation to chrome://settings.",
7
7
  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqJElxuBlonBS3TVW9FJN0mGTtShB3L1hoaYf6k39SOr1ogGYmF90EjRxy1i21k9wQQjPf26bcBu/9X67KrQjQV0uB38CaNukgiSeoLjfptN811u+PJHx6BP+jx3Qa6/3VenNPxHC8WEU0GXql8QSjIHEyCwKb6fMASXOK94JyB5Ywov2x8mt/+9ncqBBBMVzf6r5Sagy4PL1XnryLsuADD/vOEkPet8wXgH/Oj7v5tTsQQZ7U1JT51PoDs2BFnXc5v3TkVgZwd32k3ONh+nkDw1Hof+4zwUGOyJE6eMrlYzRlKM4Qxdf9JpavQvqfieAbTRWcyKeclnHeoIfE7cDBQIDAQAB",
8
8
  "background": {
package/dist/main.js CHANGED
@@ -13,7 +13,7 @@ import * as path$1 from "node:path";
13
13
  import path, { dirname, join } from "node:path";
14
14
  import process$1 from "node:process";
15
15
  import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
16
- import fs$1, { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
16
+ import fs$1, { chmodSync, closeSync, cpSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
17
17
  import { fileURLToPath } from "node:url";
18
18
  import { performance } from "node:perf_hooks";
19
19
  import { createInterface } from "node:readline";
@@ -5674,33 +5674,61 @@ function packageRoot() {
5674
5674
  } catch {}
5675
5675
  return process$1?.cwd?.() ?? ".";
5676
5676
  }
5677
+ function fileExists(p) {
5678
+ return existsSync(p);
5679
+ }
5680
+ /** Stable materialized extension dir: `<APP_DIR>/browser-ext`. */
5681
+ function stableExtensionDir() {
5682
+ return path.join(PATHS.APP_DIR, "browser-ext");
5683
+ }
5684
+ /** Stable materialized bridge bundle: `<APP_DIR>/browser-bridge/index.js`. */
5685
+ function stableBridgeBundlePath() {
5686
+ return path.join(PATHS.APP_DIR, "browser-bridge", "index.js");
5687
+ }
5677
5688
  /**
5678
- * Absolute path to the extension's source directory. Layouts:
5689
+ * The bundled (shipped) extension dir — the SOURCE for provisioning,
5690
+ * never the runtime load path. Layouts:
5679
5691
  *
5680
5692
  * - Installed via npm: `<package>/dist/browser-ext/` (the published
5681
5693
  * tarball ships only `dist/`, see package.json "files"). The build
5682
- * step copies `src/browser-ext/` → `dist/browser-ext/` so the
5683
- * unpacked extension is available to users.
5684
- * - Running from this repo: dist/browser-ext/ if it exists (after
5685
- * `bun run build`), else src/browser-ext/ for fresh-clone-pre-build.
5694
+ * step copies `src/browser-ext/` → `dist/browser-ext/`.
5695
+ * - Running from this repo: `dist/browser-ext/` if built, else
5696
+ * `src/browser-ext/` for fresh-clone-pre-build.
5697
+ */
5698
+ function bundledExtensionDir() {
5699
+ const root = packageRoot();
5700
+ const distExt = path.join(root, "dist", "browser-ext");
5701
+ if (fileExists(path.join(distExt, "manifest.json"))) return distExt;
5702
+ return path.join(root, "src", "browser-ext");
5703
+ }
5704
+ /** The bundled (shipped) bridge entrypoint — SOURCE for provisioning. */
5705
+ function bundledBridgeBundlePath() {
5706
+ return path.join(packageRoot(), "dist", "browser-bridge", "index.js");
5707
+ }
5708
+ /**
5709
+ * Runtime extension directory — the path Chrome "Load unpacked"s and the
5710
+ * path the NMH manifest's stable-id derivation reads. Resolution order:
5686
5711
  *
5687
- * Override with `GH_ROUTER_BROWSER_EXT_DIR=<abs path>` for development
5688
- * (lets you point at a working copy of the extension you're editing
5689
- * without rebuilding between iterations).
5712
+ * 1. `GH_ROUTER_BROWSER_EXT_DIR=<abs path>` dev override (lets you
5713
+ * point at a working copy you're editing without rebuilding).
5714
+ * 2. The stable materialized copy under `<APP_DIR>` if present.
5715
+ * 3. The bundled dir (dist, then src) as the pre-provision fallback.
5690
5716
  */
5691
5717
  function extensionDir() {
5692
5718
  const override = process$1.env.GH_ROUTER_BROWSER_EXT_DIR;
5693
5719
  if (override && override.length > 0) return override;
5694
- const root = packageRoot();
5695
- const distExt = path.join(root, "dist", "browser-ext");
5696
- try {
5697
- if (readFileSync(path.join(distExt, "manifest.json")).length > 0) return distExt;
5698
- } catch {}
5699
- return path.join(root, "src", "browser-ext");
5720
+ if (fileExists(path.join(stableExtensionDir(), "manifest.json"))) return stableExtensionDir();
5721
+ return bundledExtensionDir();
5700
5722
  }
5701
- /** Absolute path to the bundled bridge entrypoint. */
5723
+ /**
5724
+ * Runtime bridge bundle path — what the launcher shim invokes. Prefers
5725
+ * the stable materialized copy; falls back to the bundled bundle when
5726
+ * provisioning hasn't run yet (or couldn't, on a fresh unbuilt checkout).
5727
+ */
5702
5728
  function bridgeBundlePath() {
5703
- return path.join(packageRoot(), "dist", "browser-bridge", "index.js");
5729
+ const stable = stableBridgeBundlePath();
5730
+ if (fileExists(stable)) return stable;
5731
+ return bundledBridgeBundlePath();
5704
5732
  }
5705
5733
  function appBrowserMcpDir() {
5706
5734
  const dir = path.join(PATHS.APP_DIR, "browser-mcp");
@@ -5828,6 +5856,164 @@ function installNativeHostForAll(browsers) {
5828
5856
  return results;
5829
5857
  }
5830
5858
 
5859
+ //#endregion
5860
+ //#region src/lib/browser-mcp/provision.ts
5861
+ /** Sidecar holding the content signature of the last materialized copy. */
5862
+ const SIGNATURE_FILE = ".provisioned";
5863
+ /** Source files excluded from both the copy and the content signature. */
5864
+ const EXCLUDED_FILES = new Set(["README.md", SIGNATURE_FILE]);
5865
+ let _provisioned = false;
5866
+ let _inFlight;
5867
+ /**
5868
+ * Materialize the extension + bridge into the stable app-dir and stamp
5869
+ * the running version. Single-flight + once-guarded so the startup fire-
5870
+ * and-forget and the lazy install-check call collapse to one run per
5871
+ * process. Resolves (never rejects) regardless of outcome.
5872
+ */
5873
+ function provisionBrowserAssets() {
5874
+ if (_provisioned) return Promise.resolve();
5875
+ if (_inFlight) return _inFlight;
5876
+ _inFlight = _provisionImpl().finally(() => {
5877
+ _inFlight = void 0;
5878
+ });
5879
+ return _inFlight;
5880
+ }
5881
+ async function _provisionImpl() {
5882
+ try {
5883
+ if (process.env.GH_ROUTER_DISABLE_BROWSER_PROVISION === "1") return;
5884
+ const srcExtDir = bundledExtensionDir();
5885
+ const srcBridge = bundledBridgeBundlePath();
5886
+ if (!existsSync(srcBridge)) return;
5887
+ const destExtDir = stableExtensionDir();
5888
+ const destBridge = stableBridgeBundlePath();
5889
+ const sigPath = path.join(destExtDir, SIGNATURE_FILE);
5890
+ const signature = computeSignature(srcExtDir, srcBridge);
5891
+ const upToDate = existsSync(path.join(destExtDir, "manifest.json")) && existsSync(destBridge) && readSignature(sigPath) === signature;
5892
+ let fullySynced = true;
5893
+ if (!upToDate) {
5894
+ materializeExtension(srcExtDir, destExtDir);
5895
+ const stampOk = stampVersion(destExtDir);
5896
+ const bridgeUpdated = tryMaterializeBridge(srcBridge, destBridge);
5897
+ if (stampOk && bridgeUpdated) writeSignature(sigPath, signature);
5898
+ else fullySynced = false;
5899
+ }
5900
+ let hostOk = true;
5901
+ try {
5902
+ const browsers = detectSupportedBrowsers();
5903
+ if (browsers.length > 0) installNativeHostForAll(browsers);
5904
+ } catch (err) {
5905
+ hostOk = false;
5906
+ consola.debug("[browser-mcp] native-host install during provision failed:", err);
5907
+ }
5908
+ if (fullySynced && hostOk) _provisioned = true;
5909
+ } catch (err) {
5910
+ consola.debug("[browser-mcp] provisionBrowserAssets failed:", err);
5911
+ }
5912
+ }
5913
+ function computeSignature(srcExtDir, srcBridge) {
5914
+ const h = createHash("sha256");
5915
+ let names;
5916
+ try {
5917
+ names = readdirSync(srcExtDir).filter((n) => !EXCLUDED_FILES.has(n)).sort();
5918
+ } catch {
5919
+ names = [];
5920
+ }
5921
+ for (const name$1 of names) {
5922
+ h.update(name$1);
5923
+ try {
5924
+ h.update(readFileSync(path.join(srcExtDir, name$1)));
5925
+ } catch {
5926
+ h.update(`\x00unreadable:${name$1}\x00`);
5927
+ }
5928
+ }
5929
+ h.update("bridge");
5930
+ try {
5931
+ h.update(readFileSync(srcBridge));
5932
+ } catch {
5933
+ h.update("\0missing:bridge\0");
5934
+ }
5935
+ h.update(`\x00version:${getPackageVersion()}\x00`);
5936
+ return h.digest("hex");
5937
+ }
5938
+ function readSignature(sigPath) {
5939
+ try {
5940
+ return readFileSync(sigPath, "utf8").trim();
5941
+ } catch {
5942
+ return;
5943
+ }
5944
+ }
5945
+ function writeSignature(sigPath, signature) {
5946
+ writeFileSync(sigPath, signature, "utf8");
5947
+ }
5948
+ /**
5949
+ * Copy the bundled extension dir into the stable dir, overwriting in
5950
+ * place. README and our sidecar are filtered out. We do NOT prune extra
5951
+ * files in the destination (a stale file left from an older version is
5952
+ * harmless — Chrome loads only what the manifest references).
5953
+ */
5954
+ function materializeExtension(srcDir, destDir) {
5955
+ mkdirSync(destDir, { recursive: true });
5956
+ cpSync(srcDir, destDir, {
5957
+ recursive: true,
5958
+ force: true,
5959
+ filter: (s) => !EXCLUDED_FILES.has(path.basename(s))
5960
+ });
5961
+ }
5962
+ /**
5963
+ * Copy the bridge bundle into the stable path via temp-write + atomic
5964
+ * rename. Returns true on update; false when the destination couldn't be
5965
+ * replaced but a usable stable bridge already exists (e.g. Windows
5966
+ * EBUSY because a bridge process holds the old file — acceptable, the
5967
+ * old bridge is the version about to be reloaded). Throws only when there
5968
+ * is no usable bridge at all.
5969
+ */
5970
+ function tryMaterializeBridge(srcBridge, destBridge) {
5971
+ mkdirSync(path.dirname(destBridge), { recursive: true });
5972
+ const tmp = `${destBridge}.tmp-${process.pid}`;
5973
+ try {
5974
+ writeFileSync(tmp, readFileSync(srcBridge));
5975
+ renameSync(tmp, destBridge);
5976
+ return true;
5977
+ } catch (err) {
5978
+ try {
5979
+ rmSync(tmp, { force: true });
5980
+ } catch {}
5981
+ if (existsSync(destBridge)) {
5982
+ consola.debug("[browser-mcp] bridge update deferred (file in use?):", err);
5983
+ return false;
5984
+ }
5985
+ throw err;
5986
+ }
5987
+ }
5988
+ /**
5989
+ * Stamp the running proxy version into the materialized manifest — the
5990
+ * single place the version is set on launch. Returns true when the
5991
+ * manifest is in its intended end state (stamped, already-correct, or
5992
+ * deliberately left at the bundled stamp for a non-Chrome-compliant
5993
+ * version), false only when a read/write threw. Chrome requires
5994
+ * `manifest.version` to be 1-4 dot-separated integers, so a non-numeric
5995
+ * value (`"unknown"`, a prerelease/build semver) is left as the bundled
5996
+ * build-time stamp rather than written (which would make the unpacked
5997
+ * extension fail to load).
5998
+ */
5999
+ function stampVersion(destExtDir) {
6000
+ const version$2 = getPackageVersion();
6001
+ if (!/^\d{1,9}(\.\d{1,9}){0,3}$/.test(version$2)) return true;
6002
+ const manifestPath = path.join(destExtDir, "manifest.json");
6003
+ try {
6004
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
6005
+ if (manifest.version === version$2) return true;
6006
+ manifest.version = version$2;
6007
+ const tmp = `${manifestPath}.tmp-${process.pid}`;
6008
+ writeFileSync(tmp, `${JSON.stringify(manifest, null, 2)}\n`);
6009
+ renameSync(tmp, manifestPath);
6010
+ return true;
6011
+ } catch (err) {
6012
+ consola.debug("[browser-mcp] manifest version stamp failed:", err);
6013
+ return false;
6014
+ }
6015
+ }
6016
+
5831
6017
  //#endregion
5832
6018
  //#region src/lib/browser-mcp/install-check.ts
5833
6019
  function readBridgeDiscovery() {
@@ -5988,6 +6174,7 @@ async function ensureBridgeReady() {
5988
6174
  }
5989
6175
  async function _ensureBridgeReadyImpl() {
5990
6176
  __implInvocationsForTests++;
6177
+ await provisionBrowserAssets();
5991
6178
  const browsers = detectSupportedBrowsers();
5992
6179
  if (browsers.length === 0) return buildInstallRequired("no_supported_browser", []);
5993
6180
  if (!bridgeBundleExists()) return buildInstallRequired("bridge_bundle_missing", []);
@@ -17786,7 +17973,7 @@ function initProxyFromEnv() {
17786
17973
  //#endregion
17787
17974
  //#region package.json
17788
17975
  var name = "github-router";
17789
- var version$1 = "0.3.73";
17976
+ var version$1 = "0.3.74";
17790
17977
 
17791
17978
  //#endregion
17792
17979
  //#region src/lib/approval.ts
@@ -19905,6 +20092,7 @@ const claude = defineCommand({
19905
20092
  }
19906
20093
  }
19907
20094
  provisionAndIndexColbert();
20095
+ if (browserToolsEnabled()) provisionBrowserAssets().catch((err) => consola.debug("Browser extension provisioning failed:", err));
19908
20096
  const baseShutdown = async () => {
19909
20097
  await removeOwnClaudeConfigMirror();
19910
20098
  };
@@ -20017,6 +20205,7 @@ const codex = defineCommand({
20017
20205
  runSelfUpdate({ selfUpdate: args["self-update"] !== false });
20018
20206
  if (toolbeltEnabled()) provisionToolbelt().catch(() => {});
20019
20207
  provisionAndIndexColbert();
20208
+ if ((state.browseEnabled || process$1.env.GH_ROUTER_ENABLE_BROWSE === "1") && hasSupportedBrowserInstalled()) provisionBrowserAssets().catch((err) => consola.debug("Browser extension provisioning failed:", err));
20020
20209
  const usingDefault = !args.model;
20021
20210
  const requestedModel = args.model ?? DEFAULT_CODEX_MODEL;
20022
20211
  enableFileLogging();
@@ -20391,6 +20580,7 @@ const start = defineCommand({
20391
20580
  });
20392
20581
  runSelfUpdate({ selfUpdate: args["self-update"] !== false });
20393
20582
  provisionAndIndexColbert();
20583
+ if (browserToolsEnabled()) provisionBrowserAssets().catch((err) => consola.debug("Browser extension provisioning failed:", err));
20394
20584
  if (args.cc) generateClaudeCodeCommand(serverUrl, args.model);
20395
20585
  if (args.cx) generateCodexCommand(serverUrl, args.model);
20396
20586
  consola.box(`🌐 Usage Viewer: https://animeshkundu.github.io/github-router/dashboard.html?endpoint=${serverUrl}/usage`);