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/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.0 - 2026-07-12
4
+
5
+ ### Zero-configuration agent working agreements
6
+
7
+ - Add a package-controlled `machine-bridge://defaults/working-agreements` instruction layer so every stdio and remote MCP session starts with conservative, auditable guidance even when the user and repository provide no `MODEL.md`, `AGENTS.md`, or manifest. The baseline covers inspect-before-editing, minimal coherent changes, preservation of unrelated work, existing-toolchain reuse, tests/documentation synchronization, honest validation reporting, secret handling, safe Git practice, and explicit authorization for publication, deployment, credential rotation, live-data mutation, system-wide installation, and destructive operations.
8
+ - Keep the baseline lower precedence than explicit user and root-to-target project instructions. Expose its source, size, hash, precedence, and optional content through `session_bootstrap`, `agent_context`, and `resolve_task_capabilities`; include its hash in live capability fingerprints. The defaults are behavioral guidance rather than a replacement for Machine Bridge policy, host approvals, operating-system permissions, hooks, sandboxes, or external isolation.
9
+
10
+ ### Bounded automatic project context
11
+
12
+ - Add `machine-bridge://project-context/current`, regenerated on every relevant context scan from bounded repository metadata: target-relative path, recognized project/build entry files, JavaScript package-manager and lockfile facts, package script names, runtime constraints/version hints, common documentation files, and CI entrypoint filenames. The scanner executes nothing, writes no user or repository files, omits script bodies, dependency values, source/document contents, absolute home paths, and command output, and never claims a declared command was validated.
13
+ - Harden repository-controlled metadata with strict character/count/byte limits, no-follow regular-file reads, safe workflow-name filtering, independent 16 KiB output bounds, and conservative skipping of symbolic links, invalid UTF-8, oversized files, permission failures, and transient metadata races. Add user-global `builtin_instructions` and `automatic_project_context` boolean opt-outs; project manifests cannot disable the user's baseline controls.
14
+
15
+ ### Documentation and verification
16
+
17
+ - Document the default instruction model, precedence, opt-outs, privacy data surface, security boundary, recommended `AGENTS.md` content, progressive disclosure, and official cross-agent best-practice sources. Update repository automation guidance, shared MCP tool descriptions, initialization instructions, architecture, clients, logging, privacy, security, testing, and package contents.
18
+ - Add regression coverage for no-file defaults, initialization injection over stdio, automatic project refresh/fingerprints, package-manager/lockfile/script/CI discovery, script-body non-disclosure, hostile metadata filtering, global opt-out, project opt-out rejection, precedence, and packaged-module presence.
19
+
20
+ ## 0.10.1 - 2026-07-12
21
+
22
+ ### Foreground startup and upgrade takeover
23
+
24
+ - Make a normal `machine-mcp` start reliably take over from an active platform autostart daemon: detect whether the service is active, request shutdown, wait up to 15 seconds for the workspace daemon lock to be released, and then continue foreground startup with the newly installed CLI. A failed bounded takeover now exits with explicit `service stop`/`service status` recovery guidance instead of silently leaving the old process in place.
25
+ - Record foreground/background mode and package version in new daemon locks. A genuine lock conflict now identifies the running mode/version when known, explains how to stop it, and no longer prints the misleading `[ok] ready` block when no restart or requested change occurred. Service-style duplicate starts remain silent idempotent successes.
26
+
27
+ ### Global instructions and operator guidance
28
+
29
+ - Add copy-paste setup instructions for `~/.config/machine-bridge-mcp/agent.json` and the global `MODEL.md`, explain global-versus-project precedence, live rescanning, and when a new MCP conversation/reconnection is needed for initialization-time injection.
30
+ - Document the foreground/background distinction, safe global upgrade sequence, owner-only instruction-file permissions, background log locations, and the exact optional-dependency install command that avoids the development-only `fsevents` warning.
31
+
32
+ ## 0.10.0 - 2026-07-12
33
+
34
+ ### Session context and capability selection
35
+
36
+ - Add a global `model_instructions_file` in `~/.config/machine-bridge-mcp/agent.json`. Stdio and remote MCP initialization append the bounded user-designated instructions when the local runtime is reachable; `session_bootstrap` exposes the same content explicitly. Project manifests cannot override the global file.
37
+ - Add `resolve_task_capabilities`, which rescans effective instructions, filesystem skills, and registered commands for every task, returns a refresh fingerprint, ranks relevant capabilities, optionally loads the best skill, and recommends application/browser/file/Git/process tools. Newly added or edited skills are visible without daemon restart or dynamic MCP tool registration. Multilingual tokenization avoids weak single-character Chinese overlap when automatically selecting a skill.
38
+ - Preserve the host boundary: Machine Bridge automates discovery, refresh, ranking, and progressive loading, while the ChatGPT/MCP host still decides whether a tool is exposed, approved, or invoked.
39
+
40
+ ### Existing-profile browser and local applications
41
+
42
+ - Add a packaged Manifest V3 Chromium extension and authenticated loopback machine broker for the user's existing browser profile, windows, tabs, cookies, extensions, and login state. Multiple workspace or stdio runtimes share one extension connection through authenticated broker clients.
43
+ - Add structured browser tools for tab listing, current DOM source, frame-aware and open-Shadow-DOM interactive-element inspection, navigation/actions, complex multi-field forms, visible screenshots, and resource-backed file inputs. Page operations run through a fixed packaged module injected into the target frame rather than caller-provided or service-worker-closure code. Registered local resources can provide sensitive field text or upload bytes without returning those values through MCP results.
44
+ - Add installed-application discovery/opening and structured macOS Accessibility inspection/actions. The implementation uses fixed JXA code and does not accept caller-supplied JavaScript, AppleScript, JXA, or browser-extension source. Application paths are normalized to process names, selector indices apply to filtered matches, Linux desktop launchers use `gio launch`, Windows discovery prioritizes Start Menu launchers, and secure Accessibility fields never return values.
45
+ - Add `machine-mcp browser status|setup|pair|path` for one-time unpacked-extension setup and diagnosis. Pairing tokens remain in owner-only state and non-cacheable loopback HTML rather than MCP output, browser URL fragments, or operational logs. Established pairing is replacement-locked and requires a browser-action click on the active local pairing page before switching broker state. The extension badge reports connection state and its action opens the saved pairing page; signed browser-store packaging is the intended mass-market distribution path.
46
+
47
+ ### Architecture, security, and verification
48
+
49
+ - Split agent context, application automation, and browser broker into dedicated local domain modules while retaining one static catalog shared by Worker and stdio. Bound Worker initialization bootstrap to a short failure-tolerant daemon call.
50
+ - Add loopback Host validation, extension-origin checks, authenticated extension/runtime WebSocket subprotocols, bounded messages/concurrency/source/forms/uploads, proxy timeout/cancellation cleanup, restricted-page handling, secret/value/result non-disclosure, and full-profile gating. Treat the owner-only browser pairing file as a recognized state-root entry so safe uninstall remains available.
51
+ - Extend version synchronization to the browser-extension manifest and add broker owner/client proxy, pairing-token, resource-upload, stdio initialization, live skill refresh, and remote initialization regression coverage. Update architecture, security, operations, logging, privacy, testing, client, release, and user documentation.
52
+
3
53
  ## 0.9.0 - 2026-07-12
