@facets-cloud/mx 0.0.0-stage → 0.1.1

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