machine-bridge-mcp 0.9.0 → 0.11.0

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/docs/LOGGING.md CHANGED
@@ -78,10 +78,19 @@ The implementation omits:
78
78
  - file, patch, image, and temporary-file content;
79
79
  - OAuth request bodies;
80
80
  - connection passwords, daemon secrets, authorization codes, and access tokens;
81
- - registered resource values and source paths.
81
+ - registered resource values and source paths;
82
+ - browser pairing tokens, page URLs/source, DOM metadata, form values, uploaded file bytes, and screenshots;
83
+ - application names, Accessibility trees, selectors, and entered values;
84
+ - built-in instruction text, automatic project facts, package script names, and explicit instruction-file contents.
82
85
 
83
86
  Unexpected infrastructure failures are reduced to coarse error classes in normal logs. Client-facing tool errors may contain more detail according to the active path-display policy, but those details are not copied into operational logs.
84
87
 
88
+ ## Local automation events
89
+
90
+ Browser broker ownership, extension connection, and persistent unavailability are infrastructure state. Ordinary tab/source/action/form/upload/screenshot calls remain per-tool debug events and never log their arguments or results. Pairing tokens are excluded from all structured fields.
91
+
92
+ Application discovery and Accessibility operations follow the same rule: permission or runtime failures are classified, while app names, UI trees, selectors, and values are not operational log data.
93
+
85
94
  ## Bounding and redaction
86
95
 
87
96
  Messages, strings, object depth, object key counts, array item counts, and serialized field payloads are bounded. Control characters and Unicode display controls are neutralized. Fields with secret-like names, path-like keys, and known token formats are recursively redacted.