4
54
 
5
55
  ### Agent context and local workflows
package/README.md CHANGED
@@ -51,7 +51,7 @@ Node.js 26 or newer and npm 12 or newer are required. The repository pins the ac
51
51
  npm install -g --omit=optional --allow-scripts=esbuild,workerd,sharp,fsevents machine-bridge-mcp@latest
52
52
  ```
53
53
 
54
- Recent npm releases may otherwise warn that Wrangler's native dependencies (`esbuild`, `workerd`, and `sharp`) have install scripts awaiting approval. The scoped command approves the reviewed native script names that npm 12 evaluates during global resolution while `--omit=optional` keeps optional `fsevents` out of the installed runtime. `fsevents` is used for development-time filesystem watching rather than Machine Bridge runtime or deployment. It does not change the user's global npm policy. `machine-mcp doctor` remains the authoritative runtime check.
54
+ Recent npm releases may otherwise warn that Wrangler's native dependencies (`esbuild`, `workerd`, and `sharp`) have install scripts awaiting approval. The scoped command approves the reviewed native script names that npm 12 evaluates during global resolution while `--omit=optional` keeps optional `fsevents` out of the installed runtime. `fsevents` is used for development-time filesystem watching rather than Machine Bridge runtime or deployment. Omitting `--omit=optional` can therefore produce a harmless blocked-script warning for `fsevents@2.3.3`; use the documented command rather than changing global npm policy. `machine-mcp doctor` remains the authoritative runtime check.
55
55
 
56
56
  From a source checkout, the checked-in exact-version `allowScripts` policy approves the reviewed native dependencies:
57
57
 
@@ -79,6 +79,17 @@ On first remote start, the CLI:
79
79
  6. starts an outbound-only daemon connection;
80
80
  7. prints the Remote MCP URL and connection password.
81
81
 
82
+ A normal `machine-mcp` invocation is a foreground start: it remains attached to the terminal and prints `info` logs. If an older autostart daemon is active after a global package upgrade, the CLI stops that service, waits up to 15 seconds for its workspace lock, and then starts the newly installed version in the foreground. A genuine foreground conflict is left untouched and reported with actionable guidance. To run only in the background, use `machine-mcp service start`; inspect its owner-only logs under `~/.local/state/machine-bridge-mcp/logs/`.
83
+
84
+ Recommended upgrade sequence:
85
+
86
+ ```sh
87
+ npm install -g --omit=optional --allow-scripts=esbuild,workerd,sharp,fsevents machine-bridge-mcp@latest
88
+ machine-mcp --verbose
89
+ ```
90
+
91
+ The global install replaces files on disk but cannot hot-reload an already running Node process; the second command performs the bounded service takeover. If takeover fails, run `machine-mcp service stop`, confirm with `machine-mcp service status`, and retry.
92
+
82
93
  Use the printed values in the MCP client:
83
94
 
84
95
  ```text
