noracu 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/CHANGELOG.md +52 -0
- package/LICENSE +21 -0
- package/README.md +152 -2
- package/README.zh-CN.md +153 -0
- package/SECURITY.md +41 -0
- package/docs/architecture.md +74 -0
- package/docs/tools.md +61 -0
- package/docs/usage.md +183 -0
- package/package.json +55 -4
- package/src/server.mjs +925 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.0] — 2026-11-02
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Mac layer (9 tools)**: `mac_screenshot` (region crop; returns main-display
|
|
7
|
+
scale + logical size), `mac_ax_state` (JXA Accessibility tree with element
|
|
8
|
+
indices + frames), `mac_click` (dual-mode: `[x,y]` or `element_index`),
|
|
9
|
+
`mac_drag` (index-only: `from_index`/`to_index`), `mac_scroll`, `mac_key`,
|
|
10
|
+
`mac_type`, `mac_paste`, `mac_menu_command` (localized-safe menu bar
|
|
11
|
+
traversal).
|
|
12
|
+
- **Browser layer (12 tools)**: Playwright session model — `browser_launch` /
|
|
13
|
+
`navigate` / `snapshot` (ARIA) / `screenshot` (format/quality/clip + DPR
|
|
14
|
+
metadata) / `click` / `fill` / `press_key` / `type` / `scroll` / `read` /
|
|
15
|
+
`evaluate` / `close`.
|
|
16
|
+
- **iPhone layer (14 tools)**: iPhone Mirroring driver (imctl) — `phone_status`
|
|
17
|
+
/ `launch` / `screenshot` / `ocr` / `find` / `tap` / `longpress` / `swipe`
|
|
18
|
+
/ `home` / `app_switcher` / `scroll` / `type` / `key` / `open`.
|
|
19
|
+
- **Security**: argv-parameterized JXA (string params never in script source), `validateArgs`
|
|
20
|
+
on every tool call, `NORACU_DISABLE_EVAL` kill switch,
|
|
21
|
+
`NORACU_READ_ONLY=1` observation mode (default-deny allowlist),
|
|
22
|
+
`file://` navigation blocked by default (`NORACU_ALLOW_FILE_URL=1`
|
|
23
|
+
opt-in), injection regression tests.
|
|
24
|
+
- **CI**: GitHub Actions (macos-latest) with `NORACU_CI` e2e mode.
|
|
25
|
+
- **Docs**: English and Chinese READMEs, architecture, a usage guide, and an auto-generated tool reference; SECURITY.md with per-threat status.
|
|
26
|
+
- Focus guard: `mac_type` / `mac_paste` / `mac_key` accept `app` and refuse to type when that app is not frontmost; `mac_ax_state` reports `ax_empty` instead of an empty tree.
|
|
27
|
+
- `mac_key` accepts human key names (cmd-shift-v, esc, return, arrow-up) with validation.
|
|
28
|
+
|
|
29
|
+
### Security
|
|
30
|
+
- Fixed JXA injection in `mac_ax_state` / `mac_menu_command` (static script
|
|
31
|
+
+ `osascript` argv — parameters are data, never script source).
|
|
32
|
+
- `mac_paste` restores the previous text clipboard after pasting
|
|
33
|
+
(non-text clipboard contents are not preserved).
|
|
34
|
+
- element_index cache is never overwritten by an empty AX snapshot.
|
|
35
|
+
- `mac_paste` and modifier chords in `mac_key` now use cliclick key-down/up sequences (previously failed on `kp:cmd-v`).
|
|
36
|
+
|
|
37
|
+
### Notes
|
|
38
|
+
- macOS 15+ required for Mac/iPhone layers; Browser layer is cross-platform.
|
|
39
|
+
- imctl is a companion checkout (sibling directory or `NORACU_IMCTL_DIR`);
|
|
40
|
+
phone tools degrade gracefully when absent.
|
|
41
|
+
- Tool descriptions are English; every JSON-schema property is single-typed
|
|
42
|
+
(no union/`oneOf`) for reliable LLM tool-calling across clients.
|
|
43
|
+
- `mac_click` / `mac_drag` refuse an `element_index` when a different app is
|
|
44
|
+
frontmost than the one the snapshot came from (prevents clicking the wrong
|
|
45
|
+
window within the 30s cache window).
|
|
46
|
+
- iPhone-layer failures (imctl missing, Mirroring window not frontmost) are reported as tool errors instead of plain text.
|
|
47
|
+
- When imctl's python3 lacks pyobjc, `phone_*` returns a one-line "imctl unavailable" message instead of a raw Python traceback.
|
|
48
|
+
- `mac_ax_state` retries once after ~350ms when the tree is empty, so an app just brought to the front (whose windows haven't attached to the AX tree yet) isn't falsely reported as `ax_empty`.
|
|
49
|
+
- `browser_launch` reports a clean "profile in use" error when another noracu instance holds the persistent browser profile, instead of crashing.
|
|
50
|
+
- `phone_home` / `phone_app_switcher` use the iPhone Mirroring menu (Home Screen / App Switcher) first — reliable without the window being frontmost and without imctl — and fall back to imctl gestures on a non-English menu or when the mirror is not connected.
|
|
51
|
+
|
|
52
|
+
History note: the repository history was reset before this release; earlier unpublished `noracu-mcp` versions are superseded.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 noracu contributors
|
|
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,153 @@
|
|
|
1
|
-
#
|
|
1
|
+
# noracu
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Local, no-cloud MCP server for computer-use on macOS apps, a Playwright browser, and (experimentally) a mirrored iPhone — the agent reads an accessibility/ARIA tree and acts by `element_index`, not pixels. No API key, no telemetry.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/sgInnora/noracu/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/noracu)
|
|
7
|
+
[](https://nodejs.org)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
中文说明见 [README.zh-CN.md](README.zh-CN.md)。
|
|
11
|
+
|
|
12
|
+
One stdio MCP server. The agent snapshots a tree, gets an indexed element list, then clicks or types by `element_index` instead of raw pixels — the same observe/act split on the Mac and in the browser.
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
*`browser_snapshot` returns an indexed ARIA tree; `browser_click` acts by `element_index`. The Mac layer runs the same loop (`mac_ax_state` → `mac_click`). Highlight boxes are overlays from `demo/record-browser.mjs`, not a product feature. The iPhone layer is not in this demo.*
|
|
17
|
+
|
|
18
|
+
## What it does
|
|
19
|
+
|
|
20
|
+
One MCP server, three control surfaces:
|
|
21
|
+
|
|
22
|
+
| Layer | Tools | Drives | Status |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| **Mac** | 9 (`mac_*`) | cliclick, screencapture, JXA System Events | stable |
|
|
25
|
+
| **Browser** | 12 (`browser_*`) | Playwright (persistent profile) | stable |
|
|
26
|
+
| **iPhone** | 14 (`phone_*`) | iPhone Mirroring via imctl (not public; 2 tools work without it) | experimental |
|
|
27
|
+
|
|
28
|
+
The iPhone layer is experimental and gated by imctl, a companion tool that is not yet public. Without imctl, most `phone_*` tools return a structured error; the Mac and browser layers are unaffected (`phone_home` / `phone_app_switcher` still work through the iPhone Mirroring menu when a phone is connected). The observe/act vocabulary follows the action/observation split popularized by ChatGPT's computer-use, re-implemented on open local tooling.
|
|
29
|
+
|
|
30
|
+
## Why noracu
|
|
31
|
+
|
|
32
|
+
noracu is the local hands, not the model. Your MCP client (Claude Desktop, Claude Code, Cursor) decides what to do; this server only observes and acts on this machine.
|
|
33
|
+
|
|
34
|
+
- **Runs entirely on your machine.** No cloud, no API key, no telemetry, no postinstall scripts. Sole runtime dependency: Playwright. noracu makes no network calls of its own; the only automatic one is the one-time Chromium download on the first `browser_launch` (`NORACU_DISABLE_AUTO_INSTALL=1` turns that off). The browser still fetches the pages you point it at.
|
|
35
|
+
- **Acts on semantics, not pixels.** Observation returns the accessibility tree (Mac) or ARIA tree (browser) as an indexed list — text, not screenshots. Actions reference `element_index`; indices expire after 30 s, and clicks re-verify the target (tag/name/href/position in the browser, frontmost app on the Mac) before firing. A stale reference returns `element_no_longer_valid` instead of clicking the wrong thing. Screenshots are there when the tree is not enough.
|
|
36
|
+
- **One protocol, three surfaces — not three scripts.** The same observe/act loop drives native Mac apps, a persistent-profile browser, and (experimentally) a mirrored iPhone. The pattern is identical across surfaces; only the tool names differ (`mac_*` vs `browser_*`).
|
|
37
|
+
- **Uses your real sessions.** Your logged-in browser profile, your Mac apps — not a hosted VM. Compared with cloud computer-use: neither is universally more reliable; they fail differently. noracu keeps the screen on your machine.
|
|
38
|
+
- **Off switches that are actually off.** `NORACU_READ_ONLY=1` (observation-only, default-deny), `NORACU_DISABLE_EVAL=1`, and `file://` blocked by default. See [Security](#security).
|
|
39
|
+
- **Verifiable.** 34 end-to-end checks (`npm test`), CI on macos-latest, MIT, and the whole server is one file: `src/server.mjs`.
|
|
40
|
+
|
|
41
|
+
**Not a good fit if** you need multiple browser tabs or sessions (single session for now), you're not on macOS (the Mac and iPhone layers are macOS-only; the browser layer is cross-platform), or you need the iPhone layer today. If you only need browser automation, playwright-mcp has tab management and selector APIs that noracu v0.1 lacks; noracu is for when one agent also has to leave the browser and drive Mac apps.
|
|
42
|
+
|
|
43
|
+
## Quickstart
|
|
44
|
+
|
|
45
|
+
First success is `mac_ax_state` (or a browser snapshot) — not a click, and not a `phone_*` tool. The biggest day-one failure is granting macOS permissions to `node` instead of to the host app; grants apply only after a full quit and reopen.
|
|
46
|
+
|
|
47
|
+
**1. Install** (Node ≥ 20; no postinstall script):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install noracu # server at node_modules/noracu/src/server.mjs
|
|
51
|
+
brew install cliclick # required for Mac input (mac_click/type/key/paste)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or from source: `git clone https://github.com/sgInnora/noracu.git && cd noracu && npm install && npm test` (add `NORACU_CI=1` to skip hardware-gated checks).
|
|
55
|
+
|
|
56
|
+
**2. Add to your MCP client — use an absolute path to `src/server.mjs`.** GUI apps launched from Finder/Dock do not inherit your shell `PATH`, so give an absolute path to the `node` binary as well (`which node`) — a bare `node`, `npx`, or `noracu` needs Node on the GUI `PATH` and otherwise fails with `ENOENT`.
|
|
57
|
+
|
|
58
|
+
Claude Desktop (`~/Library/Application Support/Claude/claude_desktop_config.json`) / Cursor (`~/.cursor/mcp.json`):
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"noracu": {
|
|
64
|
+
"command": "/opt/homebrew/bin/node",
|
|
65
|
+
"args": ["/ABSOLUTE/PATH/TO/noracu/src/server.mjs"]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Claude Code: `claude mcp add noracu -- /ABSOLUTE/PATH/TO/node /ABSOLUTE/PATH/TO/noracu/src/server.mjs`
|
|
72
|
+
|
|
73
|
+
Find your node path with `which node` (Apple Silicon: `/opt/homebrew/bin/node`; Intel: `/usr/local/bin/node`; nvm: the versioned path). Convenience, only if Node is already on the GUI `PATH`: `"command": "npx", "args": ["-y", "noracu"]`.
|
|
74
|
+
|
|
75
|
+
Then **quit the host app completely (including any menu-bar icon) and reopen it.** A loaded tool list means the transport works — it does not need any permission yet.
|
|
76
|
+
|
|
77
|
+
**3. Grant macOS permissions to the host app, not to `node`.** System Settings → Privacy & Security → **Accessibility** and **Screen Recording** (macOS 15: "Screen & System Audio Recording") → enable the host (Claude Desktop / Cursor / your terminal). noracu never appears here. Quit and reopen the host after granting.
|
|
78
|
+
|
|
79
|
+
**4. Verify.** With Finder in front, call `mac_ax_state` (no args) — it returns an indexed accessibility tree; indices last 30 s. Browser smoke test (no Accessibility needed): `browser_launch` (first run downloads Chromium unless `NORACU_DISABLE_AUTO_INSTALL=1`) → `browser_navigate` → `browser_snapshot` → `browser_click` by index.
|
|
80
|
+
|
|
81
|
+
### Troubleshooting
|
|
82
|
+
|
|
83
|
+
| Symptom | Cause / fix |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `mac_*` fails with error **-1719** | Accessibility not granted to the **host** app — grant it, then quit + reopen the host |
|
|
86
|
+
| Screenshots come back all black | Screen Recording not granted to the host — grant it, then quit + reopen |
|
|
87
|
+
| `ENOENT` mentioning cliclick | `brew install cliclick` (or set `NORACU_CLICLICK`) |
|
|
88
|
+
| `spawn node` / `npx` `ENOENT` from a GUI host | use an absolute `node` path in the config — nvm/volta/homebrew shims are not on the GUI `PATH` |
|
|
89
|
+
| `browser_launch` hangs on first use | one-time Chromium download; or auto-install disabled (`NORACU_DISABLE_AUTO_INSTALL=1`) → run `npx playwright install chromium` |
|
|
90
|
+
| `phone_*` says `imctl unavailable … missing Quartz` | expected — imctl is not public; ignore the phone tools (two menu tools still work, see below) |
|
|
91
|
+
| Typed text comes out re-spaced (e.g. "no ra cu") | a CJK input method is active — use `mac_paste` instead of `mac_type` |
|
|
92
|
+
| `element_no_longer_valid` | the snapshot is older than 30 s or the UI changed — re-run `mac_ax_state` / `browser_snapshot` and use a fresh index |
|
|
93
|
+
|
|
94
|
+
Node 18 is rejected at startup — use Node ≥ 20. Claude Desktop may start one noracu process per surface (e.g. chat and a second surface); they share the browser profile, so launch the browser from only one at a time.
|
|
95
|
+
|
|
96
|
+
Full worked examples: [docs/usage.md](docs/usage.md).
|
|
97
|
+
|
|
98
|
+
## iPhone layer (experimental)
|
|
99
|
+
|
|
100
|
+
The iPhone layer has 14 `phone_*` tools that drive iPhone Mirroring through **imctl**, a companion tool that is not yet public.
|
|
101
|
+
|
|
102
|
+
- **With imctl** installed and an iPhone mirrored to this Mac: all 14 tools are available.
|
|
103
|
+
- **Without imctl:** most `phone_*` tools return a structured `imctl unavailable` error. Two tools still work when the mirror is connected, because they go through the iPhone Mirroring menu: `phone_home` (Home Screen) and `phone_app_switcher` (App Switcher).
|
|
104
|
+
|
|
105
|
+
No `phone_*` tool runs without an active iPhone Mirroring session on this Mac. Tool names and behavior may change before imctl is released. Do not set `NORACU_IMCTL_DIR` unless you already have imctl from its author.
|
|
106
|
+
|
|
107
|
+
## Environment variables
|
|
108
|
+
|
|
109
|
+
| Variable | Default | Purpose |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| `NORACU_READ_ONLY` | unset | `=1` observation-only: a default-deny allowlist of 12 observe + browser-lifecycle tools; every click/type/paste/key/eval tool is refused |
|
|
112
|
+
| `NORACU_DISABLE_EVAL` | unset | `=1` makes `browser_evaluate` (arbitrary JS) return an error when called (it stays listed) |
|
|
113
|
+
| `NORACU_ALLOW_FILE_URL` | unset | `=1` lets `browser_navigate` open `file://` URLs (blocked by default) |
|
|
114
|
+
| `NORACU_DISABLE_AUTO_INSTALL` | unset | `=1` skips the Chromium auto-install on first `browser_launch` |
|
|
115
|
+
| `NORACU_CLICLICK` | `/opt/homebrew/bin/cliclick` | cliclick binary path |
|
|
116
|
+
| `NORACU_IMCTL_DIR` | sibling `../imctl` | imctl checkout location (do not set unless you have imctl) |
|
|
117
|
+
|
|
118
|
+
## Security
|
|
119
|
+
|
|
120
|
+
noracu runs with the privileges of your MCP client. It clicks, types, pastes, and drives a real browser on this machine — there is no sandbox and no remote approval prompt. Set these on the server entry, then quit the host completely and reopen it:
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
"env": { "NORACU_READ_ONLY": "1", "NORACU_DISABLE_EVAL": "1" }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- **`NORACU_READ_ONLY=1`** is the safe first-run mode. It refuses every tool not on a 12-name allowlist (the observe tools plus `browser_launch`/`browser_navigate`/`browser_close`); every click, type, paste, and eval tool is denied. `browser_navigate` is on the allowlist, so it can still load a URL.
|
|
127
|
+
- **`NORACU_DISABLE_EVAL=1`** makes `browser_evaluate` return an error when called (it stays listed in `tools/list`). Snapshot, click, and type still work.
|
|
128
|
+
- **`file://` is refused** unless `NORACU_ALLOW_FILE_URL=1`, so a browser agent can't read `~/Documents` by navigating to it. (The initial navigation URL is scheme-checked; redirects are not — see SECURITY.md.)
|
|
129
|
+
- **Focus guard:** pass `app` to `mac_type` / `mac_paste` / `mac_key` and input is refused unless that app is frontmost. Omit `app` and the guard is off.
|
|
130
|
+
- **Stale-element protection:** an `element_index` is not a coordinate. It expires after 30 s (`element_no_longer_valid`); a browser click re-checks tag/name/href/position, and a Mac click is refused if the frontmost app changed since the snapshot.
|
|
131
|
+
- **Injection-safe JXA:** string parameters are passed to `osascript` as argv data, never built into the script; the one numeric value (AX read depth) is clamped to 1–10 before use. Regression-tested.
|
|
132
|
+
|
|
133
|
+
These switches do not make observed content trustworthy. Screenshot text, accessibility names, and page content are untrusted input to your model — do not let them override the task. Full threat model and per-threat status: [SECURITY.md](SECURITY.md).
|
|
134
|
+
|
|
135
|
+
## Tools & docs
|
|
136
|
+
|
|
137
|
+
- Full tool reference: [docs/tools.md](docs/tools.md) (auto-generated from the running server)
|
|
138
|
+
- Usage walkthroughs: [docs/usage.md](docs/usage.md)
|
|
139
|
+
- Architecture: [docs/architecture.md](docs/architecture.md)
|
|
140
|
+
|
|
141
|
+
## Roadmap
|
|
142
|
+
|
|
143
|
+
- Browser: tab management (list / select / close)
|
|
144
|
+
- Act vocabulary (browser): `hover`, `set_value`, `wait_for`
|
|
145
|
+
- Snapshot diffing to cut token use — pending measurement
|
|
146
|
+
|
|
147
|
+
## Maintenance
|
|
148
|
+
|
|
149
|
+
Solo-maintained, no SLA. macOS 15+ for the Mac and iPhone layers; the browser layer runs wherever Playwright does.
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
MIT — see [LICENSE](LICENSE).
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# noracu
|
|
2
|
+
|
|
3
|
+
**本地、无云的 computer-use MCP server:在 macOS 原生应用、Playwright 浏览器、以及(实验性的)镜像 iPhone 上操作——agent 读取 accessibility / ARIA 树,按 `element_index` 动作,而非像素坐标。无 API key,无遥测。**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/sgInnora/noracu/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/noracu)
|
|
7
|
+
[](https://nodejs.org)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
For English, see [README.md](README.md).
|
|
11
|
+
|
|
12
|
+
一个 stdio MCP server。agent 对界面做一次快照、拿到带索引的元素列表,然后按 `element_index` 点击或输入,而不是用原始像素坐标——Mac 层和 Browser 层是同一套 observe/act 协议。
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
*`browser_snapshot` 返回带索引的 ARIA 树;`browser_click` 按 `element_index` 动作。Mac 层跑同一个循环(`mac_ax_state` → `mac_click`)。高亮框是 `demo/record-browser.mjs` 录制时的叠加层,不是产品功能。iPhone 层不在此 demo 中。*
|
|
17
|
+
|
|
18
|
+
## 它做什么
|
|
19
|
+
|
|
20
|
+
一个 MCP server,三个控制面:
|
|
21
|
+
|
|
22
|
+
| 层 | 工具 | 底层 | 状态 |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| **Mac** | 9 个(`mac_*`) | cliclick、screencapture、JXA System Events | stable |
|
|
25
|
+
| **Browser** | 12 个(`browser_*`) | Playwright(持久化 profile) | stable |
|
|
26
|
+
| **iPhone** | 14 个(`phone_*`) | 经 imctl 驱动 iPhone Mirroring(尚未公开;2 个工具无需它也能用) | experimental |
|
|
27
|
+
|
|
28
|
+
iPhone 层是实验性的,依赖 imctl——一个尚未公开的伴生工具。没有 imctl 时,多数 `phone_*` 工具返回结构化错误;Mac 层和 Browser 层不受影响(`phone_home` / `phone_app_switcher` 在镜像已连接时仍可经 iPhone Mirroring 菜单工作)。observe/act 词汇表沿用 ChatGPT computer-use 推广的 action/observation 拆分思路,在开放的本地工具上重新实现。
|
|
29
|
+
|
|
30
|
+
## 为什么用 noracu
|
|
31
|
+
|
|
32
|
+
noracu 是本地的"手",不是"脑"。决策由你的 MCP client(Claude Desktop、Claude Code、Cursor)做;本 server 只在这台机器上观察与动作。
|
|
33
|
+
|
|
34
|
+
- **完全在本机运行。** 无云、无 API key、无遥测、无 postinstall 脚本。唯一运行期依赖是 Playwright。noracu 自身不发任何网络请求;唯一的自动网络行为是首次 `browser_launch` 时一次性下载 Chromium(`NORACU_DISABLE_AUTO_INSTALL=1` 可关闭)。你让浏览器打开的页面当然会被抓取。
|
|
35
|
+
- **按语义动作,而非像素。** 观察返回 accessibility 树(Mac)或 ARIA 树(browser)的带索引列表——是文本,不是截图。动作引用 `element_index`;索引 30 秒后过期,点击前会重新校验目标(browser 校验 tag/name/href/位置,Mac 校验 frontmost app)。索引失效时返回 `element_no_longer_valid`,而不是点错位置。树不够用时才用截图。
|
|
36
|
+
- **一套协议,三个面——不是三套脚本。** 同一个 observe/act 循环驱动 Mac 原生应用、持久化 profile 的浏览器、以及(实验性的)镜像 iPhone。模式跨面一致,只是工具名不同(`mac_*` vs `browser_*`)。
|
|
37
|
+
- **用你真实的会话。** 你已登录的浏览器 profile、你的 Mac 应用——不是托管的 VM。与云端 computer-use 相比:两者没有谁普遍更可靠,只是失败方式不同。noracu 把屏幕留在你本机。
|
|
38
|
+
- **真正能关掉的开关。** `NORACU_READ_ONLY=1`(只观察,默认拒绝)、`NORACU_DISABLE_EVAL=1`、以及默认封禁 `file://`。见 [安全](#安全)。
|
|
39
|
+
- **可验证。** 34 项端到端检查(`npm test`)、CI 跑在 macos-latest、MIT 许可,整个 server 就一个文件:`src/server.mjs`。
|
|
40
|
+
|
|
41
|
+
**不适合的场景:** 你需要多个浏览器标签页或会话(当前仅单会话)、你不在 macOS 上(Mac 与 iPhone 层仅限 macOS;Browser 层跨平台)、或你现在就要用 iPhone 层。如果你只需要浏览器自动化,playwright-mcp 有 noracu v0.1 还没有的 tab 管理和 selector API;noracu 的价值在于:同一个 agent 还得离开浏览器去操作 Mac 应用。
|
|
42
|
+
|
|
43
|
+
## 快速开始
|
|
44
|
+
|
|
45
|
+
第一个该成功的调用是 `mac_ax_state`(或一次 browser 快照)——不是点击,也不是 `phone_*`。上手第一天最常见的失败是:把 macOS 权限授给了 `node` 而不是宿主应用;权限要在**完全退出并重开**宿主后才生效。
|
|
46
|
+
|
|
47
|
+
**1. 安装**(Node ≥ 20;无 postinstall 脚本):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install noracu # server 位于 node_modules/noracu/src/server.mjs
|
|
51
|
+
brew install cliclick # Mac 输入所需(mac_click/type/key/paste)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
或从源码:`git clone https://github.com/sgInnora/noracu.git && cd noracu && npm install && npm test`(加 `NORACU_CI=1` 可跳过依赖硬件的检查)。
|
|
55
|
+
|
|
56
|
+
**2. 接入你的 MCP client——用 `src/server.mjs` 的绝对路径。** 从 Finder/Dock 启动的 GUI 应用不继承你的 shell `PATH`,所以 `node` 二进制也要给绝对路径(`which node`)——裸 `node`、`npx` 或 `noracu` 都需要 Node 在 GUI `PATH` 上,否则会 `ENOENT` 失败。
|
|
57
|
+
|
|
58
|
+
Claude Desktop(`~/Library/Application Support/Claude/claude_desktop_config.json`)/ Cursor(`~/.cursor/mcp.json`):
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"noracu": {
|
|
64
|
+
"command": "/opt/homebrew/bin/node",
|
|
65
|
+
"args": ["/绝对路径/TO/noracu/src/server.mjs"]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Claude Code:`claude mcp add noracu -- /绝对路径/TO/node /绝对路径/noracu/src/server.mjs`
|
|
72
|
+
|
|
73
|
+
用 `which node` 找到你的 node 路径(Apple Silicon:`/opt/homebrew/bin/node`;Intel:`/usr/local/bin/node`;nvm:对应版本路径)。便捷写法,仅当 Node 已在 GUI `PATH` 上时:`"command": "npx", "args": ["-y", "noracu"]`。
|
|
74
|
+
|
|
75
|
+
然后**完全退出宿主应用(含菜单栏图标)并重开**。工具列表能加载出来,说明 transport 通了——此时还不需要任何权限。
|
|
76
|
+
|
|
77
|
+
**3. 把 macOS 权限授给宿主应用,而不是 `node`。** 系统设置 → 隐私与安全性 → **辅助功能(Accessibility)** 和 **屏幕录制(Screen Recording)**(macOS 15 为"屏幕与系统音频录制")→ 勾选宿主(Claude Desktop / Cursor / 你的终端)。noracu 不会出现在这里。授权后退出并重开宿主。
|
|
78
|
+
|
|
79
|
+
**4. 验证。** 让 Finder 在最前,调用 `mac_ax_state`(无参数)——它返回带索引的 accessibility 树;索引有效 30 秒。浏览器冒烟测试(不需要 Accessibility):`browser_launch`(首次运行会下载 Chromium,除非 `NORACU_DISABLE_AUTO_INSTALL=1`)→ `browser_navigate` → `browser_snapshot` → 按索引 `browser_click`。
|
|
80
|
+
|
|
81
|
+
### 排障
|
|
82
|
+
|
|
83
|
+
| 现象 | 原因 / 解决 |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `mac_*` 报错 **-1719** | Accessibility 没授给**宿主**应用——授权后退出并重开宿主 |
|
|
86
|
+
| 截图全黑 | Screen Recording 没授给宿主——授权后退出并重开 |
|
|
87
|
+
| `ENOENT` 提到 cliclick | `brew install cliclick`(或设 `NORACU_CLICLICK`) |
|
|
88
|
+
| GUI 宿主报 `spawn node` / `npx` `ENOENT` | 配置里改用绝对 `node` 路径——nvm/volta/homebrew 的 shim 不在 GUI `PATH` 上 |
|
|
89
|
+
| 首次 `browser_launch` 卡住 | 一次性 Chromium 下载;或自动安装被关闭(`NORACU_DISABLE_AUTO_INSTALL=1`)→ 执行 `npx playwright install chromium` |
|
|
90
|
+
| `phone_*` 报 `imctl unavailable … missing Quartz` | 正常——imctl 未公开;忽略 phone 工具(两个菜单工具仍可用,见下) |
|
|
91
|
+
| 输入的文本被重新空格化(如 "no ra cu") | 有 CJK 输入法在激活——用 `mac_paste` 代替 `mac_type` |
|
|
92
|
+
| `element_no_longer_valid` | 快照超过 30 秒或界面变了——重跑 `mac_ax_state` / `browser_snapshot` 并用新索引 |
|
|
93
|
+
|
|
94
|
+
启动时拒绝 Node 18——请用 Node ≥ 20。Claude Desktop 可能为每个使用面各起一个 noracu 进程;它们共享浏览器 profile,所以同一时间只从一个面启动浏览器。
|
|
95
|
+
|
|
96
|
+
完整实操示例见 [docs/usage.md](docs/usage.md)(英文)。
|
|
97
|
+
|
|
98
|
+
## iPhone 层(实验性)
|
|
99
|
+
|
|
100
|
+
iPhone 层有 14 个 `phone_*` 工具,经 **imctl**(尚未公开的伴生工具)驱动 iPhone Mirroring。
|
|
101
|
+
|
|
102
|
+
- **装了 imctl** 且 iPhone 已镜像到本 Mac 时:14 个工具全部可用。
|
|
103
|
+
- **没有 imctl 时:** 多数 `phone_*` 返回结构化的 `imctl unavailable` 错误。有两个工具在镜像已连接时仍可用,因为它们走 iPhone Mirroring 菜单:`phone_home`(回主屏)和 `phone_app_switcher`(打开 App 切换器)。
|
|
104
|
+
|
|
105
|
+
任何 `phone_*` 工具都需要本 Mac 上有活动的 iPhone Mirroring 会话。imctl 发布前,工具名和行为可能变化。除非你已从作者处拿到 imctl,否则不要设 `NORACU_IMCTL_DIR`。
|
|
106
|
+
|
|
107
|
+
## 环境变量
|
|
108
|
+
|
|
109
|
+
| 变量 | 默认 | 作用 |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| `NORACU_READ_ONLY` | 未设 | `=1` 只观察模式:默认拒绝的白名单仅 12 个观察 + 浏览器生命周期工具;所有 click/type/paste/key/eval 类工具被拒 |
|
|
112
|
+
| `NORACU_DISABLE_EVAL` | 未设 | `=1` 让 `browser_evaluate`(任意 JS)被调用时报错(仍在列表中) |
|
|
113
|
+
| `NORACU_ALLOW_FILE_URL` | 未设 | `=1` 允许 `browser_navigate` 打开 `file://` URL(默认封禁) |
|
|
114
|
+
| `NORACU_DISABLE_AUTO_INSTALL` | 未设 | `=1` 跳过首次 `browser_launch` 的 Chromium 自动安装 |
|
|
115
|
+
| `NORACU_CLICLICK` | `/opt/homebrew/bin/cliclick` | cliclick 二进制路径 |
|
|
116
|
+
| `NORACU_IMCTL_DIR` | 同级 `../imctl` | imctl 检出位置(没有 imctl 就别设) |
|
|
117
|
+
|
|
118
|
+
## 安全
|
|
119
|
+
|
|
120
|
+
noracu 以你 MCP client 的权限运行。它会在本机点击、输入、粘贴、驱动真实浏览器——没有沙箱,没有远程确认弹窗。请在 server 条目上设好下面这些,然后完全退出宿主并重开:
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
"env": { "NORACU_READ_ONLY": "1", "NORACU_DISABLE_EVAL": "1" }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- **`NORACU_READ_ONLY=1`** 是安全的首次运行模式。它拒绝不在 12 项白名单内的一切工具(观察类 + `browser_launch`/`browser_navigate`/`browser_close`);所有 click/type/paste/eval 被拒。`browser_navigate` 在白名单内,仍会加载 URL。
|
|
127
|
+
- **`NORACU_DISABLE_EVAL=1`** 让 `browser_evaluate` 被调用时报错(仍列在 `tools/list` 中)。snapshot、click、type 保留。
|
|
128
|
+
- **`file://` 被拒**,除非 `NORACU_ALLOW_FILE_URL=1`,这样浏览器 agent 无法通过导航到 `~/Documents` 来读本地文件。(仅校验初始导航 URL;重定向不校验——见 SECURITY.md。)
|
|
129
|
+
- **焦点守卫:** 给 `mac_type` / `mac_paste` / `mac_key` 传 `app`,当该应用不在最前时输入被拒。不传 `app` 则守卫关闭。
|
|
130
|
+
- **失效元素保护:** `element_index` 不是坐标。它 30 秒后过期(`element_no_longer_valid`);browser 点击会重校验 tag/name/href/位置,Mac 点击在 frontmost app 自快照以来变化时拒绝执行。
|
|
131
|
+
- **防注入 JXA:** 字符串参数以 argv 数据传给 `osascript`,绝不拼进脚本;唯一的数值参数(AX 读取 depth)在使用前 clamp 到 1–10。有回归测试覆盖。
|
|
132
|
+
|
|
133
|
+
这些开关不会让被观察的内容变得可信。截图文字、accessibility 名称、页面内容都是喂给你模型的**不可信输入**——别让它们覆盖任务指令。完整威胁模型与逐条状态见 [SECURITY.md](SECURITY.md)(英文)。
|
|
134
|
+
|
|
135
|
+
## 工具与文档
|
|
136
|
+
|
|
137
|
+
- 完整工具参考:[docs/tools.md](docs/tools.md)(由运行中的 server 自动生成)
|
|
138
|
+
- 使用实操:[docs/usage.md](docs/usage.md)
|
|
139
|
+
- 架构:[docs/architecture.md](docs/architecture.md)
|
|
140
|
+
|
|
141
|
+
## 路线图
|
|
142
|
+
|
|
143
|
+
- Browser:tab 管理(list / select / close)
|
|
144
|
+
- 动作词汇(browser):`hover`、`set_value`、`wait_for`
|
|
145
|
+
- 快照 diffing 以降低 token 消耗——待测量
|
|
146
|
+
|
|
147
|
+
## 维护
|
|
148
|
+
|
|
149
|
+
个人维护,无 SLA。Mac 与 iPhone 层需 macOS 15+;Browser 层随 Playwright 跨平台。
|
|
150
|
+
|
|
151
|
+
## 许可
|
|
152
|
+
|
|
153
|
+
MIT——见 [LICENSE](LICENSE)。
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
noracu gives an MCP client control over your **real machine** — mouse,
|
|
4
|
+
keyboard, clipboard, browser, and (via iPhone Mirroring) your phone.
|
|
5
|
+
Treat every tool call as privileged.
|
|
6
|
+
|
|
7
|
+
## Threat model
|
|
8
|
+
|
|
9
|
+
| # | Threat | Vector | Status | Mitigation / notes |
|
|
10
|
+
|---|---|---|---|---|
|
|
11
|
+
| 1 | **Prompt injection → arbitrary code execution** | Observed content (web page text, AX tree titles, OCR output) contains instructions that trick the agent into calling tools with malicious arguments | **Mitigated for JXA** | `mac_ax_state` / `mac_menu_command` use static scripts + `osascript` argv — string parameters are data, never script source; the one numeric parameter (AX read depth) is clamped before use (regression-tested in `test/e2e.mjs`) |
|
|
12
|
+
| 2 | **Arbitrary local control** | `mac_click` / `mac_type` / `mac_paste` can operate password managers, banking pages, terminals | **Inherent** | Use client-side confirmation gates for high-stakes targets; see recommendations below. Optional `app` focus guard on `mac_type` / `mac_paste` / `mac_key` refuses input when another app is frontmost; `element_index` clicks additionally refuse (`element_no_longer_valid`) if the frontmost app changed since the snapshot the index came from. |
|
|
13
|
+
| 3 | **Arbitrary JS in browser** | `browser_evaluate` executes any JavaScript in the page context | **Mitigated (kill switch)** | Set `NORACU_DISABLE_EVAL=1`; `browser_evaluate` then returns an error on every call (it stays listed in `tools/list`) |
|
|
14
|
+
| 4 | **Input validation gaps** | Malformed arguments (NaN coordinates, oversized strings, `javascript:` URLs) | **Mitigated** | `validateArgs` runs on every tool call; tools with coordinate/enum/length/URL constraints are checked (others pass through) |
|
|
15
|
+
| 5 | **Screenshot privacy** | `mac_screenshot` / `phone_screenshot` capture the full screen / phone display | **Inherent** | Run in a dedicated macOS user account or virtual display for untrusted automation |
|
|
16
|
+
| 6 | **iPhone operations** | `phone_tap` / `phone_type` can interact with payment and SMS-verification UIs | **Inherent** | Consider disconnecting Apple Pay / keeping Mirroring off for untrusted sessions |
|
|
17
|
+
| 7 | **Supply chain** | malicious PRs / dependency poisoning after open-sourcing | **Partially mitigated** | Review all PRs touching `src/server.mjs` argument handling; lockfile committed; no `postinstall` scripts; the only runtime download is the one-time Playwright `chromium` install triggered by `browser_launch`, which can be disabled with `NORACU_DISABLE_AUTO_INSTALL=1` |
|
|
18
|
+
| 8 | **Local file read via browser** | `browser_navigate` to a `file://` URL, then `browser_read` exfiltrates local files | **Mitigated (default)** | `file://` blocked at the `browser_navigate` argument unless `NORACU_ALLOW_FILE_URL=1` (initial URL only; redirects fall under threat 9) |
|
|
19
|
+
| 9 | **HTTP GET side effects** | navigation triggers logout links, one-time tokens, private-network / cloud-metadata endpoints | **Not mitigated** | No URL blocklist (redirects and DNS rebinding defeat pre-navigation checks); client-side policy |
|
|
20
|
+
| 10 | **Clipboard clobbering by mac_paste** | `mac_paste` overwrites the clipboard with the pasted text | **Partially mitigated** | Previous text content is restored after ~150ms; non-text clipboard contents are lost; async paste targets may race |
|
|
21
|
+
| 11 | **Untrusted observation content** | ARIA snapshots, AX trees, OCR, page text contain attacker-controlled text | **Not mitigated** | The MCP client must treat tool results as data, never as instructions |
|
|
22
|
+
|
|
23
|
+
## Hardening recommendations
|
|
24
|
+
|
|
25
|
+
- Set `NORACU_DISABLE_EVAL=1` unless you actively need `browser_evaluate`.
|
|
26
|
+
- Gate `mac_type` / `mac_paste` behind human confirmation when the frontmost
|
|
27
|
+
app is a password manager or banking site (client-side policy).
|
|
28
|
+
- Run untrusted automation in a dedicated macOS user account with
|
|
29
|
+
Accessibility + Screen Recording granted only to that account's client.
|
|
30
|
+
- Audit tool calls in your MCP client's log — every action is visible there.
|
|
31
|
+
- The persistent browser profile at `~/.noracu/browser-profile` keeps cookies
|
|
32
|
+
across sessions — clear it between untrusted runs.
|
|
33
|
+
- `NORACU_READ_ONLY=1` does not block the one-time `playwright install
|
|
34
|
+
chromium` download triggered by `browser_launch`; set
|
|
35
|
+
`NORACU_DISABLE_AUTO_INSTALL=1` to require a manual install instead.
|
|
36
|
+
|
|
37
|
+
## Reporting a vulnerability
|
|
38
|
+
|
|
39
|
+
Please report security issues privately through a GitHub Security Advisory
|
|
40
|
+
(repo → **Security** → **Advisories** → **Report a vulnerability**). Do not
|
|
41
|
+
open public issues for exploitable bugs. We aim to respond within 72 hours.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# noracu Architecture
|
|
2
|
+
|
|
3
|
+
> A unified three-layer computer-use control plane — Mac / Browser / iPhone — behind a single MCP server (stdio JSON-RPC 2.0).
|
|
4
|
+
|
|
5
|
+
## Design principles
|
|
6
|
+
|
|
7
|
+
1. **Observe/act separation**:
|
|
8
|
+
- Observe: `ax_state` (Accessibility/ARIA tree, token-cheap) or `screenshot` (visual), chosen per step
|
|
9
|
+
- Act: click / drag / scroll / key / type / paste / evaluate, by coordinates or element reference
|
|
10
|
+
2. **Local-first**: no cloud, no API key — every capability comes from local tools
|
|
11
|
+
3. **Layer autonomy**: each layer is largely independent; a missing dependency (e.g. imctl) disables only that layer. The one deliberate cross-layer path is the iPhone layer's Home / App Switcher, which prefer the iPhone Mirroring menu (driven through the Mac layer) and fall back to imctl gestures.
|
|
12
|
+
|
|
13
|
+
The observe/act vocabulary follows the action/observation split popularized by ChatGPT's computer-use, re-implemented on open local tooling.
|
|
14
|
+
|
|
15
|
+
## Layer implementations
|
|
16
|
+
|
|
17
|
+
### Mac layer (9 tools)
|
|
18
|
+
|
|
19
|
+
| Tool | Implementation |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `mac_screenshot` | `screencapture -x` (region crop via `-R`; returns scale + logical-size + `screen:'main'` metadata) |
|
|
22
|
+
| `mac_ax_state` | `osascript` JXA → System Events AX tree (role/title/frame, depth control 1-10) |
|
|
23
|
+
| `mac_click/drag/scroll` | `cliclick` (c:/dd:dm:du:/sc:); click takes `[x,y]` or `element_index`, drag is index-only (`from_index`/`to_index`) — indexes come from the most recent `mac_ax_state` (TTL 30s; stale references raise `element_no_longer_valid`, as does a frontmost-app change since the snapshot) |
|
|
24
|
+
| `mac_key` | `cliclick` — human key names parsed to an arg sequence: a chord like `cmd-shift-v` → `kd:cmd,shift t:v ku:cmd,shift`, a named key like `return` → `kp:return` |
|
|
25
|
+
| `mac_type` | `cliclick t:<text>` (literal text) |
|
|
26
|
+
| `mac_paste` | `pbcopy` writes the clipboard, then `cliclick kd:cmd t:v ku:cmd` (⌘V), then the previous clipboard is restored |
|
|
27
|
+
| `mac_menu_command` | JXA traverses the target app's menu bar → `click` on the menu item (localization-safe) |
|
|
28
|
+
|
|
29
|
+
`mac_key` / `mac_type` / `mac_paste` accept an optional `app`: when set, the tool verifies that app is frontmost before synthesizing input and returns `focus_lost` (sending nothing) if it is not.
|
|
30
|
+
|
|
31
|
+
### Browser layer (12 tools)
|
|
32
|
+
|
|
33
|
+
A single persistent Playwright 1.58 session (one browser, one context), held in a `Map` keyed by `browser_id` that carries one active entry at a time:
|
|
34
|
+
|
|
35
|
+
- Session: `browser_launch` (headless optional) → `browser_navigate` → `browser_close`; `browser_snapshot` accepts an optional `browser_id`, but only one session is live at a time (true multi-session `browser_id` + `tab_id` is a roadmap item)
|
|
36
|
+
- Observe: `browser_snapshot` (`page.ariaSnapshot()` ARIA tree YAML + list of interactive element indices), `browser_screenshot` (fullPage / format png|jpeg / quality / clip; returns DPR + viewport metadata)
|
|
37
|
+
- Act: `browser_click` (element_index / selector / xy; element_index clicks run a tag/name/href/position TOCTOU check first), `browser_fill`, `browser_press_key`, `browser_type`, `browser_scroll`, `browser_evaluate`
|
|
38
|
+
- Read: `browser_read` (page text, optional selector)
|
|
39
|
+
|
|
40
|
+
### iPhone layer (14 tools)
|
|
41
|
+
|
|
42
|
+
imctl subprocess (`python3 -m imctl.cli`, PYTHONPATH pointing at imctl/src) drives iPhone Mirroring. **Experimental**: when imctl or its Python dependencies (pyobjc/Quartz) are absent, most tools return a structured `imctl unavailable` error rather than crashing the server (`phone_home`/`phone_app_switcher` still work via the iPhone Mirroring menu).
|
|
43
|
+
|
|
44
|
+
- Observe: `phone_ocr` (coordinates + text), `phone_screenshot` (captures the Mirroring window region), `phone_find` (locates text)
|
|
45
|
+
- Act: `phone_tap/longpress/swipe` (normalized 0-1 coordinates), `phone_home` / `phone_app_switcher` (iPhone Mirroring menu first, imctl gesture fallback), `phone_scroll`, `phone_type` (clipboard-based, IME-safe), `phone_key`
|
|
46
|
+
- Session: `phone_status`, `phone_launch`, `phone_open` (Spotlight + OCR + tap)
|
|
47
|
+
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
| Environment variable | Default | Description |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `NORACU_IMCTL_DIR` | `../imctl` beside the repo root (`src/../../imctl`) | imctl checkout location |
|
|
53
|
+
| `NORACU_CLICLICK` | `/opt/homebrew/bin/cliclick` | path to cliclick |
|
|
54
|
+
| `NORACU_READ_ONLY` | unset (full control) | `=1` observation-only mode: a default-deny allowlist of 12 tools — the observe tools (`mac_screenshot`, `mac_ax_state`, `browser_snapshot`, `browser_screenshot`, `browser_read`, `phone_status`, `phone_screenshot`, `phone_ocr`, `phone_find`) plus browser session lifecycle (`browser_launch`, `browser_navigate`, `browser_close`). `browser_navigate` still issues a page-load request. All input-synthesis tools (click/drag/type/key/paste/tap/swipe/evaluate/…) are rejected |
|
|
55
|
+
| `NORACU_DISABLE_EVAL` | unset (enabled) | `=1` makes `browser_evaluate` return an error on every call (kill switch for arbitrary JS execution; it stays listed) |
|
|
56
|
+
| `NORACU_DISABLE_AUTO_INSTALL` | unset (auto-install) | `=1` stops `browser_launch` from auto-running `playwright install chromium` on a clean machine; returns an error prompting a manual install instead |
|
|
57
|
+
| `NORACU_ALLOW_FILE_URL` | unset (blocked) | `=1` permits `browser_navigate` to open `file://` URLs (blocked by default to avoid local-file exfiltration via the browser layer) |
|
|
58
|
+
|
|
59
|
+
## Known limitations (v0.1)
|
|
60
|
+
|
|
61
|
+
- Mac-layer actions spawn subprocesses (cliclick/osascript); per-action latency is tens of milliseconds (subprocess spawn; not benchmarked in this release) — a resident FFI process could cut it (see roadmap)
|
|
62
|
+
- No axState diffing (every observation is a full snapshot)
|
|
63
|
+
- Single browser session (no tab-level management)
|
|
64
|
+
- The iPhone layer requires iPhone Mirroring to be online; OCR accuracy depends on screen content
|
|
65
|
+
- macOS-only (Mac/iPhone layers); the Browser layer is cross-platform
|
|
66
|
+
|
|
67
|
+
## Roadmap (v2 candidates)
|
|
68
|
+
|
|
69
|
+
- ~~element_index coordinate system~~ (landed in v0.1.0, both Mac and Browser layers); next: monotonic index reuse across snapshots (prerequisite for diffing)
|
|
70
|
+
- axState diffing (incremental observation, saves tokens)
|
|
71
|
+
- Browser tool gaps (select_text/set_value/hover/wait_for…) + tab management
|
|
72
|
+
- koffi FFI resident process (direct CGEvent/AXUIElement calls)
|
|
73
|
+
- Multi-session architecture (browser_id + tab_id)
|
|
74
|
+
- Security hardening: sensitive-app foreground detection, action audit log
|
package/docs/tools.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Tool reference
|
|
2
|
+
|
|
3
|
+
> Auto-generated from the running server (`node scripts/gen-tools.mjs`) — always in sync with the implementation.
|
|
4
|
+
> noracu v0.1.0 · 35 tools
|
|
5
|
+
|
|
6
|
+
## Mac layer (computer-use)
|
|
7
|
+
|
|
8
|
+
*cliclick · screencapture · JXA System Events*
|
|
9
|
+
|
|
10
|
+
| Tool | Arguments | Description |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| `mac_screenshot` | region:object? | Capture the Mac screen as PNG base64, optionally cropped to region {x,y,w,h}. Returns {scale, logical_size, screen:'main'} for the main display; click coordinates are in points (pixels / scale). |
|
|
13
|
+
| `mac_ax_state` | app:string?, depth:number? | Read the foreground app's accessibility tree (element index/role/title/value/frame; indexes feed mac_click/mac_drag for 30s); menu bar pruned by default (use mac_menu_command); optional app name and depth (default 3, clamped 1-10). Returns ax_empty if the app has no AX-visible window (not frontmost / another Space). |
|
|
14
|
+
| `mac_click` | x:number?, y:number?, element_index:integer?, button:string?, count:number? | Click at {x,y} or on a UI element, with optional button (left\|right\|middle) and count. Use element_index from the latest mac_ax_state (valid 30s); returns element_no_longer_valid when stale. |
|
|
15
|
+
| `mac_drag` | **from_index**:integer, **to_index**:integer | Drag from one UI element to another (move windows, sliders, selections). Use from_index/to_index from the latest mac_ax_state (valid 30s); returns element_no_longer_valid when stale. |
|
|
16
|
+
| `mac_scroll` | direction:string?, pages:number? | Scroll the frontmost window up/down/left/right by a number of pages (default 1); moves the pointer over that window first. |
|
|
17
|
+
| `mac_key` | key:string?, app:string? | Press a key or modifier chord, e.g. 'return', 'esc', 'cmd-c', 'cmd-shift-v', 'arrow-up'. Pass app to refuse input unless that app is frontmost (focus guard). |
|
|
18
|
+
| `mac_type` | text:string?, app:string? | Type text at the current focus without modifier keys. Pass app to refuse input unless that app is frontmost (focus guard). Not IME-safe: with a CJK input method active, keystrokes may be re-segmented — use mac_paste for reliable text entry. |
|
|
19
|
+
| `mac_paste` | text:string?, app:string? | Write text to the clipboard and paste it with Cmd+V. Pass app to refuse input unless that app is frontmost (focus guard). |
|
|
20
|
+
| `mac_menu_command` | app:string?, command:string? | Run an app's menu bar command via accessibility traversal (locale-safe). |
|
|
21
|
+
|
|
22
|
+
## Browser layer (browser-use)
|
|
23
|
+
|
|
24
|
+
*Playwright*
|
|
25
|
+
|
|
26
|
+
| Tool | Arguments | Description |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `browser_launch` | headless:boolean? | Launch the browser with a persistent profile (headed by default; pass headless:true for headless). |
|
|
29
|
+
| `browser_navigate` | url:string? | Navigate the current tab to an http(s) URL. file:// is disabled unless NORACU_ALLOW_FILE_URL=1. |
|
|
30
|
+
| `browser_snapshot` | browser_id:string? | Return the page's ARIA accessibility snapshot plus a clickable element index list (indexes feed browser_click). |
|
|
31
|
+
| `browser_screenshot` | fullPage:boolean?, format:string?, quality:number?, clip:object? | Screenshot the page with optional fullPage, format png\|jpeg, quality (1-100, jpeg) and clip {x,y,width,height}; returns the image plus DPR/viewport metadata. |
|
|
32
|
+
| `browser_click` | element_index:integer?, selector:string?, xy:array?, button:string? | Click a page element, preferring element_index; selector is a fallback, xy pixel coordinates not recommended. Use element_index from the latest browser_snapshot (valid 30s); returns element_no_longer_valid when stale. |
|
|
33
|
+
| `browser_fill` | selector:string?, value:string? | Fill an input field matched by selector; fill replaces the whole value (browser_type appends keystrokes). |
|
|
34
|
+
| `browser_press_key` | key:string? | Press a key or chord, e.g. 'Enter' or 'Control+A'. |
|
|
35
|
+
| `browser_type` | text:string? | Type text via the keyboard into the focused element; type appends keystrokes (browser_fill replaces the whole value). |
|
|
36
|
+
| `browser_scroll` | direction:string?, pages:number? | Scroll the page up or down by a number of pages. |
|
|
37
|
+
| `browser_read` | selector:string? | Read the page's visible text, optionally scoped to a selector. |
|
|
38
|
+
| `browser_evaluate` | js:string? | Evaluate JavaScript in the page and return the result. |
|
|
39
|
+
| `browser_close` | — | Close the browser. |
|
|
40
|
+
|
|
41
|
+
## iPhone layer (device-use)
|
|
42
|
+
|
|
43
|
+
*imctl / iPhone Mirroring*
|
|
44
|
+
|
|
45
|
+
| Tool | Arguments | Description |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `phone_status` | — | EXPERIMENTAL. Requires imctl (not yet public). Return the iPhone Mirroring session status as JSON. |
|
|
48
|
+
| `phone_launch` | — | EXPERIMENTAL. Requires imctl (not yet public). Ensure iPhone Mirroring is running and brought to the front. |
|
|
49
|
+
| `phone_screenshot` | — | EXPERIMENTAL. Requires imctl (not yet public). Screenshot the iPhone screen (the Mirroring window region). |
|
|
50
|
+
| `phone_ocr` | — | EXPERIMENTAL. Requires imctl (not yet public). OCR the iPhone screen into text with coordinates. |
|
|
51
|
+
| `phone_find` | text:string? | EXPERIMENTAL. Requires imctl (not yet public). Locate text on the iPhone screen. |
|
|
52
|
+
| `phone_tap` | x:number?, y:number? | EXPERIMENTAL. Requires imctl (not yet public). Tap at {x, y} in normalized 0-1 coordinates. |
|
|
53
|
+
| `phone_longpress` | x:number?, y:number?, duration:number? | EXPERIMENTAL. Requires imctl (not yet public). Long-press at {x, y} for an optional duration in seconds. |
|
|
54
|
+
| `phone_swipe` | from:array?, to:array?, ms:number? | EXPERIMENTAL. Requires imctl (not yet public). Swipe from [x,y] to [x,y] over an optional duration in ms. |
|
|
55
|
+
| `phone_home` | — | EXPERIMENTAL. Go to the iPhone Home Screen via the iPhone Mirroring menu (works without imctl when the mirror is connected); falls back to an imctl swipe. |
|
|
56
|
+
| `phone_app_switcher` | — | EXPERIMENTAL. Open the iPhone App Switcher via the iPhone Mirroring menu (works without imctl when the mirror is connected); falls back to an imctl gesture. |
|
|
57
|
+
| `phone_scroll` | direction:string?, distance:number? | EXPERIMENTAL. Requires imctl (not yet public). Scroll up or down by a distance. |
|
|
58
|
+
| `phone_type` | text:string? | EXPERIMENTAL. Requires imctl (not yet public). Type text via the clipboard (IME-safe). |
|
|
59
|
+
| `phone_key` | key:string? | EXPERIMENTAL. Requires imctl (not yet public). Press a key: return, delete, up, down, left, or right. |
|
|
60
|
+
| `phone_open` | app:string? | EXPERIMENTAL. Requires imctl (not yet public). Open an app via Spotlight search plus OCR-guided tap. |
|
|
61
|
+
|