@@ -30,6 +30,52 @@ A brief relay interruption is retried automatically and is visible only with `--
30
30
 
31
31
  Use `--verbose` only when close codes, close reasons, heartbeat timeouts, and retry delays are needed for diagnosis. A close code of 1006 means the transport ended without a normal close handshake; it does not by itself identify the cause.
32
32
 
33
+ ## Browser extension setup and diagnosis
34
+
35
+ The full-profile daemon starts the loopback browser broker automatically. Install/pair the packaged extension once:
36
+
37
+ ```sh
38
+ machine-mcp browser setup
39
+ machine-mcp browser status
40
+ ```
41
+
42
+ `browser setup` prints the unpacked-extension path and opens the local pairing page. Load that directory from the Chromium extensions page with Developer mode enabled. The pairing page contains the token only in non-cacheable loopback HTML; the CLI and MCP result do not print it. A first pairing is automatic. When local pairing state was reset or moved, click the extension icon on the active pairing page to authorize replacing the extension's previous broker configuration.
43
+
44
+ Diagnosis:
45
+
46
+ | Result | Interpretation |
47
+ |---|---|
48
+ | broker not reachable | no full-profile runtime owns the machine broker, state is stale, or a local security product blocked loopback listening |
49
+ | broker running, extension disconnected | extension not loaded/enabled, pairing not completed, browser profile not running, or MV3 worker reconnect failed |
50
+ | tab list works but page action fails | restricted browser-internal page, inaccessible frame, stale selector, page navigation, enterprise policy, or page script interference |
51
+ | `frame_id` action fails | frame was replaced or is not accessible; inspect frames again |
52
+ | resource-backed upload fails | resource unavailable/too large, input is not `type=file`, page replaced the input, or the site rejects the file |
53
+
54
+ The machine-level broker permits multiple workspace daemons/stdio clients to share one extension. A second runtime becomes an authenticated broker client rather than selecting a new port or replacing the extension. Its owner-only `browser-bridge.json` pairing record is a recognized state-root entry, so validated uninstall can remove the complete Machine Bridge state root without treating the pairing record as unrelated data. Restarting every runtime is not normally required after adding a skill or changing a form page; capability and DOM discovery are live.
55
+
56
+ ## macOS application automation
57
+
58
+ Application UI inspection/actions require Accessibility permission for the Node/Machine Bridge process. Grant it in macOS Privacy & Security, then retry `inspect_local_application`. Opening applications does not require the same Accessibility authority. If an app changes UI hierarchy or localizes labels, inspect again and use role/identifier/index selectors instead of assuming a stale title.
59
+
60
+ ## Foreground startup, background service, and upgrades
61
+
62
+ `machine-mcp` is a foreground command. It remains attached to the terminal, defaults to `info` logging, and stops on `Ctrl+C`. `machine-mcp service start` launches the installed platform service in the background and returns to the shell; that service uses `warn` logging.
63
+
64
+ A global npm install changes the CLI files on disk but does not replace an already running Node process. On a normal foreground start, Machine Bridge checks whether its autostart service is active, requests a stop, waits up to 15 seconds for the old daemon lock to be released, and then continues with the newly installed version. New daemon locks record foreground/background mode and package version. If another foreground process owns the lock, Machine Bridge does not kill it or print a false readiness message; stop it with `Ctrl+C`. If a background takeover reaches its deadline, run:
65
+
66
+ ```sh
67
+ machine-mcp service stop
68
+ machine-mcp service status
69
+ machine-mcp --verbose
70
+ ```
71
+
72
+ Recommended upgrade:
73
+
74
+ ```sh
75
+ npm install -g --omit=optional --allow-scripts=esbuild,workerd,sharp,fsevents machine-bridge-mcp@latest
76
+ machine-mcp --verbose
77
+ ```
78
+
33
79
  ## Logs
34
80
 
35
81
  Remote autostart definitions prefer a stable PATH alias that resolves to the currently running Node executable and persist a sanitized absolute-only service `PATH` containing the Node/CLI directories, the installer's absolute PATH entries, and platform defaults. This avoids versioned Homebrew-style paths becoming invalid after upgrades and prevents launchd/systemd from falling back to a minimal system-only PATH. Re-run `machine-mcp service install` after changing Node installation families or PATH layout. A service-style `--daemon-only` start that finds the same workspace daemon already running is an idempotent no-op: it exits successfully without repeating warnings or readiness output; explicit policy/secret/change requests still report that changes were not applied. Autostart logs are stored under the state root in `logs/daemon.out.log` and `logs/daemon.err.log`. Files are owner-only where supported and tail-trimmed before daemon startup. On a logging-schema upgrade, bounded prior content is moved into `daemon.out.legacy.log` and `daemon.err.legacy.log`; use the active filenames when diagnosing current behavior.
@@ -131,6 +177,9 @@ Defense-in-depth limits include:
131
177
  - managed-job timeout: 1–3,600 seconds per step;
132
178
  - managed-job output: 64 KiB per stream and 256 KiB total captured across one job;
133
179
  - registered resources: 64, 1 MiB each, 8 MiB referenced per job;
180
+ - browser source: 4 MiB; browser WebSocket messages: 8 MiB; pending browser requests: 32 per runtime;
181
+ - browser forms: 200 fields, 128 KiB per text value, and 4 MiB aggregate text; upload: eight resources and 5 MiB total;
182
+ - application Accessibility inspection: 500 elements and depth 12; action text: 4,000 characters;
134
183
  - job-scoped temporary files: 16 files, 512 KiB total content.
135
184
 
136
185
  ## Upgrade behavior
package/docs/PRIVACY.md CHANGED
@@ -27,6 +27,14 @@ Machine-specific operational notes may be kept under the ignored `.project-local
27
27
 
28
28
  Ignored does not mean safe for secrets: do not store passwords, tokens, private keys, authorization URLs, or copied secret-bearing logs there. `.privacy-denylist` remains the dedicated local vocabulary gate.
29
29
 