@@ -108,36 +119,58 @@ machine-mcp stdio --workspace /path/to/project
108
119
 
109
120
  The stdio server writes only JSON-RPC messages to stdout and operational logs to stderr. See [docs/CLIENTS.md](docs/CLIENTS.md) for the host/model distinction and transport trade-offs.
110
121
 
111
- ## Agent instructions, local skills, and command registry
122
+ ## Session instructions, local skills, commands, apps, and browser
123
+
124
+ Machine Bridge now starts with useful agent guidance even when the user has not created `MODEL.md`, `AGENTS.md`, or `.machine-bridge/agent.json`.
112
125
 
113
- For substantive workspace tasks, clients can call `agent_context` for the relevant file or directory. Its default instruction discovery mirrors Codex: under unrestricted policy it first checks `CODEX_HOME` or `~/.codex`, then walks from the project root to the target directory, selecting the first non-empty `AGENTS.override.md` or `AGENTS.md` in each scope. Deeper guidance has higher precedence, and the default combined budget is 32 KiB.
126
+ Two lower-precedence virtual sources are generated in memory:
114
127
 
115
- Skill discovery also follows Codex-style progressive disclosure. By default, Machine Bridge scans `.agents/skills` from the target directory through the project root; unrestricted policy additionally enables `~/.agents/skills` and, on Unix-like systems, `/etc/codex/skills`. `agent_context` initially returns bounded metadata, while `load_local_skill` returns the selected `SKILL.md` and file inventory without executing anything implicitly.
128
+ - `machine-bridge://defaults/working-agreements` supplies conservative cross-project rules for inspection, scoped changes, preservation of unrelated work, validation, security, Git discipline, and explicit authorization for deployment/publication/destructive operations.
129
+ - `machine-bridge://project-context/current` derives bounded facts from the active repository: target path, common project/build files, package-manager declarations and lockfiles, package script names, runtime constraints, documentation files, and CI entrypoints. It never injects package-script bodies or source contents and does not claim commands were validated.
116
130
 
117
- A project can add custom instruction candidates, explicit skill roots, and registered commands with `.machine-bridge/agent.json`:
131
+ No files are created or modified by this bootstrap. Explicit user and repository instructions load later and therefore override the defaults. `session_bootstrap` supplies the chain during MCP initialization; `agent_context` exposes every source and hash; `resolve_task_capabilities` refreshes project facts, instructions, skills, and commands for the current task.
132
+
133
+ Optional user-global preferences still live in `~/.config/machine-bridge-mcp/agent.json`:
118
134
 
119
135
  ```json
120
136
  {
121
137
  "version": 1,
122
- "instruction_files": [
123
- "PROJECT.override.md",
124
- "AGENTS.override.md",
125
- "AGENTS.md"
126
- ],
127
- "instruction_max_bytes": 65536,
128
- "commands": {
129
- "check": {
130
- "description": "Run repository validation.",
131
- "argv": ["npm", "run", "check"],
132
- "cwd": ".",
133
- "timeout_seconds": 600,
134
- "allow_extra_args": false
135
- }
136
- }
138
+ "model_instructions_file": "~/.config/machine-bridge-mcp/MODEL.md"
137
139
  }
138
140
  ```
139
141
 
140
- `instruction_files` is a priority list: each directory contributes only its first non-empty candidate. Deeper manifests override earlier settings and can remove an inherited command with `null`. `run_local_command` executes registered commands as direct argv processes without shell parsing and is available only when direct execution is enabled. See [Agent context, local skills, and registered commands](docs/AGENT_CONTEXT.md) for the complete contract.
142
+ Use `MODEL.md` for preferences not covered by the baseline, such as default language or organization-specific review rules. Repository-specific rules belong in `AGENTS.md`/`AGENTS.override.md`; deeper files take precedence. Keep secrets out of every instruction file.
143
+
144
+ To disable either automatic layer globally:
145
+
146
+ ```json
147
+ {
148
+ "version": 1,
149
+ "builtin_instructions": false,
150
+ "automatic_project_context": false
151
+ }
152
+ ```
153
+
154
+ Repositories cannot disable these user-level controls. Editing instruction files or project metadata does not require a daemon restart; start a new MCP conversation or reconnect when initialization-time injection must be guaranteed from the beginning.
155
+
156
+ Skill discovery follows Codex-style progressive disclosure. Default roots are target-to-project `.agents/skills`; unrestricted policy also enables user/admin roots. Newly added or edited skills are found on the next resolver/list call without restarting the daemon. A project can customize instruction candidates, skill roots, and direct-argv registered commands with `.machine-bridge/agent.json`. See [Session instructions, defaults, skills, commands, and capability discovery](docs/AGENT_CONTEXT.md).
157
+
158
+ Under canonical `full`, Machine Bridge also exposes structured local automation:
159
+
160
+ - installed application discovery/opening and macOS Accessibility inspection/actions;
161
+ - a packaged Chromium extension that controls the user's existing daily browser profile, active tabs, login state, and windows;
162
+ - current DOM source and frame/open-Shadow-DOM inspection, structured page actions, complex multi-field forms, resource-backed secret fields, resource-backed file uploads, and screenshots.
163
+
164
+ One-time browser setup:
165
+
166
+ ```sh
167
+ machine-mcp browser setup
168
+ machine-mcp browser status
169
+ ```
170
+
171
+ Load the printed unpacked-extension directory once in Chrome, Edge, Brave, Vivaldi, or another compatible Chromium browser. The extension badge reports connection state, and clicking it opens the saved local pairing page. The local pairing token remains in owner-only state and the loopback pairing page; it is not returned through MCP. For a mass-market release, distribute the same extension as a signed browser-store build rather than asking end users to enable Developer mode. See [Local application and browser automation](docs/LOCAL_AUTOMATION.md).
172
+
173
+ Machine Bridge can discover, refresh, rank, and load capabilities automatically. The ChatGPT/MCP host still owns tool selection and approval, so the server cannot force a host to expose or invoke a recommended skill, command, app, or browser operation.
141
174
 
