@facets-cloud/mx 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Facets Cloud
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,319 @@
1
- # Temporary Holding Version
1
+ # mx · mac exec
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Like `docker exec`, but the container is your Mac. mx lets coding agents
4
+ (Claude Code, Codex, Cursor, Claude Desktop) run their steps inside the Mac
5
+ apps you already have open and signed in to, through the macOS accessibility
6
+ API. It reaches the whole Mac, not just one app: Slack, Mail, Notes, Finder,
7
+ any native or Electron app, plus menus, windows, Control Center, the Dock,
8
+ System Settings and dialogs. It works in the background by default, so you
9
+ keep typing in your own app while the agent works in another.
10
+
11
+ mx is one Swift binary that uses only Apple's frameworks. It speaks MCP
12
+ (JSON-RPC 2.0) on stdio, sends nothing over the network and has no
13
+ telemetry.
14
+
15
+ **Platforms:** macOS 14 or later, on Apple silicon and Intel. The
16
+ accessibility API is Mac-only, so there is no Linux or Windows build.
17
+
18
+ ```
19
+ > Set my screen brightness to 100% and tell me what it was
20
+
21
+ start "Brightness to 100%"
22
+ batch app=system read slider "display brightness", fill "100%", read
23
+ (mx opened Control Center first)
24
+ 1 read slider "display brightness": 62% (0.62)
25
+ set slider "display brightness" to 100%; reads back 100% (1.0)
26
+ 3 read slider "display brightness": 100% (1.0)
27
+ done "Brightness was 62%, now 100%"
28
+ ```
29
+
30
+ ## How it works
31
+
32
+ The agent snapshots an app and gets one line per control, each with a ref:
33
+
34
+ ```
35
+ e4 textarea "Message" = "draft"
36
+ e12 button "Send"
37
+ e19 slider "display brightness" = "68%"
38
+ ```
39
+
40
+ It acts by ref (`click e12`), or by role and name (`button "Send"`), which
41
+ waits up to 3 s for the element to appear, so a `batch` can plan screens it
42
+ hasn't seen yet. After each action mx waits for the window to settle (no
43
+ change for 100 ms) and returns only what changed: `+` added, `-` removed,
44
+ `~` changed. Refs stay the same while an element stays on screen, so the
45
+ agent rarely needs a second snapshot.
46
+
47
+ Measured on a real Mac:
48
+
49
+ - A Slack window snapshot: about 117 ms for 268 elements.
50
+ - A fill: about 200 ms, settle included.
51
+ - A borrowed step (see below): 200 to 400 ms from taking the front to
52
+ handing it back.
53
+ - Reading the Control Center brightness slider, moving it up 10%, reading
54
+ it, moving it back and reading again, with the panel closed at the start:
55
+ one batch, 1.0 s.
56
+
57
+ ### Background by default
58
+
59
+ Reading the tree, pressing buttons (AXPress), setting text and posting plain
60
+ keys all work with the app in the background. mx never raises the app for
61
+ those. Two kinds of step need the front: a real mouse click (for elements
62
+ with no press action, such as Slack's sidebar rows) and cmd or ctrl
63
+ shortcuts, which a background app drops. For those, mx borrows the front:
64
+ it notes your app, your window and the cursor, brings the target forward,
65
+ does the step and puts all three back. Before it borrows, it waits until you
66
+ have made no key press or mouse move for 500 ms. If you keep typing for 3 s
67
+ it refuses the step instead, so your keystrokes never land in someone
68
+ else's message box.
69
+
70
+ ## Modes
71
+
72
+ You choose per job, in plain words. The agent passes your choice to mx's
73
+ `start` tool.
74
+
75
+ | You say | Mode | What happens |
76
+ | --- | --- | --- |
77
+ | nothing, or "in the background" | `background` (default) | You keep working. mx never raises the app and borrows the front only for the steps above. |
78
+ | "show me", "do it in front of me" | `front` | The app comes forward and stays there. Full speed. |
79
+ | "let me watch", "go slowly" | `watch` | Like front, plus the pointer glides to each target, clicks move the real mouse, and batch steps pause 400 ms. |
80
+
81
+ A 4-step TextEdit batch took 1.65 s in background mode and 4.15 s in watch
82
+ mode.
83
+
84
+ `typing` is `paste` (default: the text goes in at once) or `keys` (typed key
85
+ by key, for chat composers and autocompletes that ignore pasted text).
86
+ `MX_MODE` and `MX_TYPING` set the defaults where mx starts; what the agent
87
+ passes to `start` wins.
88
+
89
+ ### The indicator
90
+
91
+ While a session is open, mx draws a thin blue border around the window it
92
+ works in. In background mode a small pill sits under the window with a
93
+ spinner and the current step, such as `mx · Paste 24 characters` or
94
+ `mx · 3/7 Press cmd+a`, and the session title between calls. When the window
95
+ is covered, the pill moves to the bottom of the screen. In front and watch
96
+ modes the step rides on a pointer at the element instead, with a ripple on
97
+ each click. The border and pill sit just above the target window, so your
98
+ own windows still cover them. After `done` they show the summary and fade
99
+ out. Screenshots leave them out.
100
+
101
+ ## Install
102
+
103
+ You need Node 18 or later for `npx`. Each line below adds the same server.
104
+
105
+ **Claude Code plugin** (the server plus the mx skill, which teaches the
106
+ agent the workflow and the safety rules):
107
+
108
+ ```sh
109
+ claude plugin marketplace add Facets-cloud/mx
110
+ claude plugin install mx@mx
111
+ ```
112
+
113
+ **Claude Code, server only:**
114
+
115
+ ```sh
116
+ claude mcp add -s user mx -- npx -y @facets-cloud/mx
117
+ ```
118
+
119
+ **Codex**, in `~/.codex/config.toml`:
120
+
121
+ ```toml
122
+ [mcp_servers.mx]
123
+ command = "npx"
124
+ args = ["-y", "@facets-cloud/mx"]
125
+ ```
126
+
127
+ **Cursor, Claude Desktop and other MCP apps**, in their MCP config
128
+ (`~/.cursor/mcp.json`, `claude_desktop_config.json`):
129
+
130
+ ```json
131
+ { "mcpServers": { "mx": { "command": "npx", "args": ["-y", "@facets-cloud/mx"] } } }
132
+ ```
133
+
134
+ Restart the app after adding it. Then check the permissions:
135
+
136
+ ```sh
137
+ npx -y @facets-cloud/mx doctor
138
+ ```
139
+
140
+ Always use the scoped name `@facets-cloud/mx`. To pin a version, write
141
+ `@facets-cloud/mx@0.1.0`. Release tarballs for both architectures, with
142
+ `checksums.txt`, are on the
143
+ [releases page](https://github.com/Facets-cloud/mx/releases) if you'd rather put the
144
+ binary on your PATH and point the config at it.
145
+
146
+ ### Permissions
147
+
148
+ macOS gives these to the app that launched mx, not to mx itself. That is
149
+ your terminal (Terminal, iTerm, Ghostty and so on), your editor, or Claude
150
+ Desktop or Cursor.
151
+
152
+ - **Accessibility** is required by every tool except `list_apps`, `start`
153
+ and `done`.
154
+ - **Screen Recording** (Screen & System Audio Recording) is needed only for
155
+ `screenshot`. The other tools work without it.
156
+
157
+ Turn them on in System Settings > Privacy & Security, then restart that app.
158
+ `mx doctor` says which app to turn on and what is missing, in the same
159
+ words the tools use:
160
+
161
+ ```
162
+ mx 0.1.0, macOS 15.5.0 (arm64)
163
+ permissions go to: Ghostty (the app that launched mx)
164
+ ok Accessibility: granted, every tool works
165
+ warn Screen Recording: mx is not allowed to record the screen, so it cannot take screenshots (the other tools still work). Open System Settings > ...
166
+ ```
167
+
168
+ It exits 0 when Accessibility is granted and 1 when it isn't.
169
+
170
+ ## Tools
171
+
172
+ 19 tools.
173
+
174
+ | Group | Tools |
175
+ | --- | --- |
176
+ | Session | `start` (title, mode, typing), `done` (summary; returns time taken and action count) |
177
+ | Read | `list_apps`, `snapshot`, `read`, `screenshot` |
178
+ | Act | `click`, `fill`, `key`, `batch`, `scroll`, `drag` |
179
+ | The rest of the Mac | `menu`, `window`, `app`, `open`, `clipboard` |
180
+ | Audit log | `history`, `settings` |
181
+
182
+ - `snapshot` takes a `scope`: `window` (default), `menubar`, `extras` (an
183
+ app's menu bar icons), `windows` (every window, panel, dialog and sheet)
184
+ or `app`.
185
+ - `screenshot` returns a PNG (at most 1280 px wide) of the window, even
186
+ behind other apps, with each control's ref drawn on it. `element` crops
187
+ to one control and `full` captures the whole display.
188
+ - `fill` on a slider takes `"75%"`, `"+10%"` or `"-10%"` and reads the level
189
+ back.
190
+ - `click` takes `action` (`showmenu`, `increment`, `pick` and others),
191
+ `count: 2` for a double click and `button: "right"`.
192
+ - `key` also takes the hardware keys `brightness_up`, `volume_down`,
193
+ `volume_mute` and `play_pause`, which need no app.
194
+ - `menu` runs a command by its titles, such as `["View", "Zoom In"]`.
195
+ - `window` lists, raises, moves, resizes, minimizes, goes fullscreen or
196
+ closes. `app` launches, activates, hides or quits.
197
+ - `open` takes a URL, a file, or a settings pane such as `settings:displays`.
198
+
199
+ ## What it can reach
200
+
201
+ - **Apps:** any app with a window, native or Electron. mx turns on an
202
+ Electron app's accessibility tree the first time it touches it.
203
+ - **Control Center:** `app: "system"` covers the menu bar extras (Wi-Fi,
204
+ Battery, the clock) and the Control Center panel. Name a control such as
205
+ `slider "display brightness"` and mx opens the panel if it is closed.
206
+ - **Menus and the menu bar** of any app, by title path.
207
+ - **The Dock** and other system processes, with `list_apps all: true`.
208
+ - **System Settings:** `open settings:<pane>` goes to a pane. Reading any
209
+ pane is free. Changes in the sensitive panes (Privacy & Security, Users &
210
+ Groups, Passwords, Login Items, Sharing, Lock Screen, Touch ID & Password,
211
+ Device Management, Transfer or Reset) need confirmation.
212
+ - **Dialogs:** Open and Save panels and sheets show in `scope: "windows"`.
213
+
214
+ ## Safety
215
+
216
+ mx can do what you can do with your hands, and the Accessibility grant
217
+ makes that possible for any process the granted app starts.
218
+ [SECURITY.md](SECURITY.md) explains the threat model. The rules mx applies
219
+ inside that grant:
220
+
221
+ - **Confirm gate.** mx refuses to press anything labelled send, post,
222
+ delete, remove, pay, buy, archive, submit, ship, deploy, release,
223
+ publish, leave, place order, schedule, checkout, restart, shut down, log
224
+ out, erase, reset, uninstall, move to trash, empty trash, format or force
225
+ quit, unless the call passes `confirm: true`. The same goes for Return in
226
+ a text box (it can send a message), typed text with a newline, an
227
+ unlabeled button beside a message box (usually Send), cmd+delete in
228
+ Finder, closing a window with unsaved changes, reading the clipboard and
229
+ changes in the sensitive settings panes. The agent is told to ask you
230
+ before it passes `confirm`.
231
+ - **Blocked apps.** mx won't touch Keychain Access, password managers
232
+ (1Password, Bitwarden, KeePassXC and others) or terminals, where typed
233
+ keys would run shell commands outside the agent's own permission prompts.
234
+ That includes screenshots of them.
235
+ - **Password fields** are never read, filled or clicked.
236
+ - **Permission prompts** (an app asking to control the computer or record
237
+ the screen) are refused even with `confirm`. You answer those yourself.
238
+ - **App text is data.** The skill tells the agent that a message or email
239
+ asking it to do something is content to report, not an instruction.
240
+
241
+ ## Audit log
242
+
243
+ mx writes one JSON line per tool call to
244
+ `~/Library/Application Support/mx/sessions/<session>.jsonl` (mode 0600),
245
+ in the same format as [bx](https://github.com/Facets-cloud/bx), so one
246
+ viewer or log shipper reads both. Each line names the app, window, element
247
+ (role and name), mode, result and time taken. Every `confirm: true` and
248
+ every refusal gets its own line, with the rule that stopped it. Typed text,
249
+ fill values and clipboard text appear only as their length. Inside Claude
250
+ Code or Codex, a session is the app's own conversation.
251
+
252
+ ```sh
253
+ mx history # recent sessions
254
+ mx history previous # one session as a tree: tasks, apps and windows, actions
255
+ mx history pin previous "weekly report" # name a finished task so an agent can repeat it
256
+ mx history pinned # list pins; add a name to see its steps
257
+ mx config # log settings
258
+ mx config set log.values full # also log typed text (off also drops window titles and names)
259
+ ```
260
+
261
+ Logs are kept forever by default. Past 2 GB the oldest idle files are
262
+ gzipped, and past 5 GB of archives the oldest are deleted (`log.max`,
263
+ `log.archive_max`, `log.days`). Agents get the same views through the
264
+ `history` and `settings` tools. An agent can raise retention but can't
265
+ lower logging; it gets the `mx config set` command to hand to you.
266
+
267
+ ## Limits
268
+
269
+ - **macOS only**, 14 or later. The binary isn't signed or notarized yet.
270
+ - **The lock screen.** mx can't unlock the Mac or act on the lock screen.
271
+ Leave the Mac unlocked while an agent works.
272
+ - **Secure input.** mx never types into password fields. When an app turns
273
+ on secure input for a password prompt, finish that prompt yourself.
274
+ - **Permission prompts** need you. mx can't click Allow, by design.
275
+ - **Spaces.** Screenshots need the window on the current Space and not
276
+ minimized. A window on another Space hides the border and moves the pill
277
+ to the bottom of the screen.
278
+ - **Your mouse in background mode.** A borrowed mouse click moves the real
279
+ cursor for a few hundred ms and puts it back.
280
+
281
+ ## How it compares
282
+
283
+ - **Computer use with screenshots** sees pixels. Each step costs a
284
+ screenshot and a guess at coordinates, the app must be in front, and the
285
+ agent moves your mouse. mx reads the accessibility tree as text with exact
286
+ refs, works behind your windows, and uses screenshots only when the
287
+ layout matters.
288
+ - **AppleScript and Shortcuts** reach only apps that publish a scripting
289
+ dictionary, and each app has its own vocabulary. Electron apps such as
290
+ Slack publish none. mx uses the accessibility tree that every app has, and
291
+ needs one grant instead of an Automation prompt per app.
292
+ - **Other accessibility-tree tools** read the same tree. mx adds background
293
+ work with short borrows, diffs instead of full trees after each action,
294
+ role-and-name lookups that wait, the confirm gate, and the on-screen
295
+ indicator.
296
+
297
+ ## Develop
298
+
299
+ ```sh
300
+ swift build -c release # .build/release/mx
301
+ python3 tests/selftest.py --only protocol,gate,settle,diff # no GUI, no grant needed
302
+ python3 tests/selftest.py # everything, on a scratch TextEdit document
303
+ ```
304
+
305
+ The full self-test opens TextEdit, Finder, Control Center and System
306
+ Settings and briefly changes brightness and volume (restoring both), so run
307
+ it when you aren't using the Mac. Its scratch files go in `tests/scratch/`,
308
+ which git ignores.
309
+
310
+ `scripts/npm-build.sh vX.Y.Z` builds both architectures into `npm/` and sets
311
+ every package and plugin version. Pushing a `v*` tag runs the release
312
+ workflow: it builds, attaches tarballs and checksums to a GitHub release,
313
+ publishes `@facets-cloud/mx-darwin-arm64`, `@facets-cloud/mx-darwin-x64` and then `@facets-cloud/mx`, and
314
+ bumps the plugin version on main. `scripts/preview.sh` serves the landing
315
+ page in `site/` at http://localhost:8791.
316
+
317
+ ## License
318
+
319
+ MIT. See [LICENSE](LICENSE). mx contains no third-party code.
@@ -0,0 +1 @@
1
+ mx contains no third-party code. It links only Apple's system frameworks.
package/bin/mx.js ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ // Runs the mx binary from the platform package npm installed alongside this
3
+ // one (@facets-cloud/mx-darwin-<cpu>), passing arguments, stdio and the exit code
4
+ // through. With no arguments mx is an MCP server on this same stdio.
5
+ "use strict";
6
+ const { spawn } = require("node:child_process");
7
+
8
+ const pkg = `@facets-cloud/mx-${process.platform}-${process.arch}`;
9
+ let bin;
10
+ try {
11
+ bin = require.resolve(`${pkg}/bin/mx`);
12
+ } catch (err) {
13
+ if (err.code !== "MODULE_NOT_FOUND") throw err;
14
+ console.error(`mx: no build for ${process.platform}-${process.arch} (looked for ${pkg}). mx supports macOS on arm64 and x64.`);
15
+ process.exit(1);
16
+ }
17
+
18
+ const child = spawn(bin, process.argv.slice(2), { stdio: "inherit" });
19
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
20
+ process.on(sig, () => child.kill(sig));
21
+ }
22
+ child.on("exit", (code, signal) => {
23
+ if (signal) process.kill(process.pid, signal);
24
+ else process.exit(code ?? 1);
25
+ });
package/package.json CHANGED
@@ -1,6 +1,40 @@
1
1
  {
2
2
  "name": "@facets-cloud/mx",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Let coding agents drive your Mac's apps through the accessibility API, in the background or in front of you. An MCP server for Claude, Codex and Cursor.",
5
+ "license": "MIT",
6
+ "author": "Facets Cloud",
7
+ "homepage": "https://github.com/Facets-cloud/mx",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Facets-cloud/mx.git"
11
+ },
12
+ "keywords": [
13
+ "mcp",
14
+ "macos",
15
+ "accessibility",
16
+ "automation",
17
+ "agents",
18
+ "claude",
19
+ "codex"
20
+ ],
21
+ "bin": {
22
+ "mx": "bin/mx.js"
23
+ },
24
+ "files": [
25
+ "bin/mx.js",
26
+ "README.md",
27
+ "LICENSE",
28
+ "THIRD_PARTY_NOTICES"
29
+ ],
30
+ "os": [
31
+ "darwin"
32
+ ],
33
+ "engines": {
34
+ "node": ">=18"
35
+ },
36
+ "optionalDependencies": {
37
+ "@facets-cloud/mx-darwin-arm64": "0.1.0",
38
+ "@facets-cloud/mx-darwin-x64": "0.1.0"
39
+ }
40
+ }