30
+ ## Runtime instruction context
31
+
32
+ When automatic project context is enabled, Machine Bridge sends a small generated block through the same authorized MCP transport as other session instructions. It may contain the target path relative to the repository root, recognized project/build filenames, package-manager and lockfile names, package script names, runtime constraints, common documentation filenames, and CI workflow filenames.
33
+
34
+ The generator does not include package script bodies, dependency names or versions, source/document contents, environment values, absolute home paths, or command output. It executes nothing and writes no repository or user files. File and script names can still reveal project structure, so users who do not want that metadata to traverse a remote Worker/host can set `"automatic_project_context": false` in `~/.config/machine-bridge-mcp/agent.json`. Built-in instructions can be disabled separately with `"builtin_instructions": false`.
35
+
36
+ Neither generated nor explicit instruction content is written to ordinary operational logs.
37
+
30
38
  ## Review rules
31
39
 
32
40
  Before committing or publishing:
@@ -34,7 +42,8 @@ Before committing or publishing:
34
42
  - inspect the complete staged diff, including tests, snapshots, examples, release notes, and generated metadata;
35
43
  - use reserved example domains and neutral aliases;
36
44
  - run `npm run privacy:check`, `npm run check`, and `npm pack --dry-run`;
37
- - treat paths, host aliases, usernames, and codenames as private metadata even when they are not authentication secrets.
45
+ - treat paths, host aliases, usernames, codenames, real browser URLs/page captures, application names tied to a user, and form data as private metadata even when they are not authentication secrets;
46
+ - keep browser pairing-state files and captured source/screenshots out of fixtures, documentation, support logs, and release assets.
38
47
 
39
48
  The scanner is heuristic. It cannot identify every personal or organizational name, transformed value, image, archive, binary fixture, or data already present in Git history.
40
49
 
package/docs/RELEASING.md CHANGED
@@ -6,7 +6,7 @@ The release invariant is:
6
6
  - `v<package version>` points to that same commit locally and on GitHub.
7
7
  - A final GitHub Release exists for the tag.
8
8
  - The GitHub Release contains the npm tarball generated from that commit.
9
- - `package.json`, `package-lock.json`, and the Worker-reported version agree.
9
+ - `package.json`, `package-lock.json`, the Worker-reported version, and `browser-extension/manifest.json` (`version`/`version_name`) agree.
10
10
  - Every release-relevant change since the prior version tag has a higher package version and matching CHANGELOG section.
11
11
  - The same reviewed change is present on GitHub and in a new npm version.
12
12
 
@@ -14,7 +14,7 @@ The release invariant is:
14
14
 
15
15
  ## Prepare a version
16
16
 
17
- 1. Set the new version without creating an automatic npm tag:
17
+ 1. Set the new version without creating an automatic npm tag. The npm version hook synchronizes the Worker and packaged browser-extension versions:
18
18
 