142
175
  ## Policy controls
143
176
 
@@ -264,7 +297,9 @@ The exact `tools/list` response reflects the active local policy. Definitions co
264
297
 
265
298
  - `server_info`
266
299
  - `project_overview`
267
- - `agent_context` — effective instructions, skill summaries, and registered commands for a target path
300
+ - `session_bootstrap` — built-in defaults, automatic project facts, explicit instructions, and refresh metadata
301
+ - `agent_context` — complete default/explicit instruction precedence, skill summaries, and registered commands for a target path
302
+ - `resolve_task_capabilities` — live skill/command ranking and local automation recommendations
268
303
  - `list_local_skills`
269
304
  - `load_local_skill` — load instructions and file inventory without implicit execution
270
305
  - `list_local_commands`
@@ -275,6 +310,23 @@ The exact `tools/list` response reflects the active local policy. Definitions co
275
310
  - `view_image` — bounded PNG, JPEG, GIF, or WebP as native MCP image content
276
311
  - `search_text`
277
312
 
313
+ ### Local applications and browser (`full`)
314
+
315
+ - `list_local_applications`
316
+ - `open_local_application`
317
+ - `inspect_local_application` — bounded macOS Accessibility tree
318
+ - `operate_local_application` — structured Accessibility action, no arbitrary script source
319
+ - `browser_status`
320
+ - `pair_browser_extension`
321
+ - `browser_list_tabs`
322
+ - `browser_get_source` — bounded current DOM HTML, including selected frames
323
+ - `browser_inspect_page`
324
+ - `browser_action`
325
+ - `browser_fill_form`
326
+ - `browser_upload_files` — registered local resources to file inputs
327
+ - `browser_screenshot`
328
+
329
+
278
330
  ### Mutation
279
331
 
280
332
  - `write_file` — atomic whole-file write with create-only and SHA-256 checks
@@ -335,6 +387,7 @@ machine-mcp status
335
387
  machine-mcp doctor
336
388
  machine-mcp rotate-secrets
337
389
  machine-mcp resource add|list|check|remove
390
+ machine-mcp browser status|setup|pair|path
338
391
  machine-mcp job submit|inspect|approve|list|read|cancel
339
392
  machine-mcp --print-mcp-credentials
340
393
  machine-mcp uninstall [--keep-worker] [--yes]
@@ -386,7 +439,7 @@ npm pack --dry-run
386
439
 
387
440
  `npm run check` covers privacy and release-impact gates, architecture/link invariants, generated Worker types, TypeScript, JavaScript syntax, catalog-to-runtime handler parity, deterministic relay lifecycle and secure-file tests, local path/write/process/state/log/service invariants, Ed25519/RSA generation and key-pair validation, real-machine `full` sandbox acceptance, a clean npm package-manifest/sensitive-artifact check, managed-job integrity/redaction/finally/cancellation/recovery, a live stdio MCP flow, and a live local OAuth/Worker/WebSocket/MCP flow. GitHub Actions runs the suite on Linux, macOS, and Windows with the pinned Node 26/npm 12 baseline; macOS and package-audit jobs also exercise the documented isolated global installation.
388
441
 
389
- See [docs/AGENT_CONTEXT.md](docs/AGENT_CONTEXT.md), [docs/MANAGED_JOBS.md](docs/MANAGED_JOBS.md), [docs/TESTING.md](docs/TESTING.md), [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md), [docs/ENGINEERING.md](docs/ENGINEERING.md), and [SECURITY.md](SECURITY.md).
442
+ See [docs/AGENT_CONTEXT.md](docs/AGENT_CONTEXT.md), [docs/LOCAL_AUTOMATION.md](docs/LOCAL_AUTOMATION.md), [docs/MANAGED_JOBS.md](docs/MANAGED_JOBS.md), [docs/TESTING.md](docs/TESTING.md), [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md), [docs/ENGINEERING.md](docs/ENGINEERING.md), and [SECURITY.md](SECURITY.md).
390
443
 
391
444
  ## Uninstall
392
445
 
package/SECURITY.md CHANGED
@@ -53,13 +53,27 @@ For untrusted repositories or instructions, run the bridge inside a disposable V
53
53
 
54
54
  ## Agent instructions, skills, and command manifests
55
55
 
56
- Repository and user instruction files are untrusted content from the model's perspective. `agent_context` returns their text in deterministic precedence order but does not certify correctness or safety. A malicious `AGENTS.md`, custom instruction file, or `SKILL.md` can attempt prompt injection or recommend destructive operations. Use only trusted repositories and skill roots, or isolate the bridge externally.
56
+ Machine Bridge prepends a package-controlled built-in working-agreement block and an optional bounded automatic project-context block before repository/user instructions. The built-in block is guidance, not a security boundary. Automatic context reads only recognized root filenames, package-manager/lockfile facts, package script names, runtime constraints, common documentation names, and CI filenames; it does not inject script bodies, dependency values, source contents, or execute discovered commands. Symbolic-link, oversized, invalid-UTF-8, and unsupported metadata is skipped conservatively.
57
+
58
+ Repository and user instruction files remain untrusted content from the model's perspective. `agent_context` returns their text in deterministic precedence order but does not certify correctness or safety. A malicious `AGENTS.md`, custom instruction file, filename, script name, or `SKILL.md` can attempt prompt injection or recommend destructive operations. Use only trusted repositories and skill roots, keep host approvals/policy enabled, or isolate the bridge externally.
57
59
 
58
60
  Skill loading is non-executing. `load_local_skill` returns an entrypoint and bounded file inventory; scripts remain inert until a separate process or command tool is called. Symlinked skill directories are followed only after canonical path-policy validation, while symbolic-link `SKILL.md` entrypoints are rejected. Traversal, cycles, content, summaries, and inventory are bounded.
59
61
 
60
62
  Registered commands are available only under direct-execution-capable policy. They use argv spawning rather than shell parsing, reject undeclared caller arguments, and enforce the manifest timeout as a ceiling. They are not an approval or sandbox boundary: a repository-controlled package script, interpreter, compiler, or executable can still run arbitrary code with local-user authority. Deeper manifests can override or remove inherited commands, so callers must obtain context for the actual target path rather than assuming the repository-root registry applies everywhere.
61
63
 
62
- The remote MCP host remains an independent boundary. Machine Bridge can advertise these tools and instruct the model to bootstrap with `agent_context`, but it cannot force a host to expose or invoke them, and it cannot bypass host confirmation or refusal.
64
+ The remote MCP host remains an independent boundary. Machine Bridge can advertise these tools, append `model_instructions_file` during initialization, and recommend capabilities through `resolve_task_capabilities`, but it cannot force a host to preserve instructions, expose tools, approve calls, or invoke them.
65
+
66
+ The global `model_instructions_file` is an explicit user trust decision. It is read for every profile and sent to the authorized host during session initialization; do not place credentials, private records, or content inappropriate for every connected session in it. Project manifests cannot override this global file or the global `builtin_instructions`/`automatic_project_context` controls. Other global manifest fields that would widen local scope are ignored under workspace-confined profiles.
67
+
68
+ ## Browser and application automation
69
+
70
+ Canonical `full` exposes structured browser and desktop UI authority. The browser extension operates the user's existing Chromium profile, including active login state, and has broad host access so it can inspect arbitrary pages and fill complex forms. An authorized client may therefore read page content, click controls, submit forms, upload registered files, and cause transactions with the user's browser identity. macOS Accessibility actions similarly operate applications with the local user's UI authority.
71
+
72
+ The browser loopback broker validates loopback `Host`, requires a random owner-only bearer subprotocol, accepts extension sockets only with a `chrome-extension://` origin, and authenticates additional local runtimes separately. Initial extension pairing is trust-on-first-use; after pairing, a different localhost page cannot replace the stored broker unless the user clicks the extension action while the genuine pairing page is active. The pairing token is never returned by MCP or written to operational logs. These controls defend against casual cross-origin and DNS-rebinding access; they do not protect against malicious code already running as the same OS user or a compromised browser extension profile.
73
+
74
+ Caller-supplied JavaScript, AppleScript, and JXA source are deliberately unsupported. Actions use fixed implementation code and structured selectors. This removes an avoidable arbitrary-evaluation surface but does not make DOM or Accessibility actions safe. Pages can contain prompt injection, misleading labels, hidden consequences, cross-origin frames unavailable to inspection, or state that changes between inspection and submission. High-impact account, financial, legal, medical, publishing, deletion, and purchase actions require contextual review and any host/user confirmation the client provides.
75
+
76
+ Text and file resources can be injected locally so their contents do not appear in MCP arguments or results. The destination page/application still receives the value, and remote tool metadata/results still traverse the Worker and host. Page source and screenshots can themselves contain secrets. Browser/app tool arguments, source, screenshots, field values, and results are intentionally omitted from operational logs.
63
77
 