19
19
  ```sh
20
20
  npm version <version> --no-git-tag-version
package/docs/TESTING.md CHANGED
@@ -17,7 +17,9 @@ The suite includes:
17
17
  - generated Cloudflare Worker types and strict TypeScript checking, including unused-local and unused-parameter rejection;
18
18
  - syntax validation for every shipped JavaScript entry point;
19
19
  - shared tool-catalog schema, annotation, and profile-inventory checks;
20
- - hierarchical agent-context precedence, custom instruction ordering, local skill discovery/loading, command override/removal, direct argv argument handling, timeout ceilings, and execution-profile denial;
20
+ - default working-agreement injection without user files, bounded automatic project metadata, script-body non-disclosure, user-global opt-out, repository opt-out rejection, global `model_instructions_file` injection in stdio/remote initialization, hierarchical precedence, live project/skill rescanning and fingerprints, automatic task ranking/loading, command override/removal, direct argv handling, timeout ceilings, and execution-profile denial;
21
+ - foreground takeover of an asynchronously stopping background daemon, bounded timeout guidance, daemon lock mode/version metadata, and silent idempotent duplicate service starts;
22
+ - machine-level browser-broker ownership/client proxying, authenticated extension origin/subprotocol, non-cacheable local pairing, pairing-token non-disclosure, resource-backed upload routing, and broker result redaction;
21
23
  - canonical path and symbolic-link escape tests;
22
24
  - relative-path privacy and error-path redaction tests;
23
25
  - atomic create/update, optimistic hash, exact edit, and patch transaction tests;
@@ -42,8 +44,8 @@ The suite includes:
42
44
  - deterministic relay connection lifecycle coverage for transport construction/error/deadline, pre-handshake `welcome` validation, authenticated `hello_ack` readiness, identity/version mismatch, retryable Worker handshake errors, fatal protocol errors, autonomous outage-reminder backoff, handshake and heartbeat timeout, brief-outage suppression, sustained-outage escalation, recovery summaries, and supersession;
43
45
  - shared no-follow bounded-file reads for normal files, over-limit data, directories, and symbolic links;
44
46
  - CLI parsing, policy profiles, and client configuration boundaries;
45
- - live stdio MCP initialization, discovery, calls, rich content, sessions, cancellation, managed-job acceptance, and a detached job/finally phase that survives stdio shutdown;
46
- - live local Worker OAuth registration, consent, PKCE, token replay rejection, throttling, CORS, protocol negotiation, dynamic tool advertisement, rich content, daemon replacement, cancellation, malformed daemon JSON/non-object rejection, duplicate hello rejection, and unknown-message closure.
47
+ - live stdio MCP initialization with session instructions, capability resolution, discovery, calls, rich content, sessions, cancellation, managed-job acceptance, and a detached job/finally phase that survives stdio shutdown;
48
+ - live local Worker OAuth registration, consent, PKCE, token replay rejection, throttling, CORS, protocol negotiation, daemon-backed session bootstrap, dynamic tool advertisement, rich content, daemon replacement, cancellation, malformed daemon JSON/non-object rejection, duplicate hello rejection, and unknown-message closure.
47
49
 
48
50
  ## Additional release checks
49
51
 
@@ -63,7 +65,7 @@ GitHub Actions executes the main suite on Linux, macOS, and Windows using the pi
63
65
  ## Test design rules
64
66
 
65
67
  - Tests should exercise the public boundary rather than only helper functions when practical.
66
- - Every permission-expanding feature needs a denial test.
68
+ - Every permission-expanding feature needs a denial test. Browser/application features also require a token/value/content non-disclosure assertion.
67
69
  - Every bounded resource needs an over-limit test.
68
70
  - Every multi-stage mutation needs a no-partial-commit test.
69
71
  - Every remote call correlation change needs daemon replacement and cancellation coverage.
@@ -78,7 +80,7 @@ Run `npm run privacy:check` before committing and before packaging. Developers s
78
80
 
79
81
  ## Package manifest
80
82
 
81
- `npm run package:test` executes a real silent `npm pack --dry-run --json`, requires clean parseable JSON, rejects sensitive local artifacts and credential-like file classes, and verifies that privacy/engineering guidance, the runtime/relay/secure-file modules, contribution discipline, and privacy/release-impact checkers are present in the published package. `npm run install:test` packs the real tarball, installs it into an isolated global prefix with the documented npm 12 options, rejects blocked-script warnings, confirms optional `fsevents` is absent, and runs the installed CLI.
83
+ `npm run package:test` executes a real silent `npm pack --dry-run --json`, requires clean parseable JSON, rejects sensitive local artifacts and credential-like file classes, and verifies that privacy/engineering guidance, the runtime/relay/secure-file/browser/app modules, packaged browser extension, contribution discipline, and privacy/release-impact checkers are present in the published package. `npm run install:test` packs the real tarball, installs it into an isolated global prefix with the documented npm 12 options, rejects blocked-script warnings, confirms optional `fsevents` is absent, and runs the installed CLI.
82
84
 
83
85
  The stdio integration test also sends an oversized line, verifies bounded rejection, and confirms that the next valid request is still processed.
84
86
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "machine-bridge-mcp",
3
- "version": "0.9.0",
4
- "description": "Cross-client MCP bridge for canonical local automation, files, Git, SSH resources, managed jobs, and processes over stdio or an OAuth-protected remote relay.",
3
+ "version": "0.11.0",
4
+ "description": "Cross-client MCP bridge for local agent context, structured browser and application automation, files, Git, processes, resources, and durable jobs over stdio or OAuth relay.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "engines": {
@@ -16,6 +16,7 @@
16
16
  "mbm",
17
17
  "mbm.cmd",
18
18
  "bin",
19
+ "browser-extension",
19
20
  "src/local",
20
21
  "src/shared",
21
22
  "src/worker/index.ts",
@@ -38,13 +39,13 @@
38
39
  "self-test": "node tests/local-self-test.mjs",
39
40
  "version:sync": "node scripts/sync-worker-version.mjs",
40
41
  "version:check": "node scripts/sync-worker-version.mjs --check",
41
- "version": "node scripts/sync-worker-version.mjs && git add package.json package-lock.json src/worker/index.ts",
42
+ "version": "node scripts/sync-worker-version.mjs && git add package.json package-lock.json src/worker/index.ts browser-extension/manifest.json",
42
43
  "prepack": "npm run version:check && npm run privacy:check && npm run release-impact:check",
43
44
  "prepublishOnly": "npm run check && npm run version:check && npm run release:check",
44
45
  "worker:types": "wrangler types src/worker/worker-configuration.d.ts",
45
46
  "typecheck": "npm run worker:types && tsc -p tsconfig.json --noEmit",
46
- "syntax": "sh -n mbm && node --check bin/machine-mcp.mjs && node --check src/local/cli.mjs && node --check src/local/runtime.mjs && node --check src/local/agent-context.mjs && node --check src/local/relay-connection.mjs && node --check src/local/secure-file.mjs && node --check src/local/patch.mjs && node --check src/local/process-sessions.mjs && node --check src/local/tools.mjs && node --check src/local/stdio.mjs && node --check src/local/state.mjs && node --check src/local/shell.mjs && node --check src/local/service.mjs && node --check src/local/log.mjs && node --check src/local/atomic-fs.mjs && node --check src/local/ssh-key.mjs && node --check src/local/resource-operations.mjs && node --check src/local/full-access-test.mjs && node --check src/local/managed-jobs.mjs && node --check src/local/job-runner.mjs && node --check scripts/sync-worker-version.mjs && node --check scripts/privacy-check.mjs && node --check scripts/release-impact-check.mjs && node --check scripts/network-retry.mjs && node --check scripts/release-state.mjs && node --check scripts/github-release.mjs && node --check tests/worker-integration-test.mjs && node --check tests/stdio-integration-test.mjs && node --check tests/catalog-test.mjs && node --check tests/agent-context-test.mjs && node --check tests/local-self-test.mjs && node --check tests/runtime-self-test.mjs && node --check tests/managed-jobs-test.mjs && node --check tests/ssh-key-test.mjs && node --check tests/full-access-test.mjs && node --check tests/atomic-fs-test.mjs && node --check tests/package-test.mjs && node --check tests/release-impact-test.mjs && node --check tests/release-state-test.mjs && node --check tests/privacy-test.mjs && node --check tests/network-retry-test.mjs && node --check tests/relay-connection-test.mjs && node --check tests/install-smoke-test.mjs && node --check tests/secure-file-test.mjs && node --check tests/architecture-test.mjs",
47
- "check": "npm run privacy:check && npm run privacy:test && npm run release-impact:check && npm run release-impact:test && npm run release-state:test && npm run network-retry:test && npm run relay:test && npm run secure-file:test && npm run architecture:test && npm run typecheck && npm run syntax && npm run catalog:test && npm run agent-context:test && npm run self-test && npm run atomic-fs:test && npm run package:test && npm run ssh-key:test && npm run full-access:test && npm run managed-jobs:test && npm run stdio:integration-test && npm run worker:integration-test",
47
+ "syntax": "sh -n mbm && node --check bin/machine-mcp.mjs && node --check src/local/cli.mjs && node --check src/local/runtime.mjs && node --check src/local/agent-context.mjs && node --check src/local/default-instructions.mjs && node --check src/local/browser-bridge.mjs && node --check src/local/app-automation.mjs && node --check src/local/relay-connection.mjs && node --check src/local/secure-file.mjs && node --check src/local/patch.mjs && node --check src/local/process-sessions.mjs && node --check src/local/tools.mjs && node --check src/local/stdio.mjs && node --check src/local/state.mjs && node --check src/local/shell.mjs && node --check src/local/service.mjs && node --check src/local/log.mjs && node --check src/local/atomic-fs.mjs && node --check src/local/ssh-key.mjs && node --check src/local/resource-operations.mjs && node --check src/local/full-access-test.mjs && node --check src/local/managed-jobs.mjs && node --check src/local/job-runner.mjs && node --check scripts/sync-worker-version.mjs && node --check scripts/privacy-check.mjs && node --check scripts/release-impact-check.mjs && node --check scripts/network-retry.mjs && node --check scripts/release-state.mjs && node --check scripts/github-release.mjs && node --check tests/worker-integration-test.mjs && node --check tests/stdio-integration-test.mjs && node --check tests/catalog-test.mjs && node --check tests/agent-context-test.mjs && node --check tests/browser-bridge-test.mjs && node --check tests/app-automation-test.mjs && node --check tests/local-self-test.mjs && node --check tests/runtime-self-test.mjs && node --check tests/managed-jobs-test.mjs && node --check tests/ssh-key-test.mjs && node --check tests/full-access-test.mjs && node --check tests/atomic-fs-test.mjs && node --check tests/package-test.mjs && node --check tests/release-impact-test.mjs && node --check tests/release-state-test.mjs && node --check tests/privacy-test.mjs && node --check tests/network-retry-test.mjs && node --check tests/relay-connection-test.mjs && node --check tests/install-smoke-test.mjs && node --check tests/secure-file-test.mjs && node --check tests/architecture-test.mjs && node --check browser-extension/service-worker.js && node --check browser-extension/pairing.js && node --check browser-extension/page-automation.js",
48
+ "check": "npm run privacy:check && npm run privacy:test && npm run release-impact:check && npm run release-impact:test && npm run release-state:test && npm run network-retry:test && npm run relay:test && npm run secure-file:test && npm run architecture:test && npm run typecheck && npm run syntax && npm run catalog:test && npm run agent-context:test && npm run browser-bridge:test && npm run app-automation:test && npm run self-test && npm run atomic-fs:test && npm run package:test && npm run ssh-key:test && npm run full-access:test && npm run managed-jobs:test && npm run stdio:integration-test && npm run worker:integration-test",
48
49
  "worker:dry-run": "wrangler deploy --dry-run",
49
50
  "worker:integration-test": "node tests/worker-integration-test.mjs",
50
51
  "release:check": "node scripts/github-release.mjs --check",
@@ -67,7 +68,9 @@
67
68
  "secure-file:test": "node tests/secure-file-test.mjs",
68
69
  "architecture:test": "node tests/architecture-test.mjs",
69
70
  "release-state:test": "node tests/release-state-test.mjs",
70
- "agent-context:test": "node tests/agent-context-test.mjs"
71
+ "agent-context:test": "node tests/agent-context-test.mjs",
72
+ "browser-bridge:test": "node tests/browser-bridge-test.mjs",
73
+ "app-automation:test": "node tests/app-automation-test.mjs"
71
74
  },
72
75
  "dependencies": {
73
76
  "wrangler": "4.110.0",
@@ -90,7 +93,10 @@
90
93
  "cross-platform",
91
94
  "ssh",
92
95
  "automation",
93
- "managed-jobs"
96
+ "managed-jobs",
97
+ "browser-automation",
98
+ "desktop-automation",
99
+ "agent-skills"
94
100
  ],
95
101
  "repository": {
96
102
  "type": "git",
@@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url";
7
7
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8
8
  const packagePath = path.join(repoRoot, "package.json");
9
9
  const workerPath = path.join(repoRoot, "src", "worker", "index.ts");
10
+ const extensionManifestPath = path.join(repoRoot, "browser-extension", "manifest.json");
10
11
  const args = new Set(process.argv.slice(2));
11
12
  const checkOnly = args.has("--check");
12
13
  const log = message => process.stderr.write(`${message}\n`);
@@ -17,24 +18,38 @@ if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(expected)) {
17
18
  fail(`package.json version is not a valid release version: ${JSON.stringify(expected)}`);
18
19
  }
19
20
 
20
- const source = readFileSync(workerPath, "utf8");
21
- const pattern = /const SERVER_VERSION = "([^"]+)";/;
22
- const match = source.match(pattern);
23
- if (!match) fail("Could not find `const SERVER_VERSION = \"...\";` in src/worker/index.ts");
21
+ const workerSource = readFileSync(workerPath, "utf8");
22
+ const workerPattern = /const SERVER_VERSION = "([^"]+)";/;
23
+ const workerMatch = workerSource.match(workerPattern);
24
+ if (!workerMatch) fail("Could not find `const SERVER_VERSION = \"...\";` in src/worker/index.ts");
24
25
 