64
78
  ## Filesystem exposure
65
79
 
@@ -0,0 +1,33 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "Machine Bridge Browser",
4
+ "version": "0.11.0",
5
+ "description": "Connects the current Chromium browser profile to the local Machine Bridge runtime for user-authorized automation.",
6
+ "permissions": [
7
+ "tabs",
8
+ "scripting",
9
+ "storage",
10
+ "alarms"
11
+ ],
12
+ "host_permissions": [
13
+ "<all_urls>"
14
+ ],
15
+ "background": {
16
+ "service_worker": "service-worker.js"
17
+ },
18
+ "content_scripts": [
19
+ {
20
+ "matches": [
21
+ "http://127.0.0.1/*"
22
+ ],
23
+ "js": [
24
+ "pairing.js"
25
+ ],
26
+ "run_at": "document_idle"
27
+ }
28
+ ],
29
+ "action": {
30
+ "default_title": "Machine Bridge Browser"
31
+ },
32
+ "version_name": "0.11.0"
33
+ }
@@ -0,0 +1,246 @@
1
+ (() => {
2
+ if (globalThis.__machineBridgePageAutomation) return;
3
+
4
+ const INTERACTIVE_SELECTOR = "a,button,input,select,textarea,[role],[contenteditable='true'],summary";
5
+ const MAX_SHADOW_ROOTS = 200;
6
+
7
+ function collectRoots() {
8
+ const roots = [document];
9
+ for (let index = 0; index < roots.length && roots.length <= MAX_SHADOW_ROOTS; index += 1) {
10
+ const root = roots[index];
11
+ for (const element of root.querySelectorAll("*")) {
12
+ if (element.shadowRoot && !roots.includes(element.shadowRoot)) roots.push(element.shadowRoot);
13
+ if (roots.length > MAX_SHADOW_ROOTS) break;
14
+ }
15
+ }
16
+ return roots;
17
+ }
18
+
19
+ function deepQuerySelectorAll(selector) {
20
+ const results = [];
21
+ for (const root of collectRoots()) results.push(...root.querySelectorAll(selector));
22
+ return results;
23
+ }
24
+
25
+ function rootById(element, id) {
26
+ const root = element.getRootNode?.();
27
+ if (root && typeof root.getElementById === "function") return root.getElementById(id);
28
+ return document.getElementById(id);
29
+ }
30
+
31
+ function inspect(params) {
32
+ const maxElements = Number(params.maxElements) || 300;
33
+ const includeValues = params.includeValues === true;
34
+ const all = deepQuerySelectorAll(INTERACTIVE_SELECTOR);
35
+ const candidates = all.slice(0, maxElements);
36
+ return {
37
+ document: {
38
+ title: document.title,
39
+ url: location.href,
40
+ language: document.documentElement.lang || "",
41
+ forms: deepQuerySelectorAll("form").length,
42
+ open_shadow_roots: Math.max(0, collectRoots().length - 1),
43
+ },
44
+ elements: candidates.map((element, index) => describeElement(element, index, includeValues)),
45
+ truncated: all.length > candidates.length,
46
+ };
47
+ }
48
+
49
+ function action(params) {
50
+ const element = findElement(params.selector);
51
+ if (!element) throw new Error("no element matched selector");
52
+ applyOne(element, params.action, params.value, params.key);
53
+ return { ok: true, element: describeElement(element, 0, false) };
54
+ }
55
+
56
+ function fillForm(params) {
57
+ const results = [];
58
+ for (let index = 0; index < params.fields.length; index += 1) {
59
+ const field = params.fields[index];
60
+ const element = findElement(field.selector);
61
+ if (!element) throw new Error(`no element matched fields[${index}] selector`);
62
+ applyOne(element, field.action, field.value, "");
63
+ results.push({ index, action: field.action, sensitive: field.sensitive === true, element: describeElement(element, index, false) });
64
+ }
65
+ if (params.submit) {
66
+ const submitter = params.submitSelector ? findElement(params.submitSelector) : deepQuerySelectorAll("button[type='submit'],input[type='submit']")[0];
67
+ if (submitter) submitter.click();
68
+ else {
69
+ const field = params.fields.length ? findElement(params.fields[0].selector) : null;
70
+ const form = field?.form || field?.closest?.("form") || deepQuerySelectorAll("form")[0];
71
+ if (!form) throw new Error("no form or submit control found");
72
+ if (typeof form.requestSubmit === "function") form.requestSubmit();
73
+ else form.submit();
74
+ }
75
+ }
76
+ return { ok: true, fields: results, submitted: params.submit === true, values_exposed: false };
77
+ }
78
+
79
+ function uploadFiles(params) {
80
+ const input = findElement(params.selector);
81
+ if (!(input instanceof HTMLInputElement) || input.type !== "file") throw new Error("matched element is not a file input");
82
+ const transfer = new DataTransfer();
83
+ for (const item of params.files) {
84
+ const binary = atob(item.data);
85
+ const bytes = new Uint8Array(binary.length);
86
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
87
+ transfer.items.add(new File([bytes], item.filename, { type: item.mime || "application/octet-stream" }));
88
+ }
89
+ input.files = transfer.files;
90
+ dispatchValueEvents(input);
91
+ return { ok: true, file_count: transfer.files.length, values_exposed: false };
92
+ }
93
+
94
+ function describeElement(element, index, includeValues) {
95
+ const tag = element.tagName.toLowerCase();
96
+ const type = String(element.getAttribute("type") || "").toLowerCase();
97
+ const sensitive = isSensitiveElement(element, tag, type);
98
+ const item = {
99
+ index,
100
+ tag,
101
+ type,
102
+ role: element.getAttribute("role") || implicitRole(element),
103
+ name: accessibleName(element),
104
+ id: element.id || "",
105
+ field_name: element.getAttribute("name") || "",
106
+ label: labelText(element),
107
+ placeholder: element.getAttribute("placeholder") || "",
108
+ disabled: Boolean(element.disabled),
109
+ checked: "checked" in element ? Boolean(element.checked) : null,
110
+ selected: tag === "select" ? element.value : null,
111
+ href: tag === "a" ? element.href : "",
112
+ sensitive,
113
+ in_shadow_dom: element.getRootNode?.() instanceof ShadowRoot,
114
+ };
115
+ if (includeValues && !sensitive && "value" in element) item.value = String(element.value || "").slice(0, 2000);
116
+ return item;
117
+ }
118
+
119
+ function isSensitiveElement(element, tag, type) {
120
+ if (tag !== "input" && tag !== "textarea") return false;
121
+ if (["password", "hidden"].includes(type)) return true;
122
+ const autocomplete = String(element.getAttribute("autocomplete") || "").toLowerCase();
123
+ if (/(?:password|one-time-code|cc-number|cc-csc|cc-exp)/.test(autocomplete)) return true;
124
+ const identity = `${element.id || ""} ${element.getAttribute("name") || ""} ${element.getAttribute("aria-label") || ""} ${element.getAttribute("placeholder") || ""}`.toLowerCase();
125
+ return /(?:password|passwd|secret|token|api[-_ ]?key|otp|one[-_ ]?time|verification|cvc|cvv|security[-_ ]?code|card[-_ ]?number)/.test(identity);
126
+ }
127
+
128
+ function applyOne(element, operation, value, key) {
129
+ element.scrollIntoView({ block: "center", inline: "nearest" });
130
+ if (operation === "click") { element.click(); return; }
131
+ if (operation === "focus") { element.focus(); return; }
132
+ if (operation === "submit") {
133
+ const form = element.form || element.closest("form");
134
+ if (!form) throw new Error("matched element is not associated with a form");
135
+ if (typeof form.requestSubmit === "function") form.requestSubmit(); else form.submit();
136
+ return;
137
+ }
138
+ if (operation === "check" || operation === "uncheck") {
139
+ if (!("checked" in element)) throw new Error("matched element is not checkable");
140
+ const wanted = operation === "check";
141
+ if (Boolean(element.checked) !== wanted) element.click();
142
+ return;
143
+ }
144
+ if (operation === "select") {
145
+ if (!(element instanceof HTMLSelectElement)) throw new Error("matched element is not a select control");
146
+ const text = String(value ?? "");
147
+ const option = [...element.options].find((item) => item.value === text || item.text.trim() === text);
148
+ if (!option) throw new Error("select option was not found");
149
+ element.value = option.value;
150
+ dispatchValueEvents(element);
151
+ return;
152
+ }
153
+ if (operation === "fill") {
154
+ if (!("value" in element) && !element.isContentEditable) throw new Error("matched element is not editable");
155
+ element.focus();
156
+ if (element.isContentEditable) element.textContent = String(value ?? "");
157
+ else setNativeValue(element, String(value ?? ""));
158
+ dispatchValueEvents(element);
159
+ return;
160
+ }
161
+ if (operation === "press") {
162
+ const pressed = String(key || value || "Enter");
163
+ element.focus();
164
+ element.dispatchEvent(new KeyboardEvent("keydown", { key: pressed, bubbles: true, composed: true }));
165
+ element.dispatchEvent(new KeyboardEvent("keyup", { key: pressed, bubbles: true, composed: true }));
166
+ return;
167
+ }
168
+ throw new Error(`unsupported element action: ${operation}`);
169
+ }
170
+
171
+ function setNativeValue(element, value) {
172
+ const prototype = element instanceof HTMLTextAreaElement
173
+ ? HTMLTextAreaElement.prototype
174
+ : element instanceof HTMLSelectElement
175
+ ? HTMLSelectElement.prototype
176
+ : HTMLInputElement.prototype;
177
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, "value");
178
+ if (descriptor?.set) descriptor.set.call(element, value);
179
+ else element.value = value;
180
+ }
181
+
182
+ function dispatchValueEvents(element) {
183
+ element.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
184
+ element.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
185
+ }
186
+
187
+ function findElement(selector) {
188
+ let elements = [];
189
+ if (selector.css) elements = deepQuerySelectorAll(selector.css);
190
+ else if (selector.id) elements = deepQuerySelectorAll(`[id="${cssEscape(selector.id)}"]`);
191
+ else elements = deepQuerySelectorAll(INTERACTIVE_SELECTOR);
192
+ elements = elements.filter((element) => {
193
+ if (selector.name && element.getAttribute("name") !== selector.name) return false;
194
+ if (selector.label && !sameText(labelText(element), selector.label)) return false;
195
+ if (selector.text && !sameText(element.innerText || element.textContent || "", selector.text)) return false;
196
+ if (selector.role && !sameText(element.getAttribute("role") || implicitRole(element), selector.role)) return false;
197
+ if (selector.placeholder && !sameText(element.getAttribute("placeholder") || "", selector.placeholder)) return false;
198
+ return true;
199
+ });
200
+ return elements[Number.isInteger(selector.index) ? selector.index : 0] || null;
201
+ }
202
+
203
+ function cssEscape(value) {
204
+ if (globalThis.CSS?.escape) return globalThis.CSS.escape(String(value));
205
+ return String(value).replace(/["\\]/g, "\\$&");
206
+ }
207
+
208
+ function sameText(left, right) {
209
+ return String(left || "").trim().replace(/\s+/g, " ").toLowerCase() === String(right || "").trim().replace(/\s+/g, " ").toLowerCase();
210
+ }
211
+
212
+ function labelText(element) {
213
+ if (element.labels?.length) return [...element.labels].map((label) => label.innerText || label.textContent || "").join(" ").trim();
214
+ const parent = element.closest("label");
215
+ if (parent) return (parent.innerText || parent.textContent || "").trim();
216
+ const labelledBy = element.getAttribute("aria-labelledby");
217
+ if (labelledBy) return labelledBy.split(/\s+/).map((id) => rootById(element, id)?.textContent || "").join(" ").trim();
218
+ return "";
219
+ }
220
+
221
+ function accessibleName(element) {
222
+ return (element.getAttribute("aria-label") || labelText(element) || element.getAttribute("title") || element.getAttribute("alt") || element.innerText || element.textContent || "")
223
+ .trim().replace(/\s+/g, " ").slice(0, 500);
224
+ }
225
+
226
+ function implicitRole(element) {
227
+ const tag = element.tagName.toLowerCase();
228
+ if (tag === "a" && element.hasAttribute("href")) return "link";
229
+ if (tag === "button") return "button";
230
+ if (tag === "textarea") return "textbox";
231
+ if (tag === "select") return "combobox";
232
+ if (tag === "input") {
233
+ const type = String(element.type || "text").toLowerCase();
234
+ if (["button", "submit", "reset"].includes(type)) return "button";
235
+ if (type === "checkbox") return "checkbox";
236
+ if (type === "radio") return "radio";
237
+ return "textbox";
238
+ }
239
+ return "";
240
+ }
241
+
242
+ Object.defineProperty(globalThis, "__machineBridgePageAutomation", {
243
+ value: Object.freeze({ inspect, action, fillForm, uploadFiles }),
244
+ configurable: true,
245
+ });
246
+ })();
@@ -0,0 +1,19 @@
1
+ (() => {
2
+ const marker = document.querySelector('meta[name="machine-bridge-browser-pair"]');
3
+ const portMeta = document.querySelector('meta[name="machine-bridge-browser-port"]');
4
+ const tokenMeta = document.querySelector('meta[name="machine-bridge-browser-token"]');
5
+ if (!marker || !portMeta || !tokenMeta) return;
6
+ const token = tokenMeta.content;
7
+ const port = Number(portMeta.content);
8
+ if (!/^[A-Za-z0-9_-]{32,100}$/.test(token) || !Number.isInteger(port) || port < 1024 || port > 65535) return;
9
+ chrome.runtime.sendMessage({ type: "pair", endpoint: `ws://127.0.0.1:${port}/extension`, token }, (response) => {
10
+ const status = document.getElementById("status");
11
+ if (chrome.runtime.lastError || response?.ok !== true) {
12
+ if (status) status.textContent = response?.requires_manual_repair
13
+ ? "This extension is already paired to different local state. Click the Machine Bridge extension icon while this page is active to confirm re-pairing."
14
+ : "Pairing failed. Confirm the local Machine Bridge runtime is running, then reload this page.";
15
+ return;
16
+ }
17
+ if (status) status.textContent = "Paired. You may close this tab.";
18
+ });
19
+ })();