25
- const current = match[1];
26
- if (current === expected) {
27
- log(`Worker version is in sync: ${expected}`);
28
- process.exit(0);
26
+ const extension = JSON.parse(readFileSync(extensionManifestPath, "utf8"));
27
+ const extensionVersion = expected.split(/[+-]/, 1)[0];
28
+ const extensionParts = extensionVersion.split(".").map((value) => Number(value));
29
+ if (extensionParts.length < 1 || extensionParts.length > 4 || extensionParts.some((value) => !Number.isInteger(value) || value < 0 || value > 65535)) {
30
+ fail(`package version cannot be represented as a Chromium extension version: ${expected}`);
29
31
  }
32
+ const mismatches = [];
33
+ if (workerMatch[1] !== expected) mismatches.push(`Worker=${workerMatch[1]}`);
34
+ if (String(extension.version || "") !== extensionVersion) mismatches.push(`browser-extension.version=${extension.version || "<missing>"}`);
35
+ if (String(extension.version_name || "") !== expected) mismatches.push(`browser-extension.version_name=${extension.version_name || "<missing>"}`);
30
36
 
37
+ if (!mismatches.length) {
38
+ log(`Runtime versions are in sync: ${expected}`);
39
+ process.exit(0);
40
+ }
31
41
  if (checkOnly) {
32
- fail(`Worker version mismatch: package.json=${expected}, src/worker/index.ts=${current}. Run npm run version:sync.`);
42
+ fail(`Version mismatch: package.json=${expected}; ${mismatches.join(", ")}. Run npm run version:sync.`);
33
43
  }
34
44
 
35
- const updated = source.replace(pattern, `const SERVER_VERSION = "${expected}";`);
36
- writeFileSync(workerPath, updated);
37
- log(`Updated Worker version: ${current} -> ${expected}`);
45
+ if (workerMatch[1] !== expected) {
46
+ writeFileSync(workerPath, workerSource.replace(workerPattern, `const SERVER_VERSION = "${expected}";`));
47
+ log(`Updated Worker version: ${workerMatch[1]} -> ${expected}`);
48
+ }
49
+ extension.version = extensionVersion;
50
+ extension.version_name = expected;
51
+ writeFileSync(extensionManifestPath, `${JSON.stringify(extension, null, 2)}\n`);
52
+ log(`Updated browser extension version: ${extensionVersion} (${expected})`);
38
53
 
39
54
  function fail(message) {
40
55
  console.error(message);