codex-computer-use-mcp 0.1.0 → 0.2.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/ARCHITECTURE.md +134 -0
- package/CONTRIBUTING.md +16 -10
- package/MIGRATION.md +77 -0
- package/PROOF.md +114 -67
- package/README.md +94 -116
- package/SECURITY.md +43 -22
- package/dist/audit.d.ts +15 -14
- package/dist/audit.js +1 -1
- package/dist/audit.js.map +1 -1
- package/dist/config.d.ts +8 -8
- package/dist/config.js +8 -103
- package/dist/config.js.map +1 -1
- package/dist/direct-broker.d.ts +57 -0
- package/dist/direct-broker.js +638 -0
- package/dist/direct-broker.js.map +1 -0
- package/dist/direct-service.d.ts +31 -0
- package/dist/direct-service.js +345 -0
- package/dist/direct-service.js.map +1 -0
- package/dist/lock.d.ts +1 -0
- package/dist/lock.js +32 -3
- package/dist/lock.js.map +1 -1
- package/dist/mcp-server.js +46 -105
- package/dist/mcp-server.js.map +1 -1
- package/dist/system.d.ts +0 -1
- package/dist/system.js +12 -37
- package/dist/system.js.map +1 -1
- package/dist/tools.d.ts +78 -0
- package/dist/tools.js +192 -0
- package/dist/tools.js.map +1 -0
- package/integrations/pi/README.md +41 -27
- package/integrations/pi/index.ts +110 -123
- package/integrations/pi/mcp.json.example +3 -3
- package/npm-shrinkwrap.json +3278 -48
- package/package.json +40 -13
- package/dist/policy.d.ts +0 -42
- package/dist/policy.js +0 -428
- package/dist/policy.js.map +0 -1
- package/dist/prompt.d.ts +0 -62
- package/dist/prompt.js +0 -119
- package/dist/prompt.js.map +0 -1
- package/dist/runner.d.ts +0 -55
- package/dist/runner.js +0 -494
- package/dist/runner.js.map +0 -1
- package/dist/service.d.ts +0 -17
- package/dist/service.js +0 -382
- package/dist/service.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,192 +1,170 @@
|
|
|
1
1
|
# Codex Computer Use MCP
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Version 0.2.0 exposes the official signed macOS Computer Use capabilities as direct typed tools for Pi and MCP clients. The calling agent chooses every tool and argument itself.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The primary path has:
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- per-app kernel locks;
|
|
13
|
-
- foreground/focus telemetry with fail-closed background guarantees;
|
|
14
|
-
- output schemas, call budgets, timeout and process-group cleanup;
|
|
15
|
-
- sanitized, mode-`0600` metadata audits.
|
|
7
|
+
- no nested model call;
|
|
8
|
+
- no model-generated action plan;
|
|
9
|
+
- no subagent;
|
|
10
|
+
- no prompt sent to Codex;
|
|
11
|
+
- no separate model-token usage.
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
It does use OpenAI's signed `codex app-server` as the official host for the bundled Computer Use MCP client. The current official `mcpServer/tool/call` API requires a loaded thread identifier, so the bridge creates an empty, in-memory, zero-turn context (`ephemeral: true`, `turns: []`, `path: null`). It never calls `turn/start` and fails closed if any `turn/*` or `item/*` model activity appears.
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
> **Independent project.** This is not an OpenAI product and is not endorsed by OpenAI. The app-server API is marked experimental and fixed ChatGPT bundle paths may change.
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
## Direct tools
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
Pi registers namespaced tools to avoid collisions with Pi's built-ins. The MCP server exposes the upstream method names.
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
| Pi tool | MCP method | No-permissions | Purpose |
|
|
22
|
+
|---|---|---:|---|
|
|
23
|
+
| `computer_use_list_apps` | `list_apps` | yes | List apps known to official Computer Use |
|
|
24
|
+
| `computer_use_get_app_state` | `get_app_state` | yes | Read accessibility state and imagery for one app |
|
|
25
|
+
| `computer_use_click` | `click` | yes | Click an element or screenshot coordinates |
|
|
26
|
+
| `computer_use_perform_secondary_action` | `perform_secondary_action` | yes | Invoke a named accessibility action |
|
|
27
|
+
| `computer_use_set_value` | `set_value` | yes | Assign an accessibility value |
|
|
28
|
+
| `computer_use_select_text` | `select_text` | yes | Select text or place the cursor |
|
|
29
|
+
| `computer_use_scroll` | `scroll` | yes | Scroll an element |
|
|
30
|
+
| `computer_use_drag` | `drag` | yes | Drag between screenshot coordinates |
|
|
31
|
+
| `computer_use_press_key` | `press_key` | yes | Send a key or key combination |
|
|
32
|
+
| `computer_use_type_text` | `type_text` | yes | Type literal text |
|
|
27
33
|
|
|
28
|
-
|
|
34
|
+
Pi—not a nested planner—must call `computer_use_get_app_state`, choose a current element identifier or coordinates, execute one action, and inspect again when needed.
|
|
29
35
|
|
|
30
|
-
##
|
|
36
|
+
## Authorization policy: durable no-permissions
|
|
31
37
|
|
|
32
|
-
-
|
|
33
|
-
- Node.js 22 or newer
|
|
34
|
-
- the official ChatGPT macOS app at `/Applications/ChatGPT.app`
|
|
35
|
-
- a Codex account/session available to the app-bundled Codex CLI
|
|
36
|
-
- required first-party OpenAI app approvals and macOS privacy permissions, completed in their official UI
|
|
38
|
+
`no-permissions` has one precise meaning here: **the wrapper asks no permission questions and exposes all ten official actions**. It is the only mode and the durable default. There is no safe/full selector, config file, environment override, slash command, CLI switch, per-call elevation, app allowlist, intent classifier, task schema, per-action confirmation, special-case app policy, or method gate.
|
|
37
39
|
|
|
38
|
-
The server never
|
|
40
|
+
The app-server runtime is also created with `approvalPolicy: "never"`. The client does not advertise an elicitation UI. If the official downstream service unexpectedly requests elicitation, the bridge silently declines it; it never opens a prompt and never self-accepts. Any persistent first-party access required by Computer Use must therefore already be configured in the official ChatGPT app.
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
No-permissions does **not** bypass:
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
- first-party OpenAI app approvals or sensitive-action prompts;
|
|
45
|
+
- macOS Screen Recording, Accessibility, or TCC controls;
|
|
46
|
+
- strict OpenAI Team ID and code-signature checks;
|
|
47
|
+
- exact upstream ten-tool schema verification;
|
|
48
|
+
- canonical app identity resolution and per-user/per-app kernel locks shared across Pi and MCP state roots;
|
|
49
|
+
- focus telemetry, timeouts, verified process-tree cleanup, or private audit logging.
|
|
43
50
|
|
|
44
|
-
|
|
45
|
-
npm install -g codex-computer-use-mcp
|
|
46
|
-
codex-computer-use-mcp --status
|
|
47
|
-
```
|
|
51
|
+
## Why the signed app-server is required
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
Calling the signed `SkyComputerUseClient mcp` binary directly from an ordinary Pi/Node parent successfully initializes and lists all ten schemas, but real calls are rejected with:
|
|
50
54
|
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
cd codex-computer-use-mcp
|
|
54
|
-
npm ci
|
|
55
|
-
npm test
|
|
56
|
-
npm run build
|
|
57
|
-
node dist/mcp-server.js --status
|
|
55
|
+
```text
|
|
56
|
+
Computer Use server error -10000: Sender process is not authenticated
|
|
58
57
|
```
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
## Tools
|
|
63
|
-
|
|
64
|
-
### `background_computer_use`
|
|
59
|
+
OpenAI's app-server exposes a documented `mcpServer/tool/call` endpoint. That endpoint calls a configured MCP tool directly; no model turn is required. Running it from the signed app-bundled binary preserves the official responsible-process/authentication chain without injection, re-signing, TCC changes, private socket emulation, or credential extraction.
|
|
65
60
|
|
|
66
|
-
|
|
67
|
-
|---|---|---|
|
|
68
|
-
| `list` | List apps visible to official Computer Use | Safe or full |
|
|
69
|
-
| `inspect` | Read one app | Full only |
|
|
70
|
-
| `act` | Perform a concrete app task | Full only |
|
|
71
|
-
| `dictionary_lookup` | Constrained local Apple Dictionary workflow | Full only |
|
|
61
|
+
See [`ARCHITECTURE.md`](ARCHITECTURE.md) for source links and the full restriction inventory.
|
|
72
62
|
|
|
73
|
-
|
|
63
|
+
## Requirements
|
|
74
64
|
|
|
75
|
-
|
|
65
|
+
- macOS
|
|
66
|
+
- Node.js 22 or newer
|
|
67
|
+
- official ChatGPT macOS app at `/Applications/ChatGPT.app`
|
|
68
|
+
- official Computer Use component installed and its first-party permissions configured
|
|
76
69
|
|
|
77
|
-
|
|
70
|
+
The direct bridge starts app-server with a new private `CODEX_HOME` containing no account credentials and only one configured MCP server: official Computer Use. It does not inherit the user's Codex MCP servers, plugins, history, memories, API keys, or auth file. It selects a non-websocket dummy model provider bound to unreachable loopback, disables plugin/remote-control features, and never starts a turn; this prevents app-server model prewarm or Responses API traffic.
|
|
78
71
|
|
|
79
|
-
|
|
72
|
+
### Locked-screen limitation
|
|
80
73
|
|
|
81
|
-
|
|
74
|
+
Version 0.2.0 supports direct local calls in an unlocked macOS session. It does not support window or accessibility actions after the Mac locks.
|
|
82
75
|
|
|
83
|
-
|
|
76
|
+
OpenAI's [locked Computer Use](https://developers.openai.com/codex/app/computer-use#use-computer-use-while-your-mac-is-locked) is limited to active, trusted ChatGPT turns started from a connected device. It does not authorize other apps or local processes to unlock the Mac. This package uses local zero-turn dispatch, so targeted calls can fail with official error `-10005` while the Mac is locked. Support for locked local use remains a follow-up and is not part of version 0.2.0.
|
|
84
77
|
|
|
85
|
-
|
|
78
|
+
## Pi integration
|
|
86
79
|
|
|
87
|
-
|
|
80
|
+
Install the exact release from npm:
|
|
88
81
|
|
|
89
|
-
|
|
82
|
+
```bash
|
|
83
|
+
pi install npm:codex-computer-use-mcp@0.2.0
|
|
84
|
+
```
|
|
90
85
|
|
|
91
|
-
|
|
86
|
+
To evaluate a source checkout instead:
|
|
92
87
|
|
|
93
88
|
```bash
|
|
94
|
-
|
|
89
|
+
npm ci
|
|
90
|
+
npm run build
|
|
91
|
+
pi -ne -e /absolute/path/to/codex-computer-use-mcp/integrations/pi/index.ts
|
|
95
92
|
```
|
|
96
93
|
|
|
97
|
-
|
|
94
|
+
Command:
|
|
98
95
|
|
|
99
|
-
```
|
|
100
|
-
|
|
96
|
+
```text
|
|
97
|
+
/computer-use-status
|
|
101
98
|
```
|
|
102
99
|
|
|
103
|
-
|
|
100
|
+
The native Pi adapter is the primary product path. It always registers all ten typed tools directly. It exposes no mode-changing command and no approval UI.
|
|
104
101
|
|
|
105
|
-
## MCP
|
|
102
|
+
## MCP server
|
|
106
103
|
|
|
107
|
-
|
|
104
|
+
Running the binary without arguments starts a stdio MCP server exposing the same ten direct methods plus `computer_use_status`:
|
|
108
105
|
|
|
109
106
|
```bash
|
|
110
|
-
|
|
107
|
+
node dist/mcp-server.js
|
|
111
108
|
```
|
|
112
109
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
### Pi MCP gateway
|
|
116
|
-
|
|
117
|
-
Alternatively add this to `~/.pi/agent/mcp.json`. `directTools: false` keeps the capability intentional instead of injecting it into every turn:
|
|
110
|
+
For Pi's generic MCP gateway, keep `directTools: false` so this powerful surface remains intentional:
|
|
118
111
|
|
|
119
112
|
```json
|
|
120
113
|
{
|
|
121
114
|
"mcpServers": {
|
|
122
|
-
"
|
|
123
|
-
"command": "
|
|
124
|
-
"args": ["
|
|
115
|
+
"computer-use": {
|
|
116
|
+
"command": "node",
|
|
117
|
+
"args": ["/absolute/path/to/codex-computer-use-mcp/dist/mcp-server.js"],
|
|
125
118
|
"lifecycle": "lazy",
|
|
126
|
-
"requestTimeoutMs":
|
|
119
|
+
"requestTimeoutMs": 180000,
|
|
127
120
|
"directTools": false
|
|
128
121
|
}
|
|
129
122
|
}
|
|
130
123
|
}
|
|
131
124
|
```
|
|
132
125
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
### Claude Desktop
|
|
136
|
-
|
|
137
|
-
```json
|
|
138
|
-
{
|
|
139
|
-
"mcpServers": {
|
|
140
|
-
"codex-computer-use": {
|
|
141
|
-
"command": "npx",
|
|
142
|
-
"args": ["-y", "codex-computer-use-mcp@0.1.0"]
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
For a source checkout, replace `npx` and its arguments with `node` and the absolute path to `dist/mcp-server.js`.
|
|
149
|
-
|
|
150
|
-
## How it works
|
|
126
|
+
The generic MCP server exposes the same no-permissions behavior: no wrapper approval UI and all ten methods. Unexpected downstream elicitations are silently declined; configure persistent first-party app access only in official ChatGPT Computer Use settings.
|
|
151
127
|
|
|
152
|
-
|
|
153
|
-
MCP client
|
|
154
|
-
→ this local server
|
|
155
|
-
→ ChatGPT.app's signed app-bundled Codex CLI
|
|
156
|
-
→ signed SkyComputerUseClient
|
|
157
|
-
→ official Computer Use service
|
|
158
|
-
→ target macOS app
|
|
159
|
-
```
|
|
128
|
+
## Security and privacy
|
|
160
129
|
|
|
161
|
-
|
|
130
|
+
Each call:
|
|
162
131
|
|
|
163
|
-
|
|
132
|
+
1. validates typed arguments;
|
|
133
|
+
2. applies the single durable no-permissions policy with no mode or prompt branch;
|
|
134
|
+
3. resolves a target to a canonical installed bundle ID;
|
|
135
|
+
4. acquires a fixed per-user/per-app kernel lock shared across all supported clients and state roots;
|
|
136
|
+
5. starts global focus telemetry;
|
|
137
|
+
6. verifies fixed OpenAI-signed broker/client binaries;
|
|
138
|
+
7. starts a credential-free isolated app-server process tree with model transport disabled;
|
|
139
|
+
8. requires explicit `ephemeral: true`, `turns: []`, and `path: null` attestation;
|
|
140
|
+
9. verifies the exact upstream ten-tool inventory and schemas;
|
|
141
|
+
10. issues exactly one `mcpServer/tool/call`;
|
|
142
|
+
11. rejects any model-turn notification, including during teardown;
|
|
143
|
+
12. combines partial-preserving ancestry enumeration with private-working-directory ownership recovery, then freezes, terminates, and verifies the app-server plus separately grouped or reparented helpers; finally it removes temporary state, releases the lock, and writes a content-safe audit with separate broker/lease cleanup evidence.
|
|
164
144
|
|
|
165
|
-
|
|
145
|
+
Focus checks are detection/completion criteria, not a preventive macOS sandbox. If the target becomes frontmost, the call is reported as failed even though an individual official action may already have completed.
|
|
166
146
|
|
|
167
|
-
|
|
147
|
+
Tool results may contain visible target-app text or screenshots because that is the purpose of Computer Use. They return only to the invoking Pi/MCP client. Audits never retain arguments, typed values, screenshots, app-state payloads, result text, prompts, credentials, or tokens—only bounded metadata such as method, canonical/hashed app identity, byte counts, content types, outcome, focus, broker version, and zero-turn evidence.
|
|
168
148
|
|
|
169
|
-
|
|
149
|
+
## State and migration
|
|
170
150
|
|
|
171
|
-
|
|
151
|
+
Audit state defaults to `~/.direct-computer-use`; override with `CODEX_COMPUTER_USE_HOME`. Permission policy is not read from that agent-writable path: no-permissions is compiled as the only interface. Legacy `config.json` files are ignored.
|
|
172
152
|
|
|
173
|
-
|
|
174
|
-
- App updates may change bundled paths, CLI flags, plugin schemas, or signing layout.
|
|
175
|
-
- First-party approvals may need completion in an official interactive OpenAI session.
|
|
176
|
-
- Browser-host Computer Use is not exposed; this project targets native macOS apps.
|
|
177
|
-
- In full-permissions mode, a target/method/focus/cleanup failure is detected after streamed events; mutations may already have occurred. Inspect and restore manually before retrying.
|
|
178
|
-
- Secure config-path failures may prevent audit creation because the server refuses to write through an untrusted path.
|
|
153
|
+
See [`MIGRATION.md`](MIGRATION.md) for version 0.1 migration, source acceptance, rollback, and conflict avoidance.
|
|
179
154
|
|
|
180
155
|
## Development
|
|
181
156
|
|
|
182
157
|
```bash
|
|
183
158
|
npm ci
|
|
184
159
|
npm run check
|
|
160
|
+
npm run check:pi
|
|
185
161
|
npm test
|
|
186
162
|
npm run build
|
|
163
|
+
npm audit --omit=dev
|
|
164
|
+
npm pack --dry-run
|
|
187
165
|
```
|
|
188
166
|
|
|
189
|
-
|
|
167
|
+
Registry dependency tarballs are exact-pinned with integrity and the package includes `npm-shrinkwrap.json`.
|
|
190
168
|
|
|
191
169
|
See [`PROOF.md`](PROOF.md), [`SECURITY.md`](SECURITY.md), and [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
192
170
|
|
package/SECURITY.md
CHANGED
|
@@ -2,37 +2,58 @@
|
|
|
2
2
|
|
|
3
3
|
## Reporting a vulnerability
|
|
4
4
|
|
|
5
|
-
Do not open a public issue for a vulnerability that could weaken
|
|
5
|
+
Do not open a public issue for a vulnerability that could weaken signing, schema verification, permission-mode dispatch, first-party approval handling, app identity, locking, focus telemetry, cleanup, or audit integrity.
|
|
6
6
|
|
|
7
|
-
Use GitHub
|
|
7
|
+
Use GitHub private vulnerability reporting. Include the affected commit/version, the smallest non-sensitive reproduction, expected versus observed behavior, and whether an official action completed before failure.
|
|
8
8
|
|
|
9
|
-
Never include credentials, tokens, private
|
|
9
|
+
Never include credentials, tokens, customer/private content, sensitive screenshots, raw app-state payloads, elicitation contents, or private audit files.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Architecture boundary
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Raw Pi/Node invocation of the signed Computer Use MCP helper is not an authenticated responsible-process path for real calls. The bridge therefore uses the official signed app-server `mcpServer/tool/call` API.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
The API requires a loaded thread ID. The bridge creates an empty, pathless, ephemeral context solely to own the MCP runtime and requires the response to explicitly attest `ephemeral: true`, `path: null`, and `turns: []` before dispatch. It never calls `turn/start`; any `turn/*` or `item/*` event—including one received during teardown—is a fatal architecture violation. The temporary `CODEX_HOME` contains no auth file, API key, user MCPs, plugins, history, memories, or account configuration. A non-websocket dummy model provider points at unreachable loopback, and plugin/remote-control features are disabled, preventing app-server model prewarm and Responses API traffic.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
- full-permissions mode is broad wrapper authorization, not a preventive per-app sandbox;
|
|
19
|
-
- post-dispatch validation can fail completion and surface cleanup risk, but cannot undo a mutation;
|
|
20
|
-
- official OpenAI app approvals, sensitive-action prompts, and macOS privacy controls remain authoritative.
|
|
17
|
+
This is direct tool dispatch—not model orchestration.
|
|
21
18
|
|
|
22
19
|
## Security invariants
|
|
23
20
|
|
|
24
|
-
1. Only
|
|
25
|
-
2. Both binaries
|
|
26
|
-
3.
|
|
27
|
-
4.
|
|
28
|
-
5.
|
|
29
|
-
6.
|
|
30
|
-
7.
|
|
31
|
-
8. Same-app work
|
|
32
|
-
9.
|
|
33
|
-
10.
|
|
34
|
-
11.
|
|
21
|
+
1. Only fixed app-bundled Codex and Computer Use client paths are allowed in production.
|
|
22
|
+
2. Both binaries pass strict code-signature verification and OpenAI Team ID `2DC432GLL2` checks before dispatch.
|
|
23
|
+
3. The helper's exact ten method names and input schemas match the pinned expected inventory before every call.
|
|
24
|
+
4. `no-permissions` is the only wrapper policy: all ten methods are exposed and no wrapper permission prompt is opened.
|
|
25
|
+
5. There is no config file, environment override, command, tool argument, per-call branch, or alternate safe/full route that an agent can select.
|
|
26
|
+
6. App-server uses `approvalPolicy: "never"`; elicitation capability is disabled and unexpected first-party requests are silently declined, never prompted or self-accepted. Persistent official app access remains authoritative and must be configured outside this wrapper.
|
|
27
|
+
7. Target selectors resolve to canonical installed bundle IDs before dispatch.
|
|
28
|
+
8. Same-app work is excluded across native Pi, generic MCP, and custom state roots with one fixed per-user kernel `lockf` lease namespace.
|
|
29
|
+
9. Target focus events, periodic samples, watcher health, queued ASN resolution, and final state are checked. This is post-action detection, not a preventive OS sandbox.
|
|
30
|
+
10. One direct request emits one official `mcpServer/tool/call`; no model turn, subagent, shell, web, plugin, prompt, or reachable model transport is available.
|
|
31
|
+
11. App-server and helper share one private per-call working directory. Cleanup combines strict ancestry enumeration (preserving partial results) with working-directory ownership recovery, freezes processes to a stable set, kills every owned process, awaits stdio closure, and verifies exit. This still finds a helper reparented by an early app-server exit; enumeration/freeze/exit uncertainty is fatal.
|
|
32
|
+
12. Protocol JSONL is bounded before an unterminated line can exceed 8 MB in memory.
|
|
33
|
+
13. Per-call `CODEX_HOME` and work directories are mode-private and recursively removed.
|
|
34
|
+
14. Only validated text/image result blocks cross to the invoking client. No full-result spill file is written.
|
|
35
|
+
15. Audits contain metadata only, including separate `brokerCleanupVerified` and `appLeaseReleased` evidence; lease-release failure changes the audited outcome before surfacing. Arguments, values, screenshots, app-state text, result content, prompts, approvals, credentials, and tokens are forbidden.
|
|
36
|
+
16. Policy rejections are audited; audit failure is fatal once a secure state path exists.
|
|
37
|
+
17. Runtime and development dependency tarballs are exact-pinned with integrity in `npm-shrinkwrap.json`.
|
|
38
|
+
|
|
39
|
+
## Permission semantics
|
|
40
|
+
|
|
41
|
+
### No-permissions
|
|
42
|
+
|
|
43
|
+
All ten official methods and arbitrary resolvable app targets are available without wrapper permission prompts. The name means “ask no permission,” not “disable tools.” This is broad authorization. It does not imply that actions are reversible or that focus detection can prevent an already dispatched action. App-state reads can expose visible sensitive information to the calling model.
|
|
44
|
+
|
|
45
|
+
The mode is compiled as the sole policy. Agent-writable audit state and legacy configuration files cannot change it, and there is no CLI/slash/tool/environment mode selector.
|
|
46
|
+
|
|
47
|
+
## Elicitations
|
|
48
|
+
|
|
49
|
+
Neither Pi nor stdio MCP advertises or renders an approval UI. The bridge starts app-server with `approvalPolicy: "never"`. An unexpected downstream elicitation is counted, silently declined, and never self-accepted. Persistent first-party app access belongs in official ChatGPT Computer Use settings.
|
|
50
|
+
|
|
51
|
+
## Visible-content warning
|
|
52
|
+
|
|
53
|
+
Computer Use is designed to return app state and screenshots. Do not point it at apps containing credentials, payment data, private messages, or customer secrets unless the user explicitly requires that context and accepts model exposure. Audit safety does not make target-app content non-sensitive.
|
|
35
54
|
|
|
36
55
|
## Supported versions
|
|
37
56
|
|
|
38
|
-
Only the latest release is supported.
|
|
57
|
+
Only the latest approved release is supported. Version 0.2.0 supports direct local calls in an unlocked macOS session. Targeted local calls while the Mac is locked are not supported.
|
|
58
|
+
|
|
59
|
+
App-server is experimental and bundle paths or schemas can change. Drift fails closed until reviewed.
|
package/dist/audit.d.ts
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
|
+
import type { PermissionMode } from "./config.ts";
|
|
2
|
+
import type { DirectMethod } from "./tools.ts";
|
|
1
3
|
export interface AuditRecord {
|
|
2
4
|
timestamp: string;
|
|
3
5
|
runId: string;
|
|
4
|
-
|
|
5
|
-
permissionMode:
|
|
6
|
+
method: DirectMethod | "invalid_request";
|
|
7
|
+
permissionMode: PermissionMode;
|
|
6
8
|
app: string | null;
|
|
7
9
|
mutating: boolean;
|
|
8
|
-
|
|
9
|
-
userConfirmed: boolean;
|
|
10
|
-
authorization: "none" | "full_permissions_config";
|
|
10
|
+
authorization: "no_permissions_unrestricted" | "none";
|
|
11
11
|
inputBytes: number;
|
|
12
12
|
outcome: string;
|
|
13
13
|
durationMs: number;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
computerUseCalls: number;
|
|
21
|
-
computerUseMethods: string[];
|
|
14
|
+
brokerVersion: string | null;
|
|
15
|
+
clientBuild: string | null;
|
|
16
|
+
directCalls: number;
|
|
17
|
+
modelTurnsStarted: number;
|
|
18
|
+
ephemeralThread: boolean | null;
|
|
19
|
+
approvalRequests: number;
|
|
22
20
|
backgroundPreserved: boolean | null;
|
|
23
|
-
|
|
21
|
+
brokerCleanupVerified: boolean;
|
|
22
|
+
appLeaseReleased: boolean;
|
|
23
|
+
resultContentTypes: string[];
|
|
24
|
+
resultBytes: number;
|
|
24
25
|
}
|
|
25
26
|
export declare function appendAudit(stateDir: string, record: AuditRecord, fileName?: string): Promise<string>;
|
package/dist/audit.js
CHANGED
|
@@ -17,7 +17,7 @@ async function ensurePrivateDirectory(directory) {
|
|
|
17
17
|
}
|
|
18
18
|
await chmod(directory, 0o700);
|
|
19
19
|
}
|
|
20
|
-
export async function appendAudit(stateDir, record, fileName = "
|
|
20
|
+
export async function appendAudit(stateDir, record, fileName = "direct-computer-use.jsonl") {
|
|
21
21
|
if (path.basename(fileName) !== fileName)
|
|
22
22
|
throw new Error("Audit filename must not contain a path");
|
|
23
23
|
await ensurePrivateDirectory(stateDir);
|
package/dist/audit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,IAAI,MAAM,WAAW,CAAC;AA4B7B,KAAK,UAAU,sBAAsB,CAAC,SAAiB;IACtD,IAAI,CAAC;QACJ,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC;QACpC,IAAI,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACvH,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACrB,IAAI,KAAK,EAAE,IAAI,KAAK,QAAQ;YAAE,MAAM,KAAK,CAAC;QAC1C,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACzD,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC;QACpC,IAAI,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACvH,CAAC;IACD,MAAM,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAChC,QAAgB,EAChB,MAAmB,EACnB,QAAQ,GAAG,2BAA2B;IAEtC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACpG,MAAM,sBAAsB,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9C,MAAM,sBAAsB,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAChD,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,OAAO,GAAG,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,UAAU,CAAC;IACjG,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IACnD,IAAI,CAAC;QACJ,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;QAC3E,MAAM,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1B,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC9D,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;IACrB,CAAC;YAAS,CAAC;QACV,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IACD,OAAO,SAAS,CAAC;AAClB,CAAC"}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
export type PermissionMode = "no-permissions";
|
|
2
2
|
export interface ExtensionConfig {
|
|
3
|
-
version:
|
|
3
|
+
version: 2;
|
|
4
4
|
permissionMode: PermissionMode;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* The direct interface has one durable policy: no wrapper permission prompts and
|
|
8
|
+
* no wrapper method restrictions. There is intentionally no file, environment,
|
|
9
|
+
* command, tool argument, or per-call override that can select another mode.
|
|
10
|
+
*/
|
|
6
11
|
export declare const DEFAULT_CONFIG: ExtensionConfig;
|
|
7
|
-
export declare
|
|
8
|
-
constructor(message: string);
|
|
9
|
-
}
|
|
10
|
-
export declare function configPath(stateDir: string): string;
|
|
11
|
-
export declare function loadConfig(stateDir: string): Promise<ExtensionConfig>;
|
|
12
|
-
export declare function saveConfig(stateDir: string, config: ExtensionConfig): Promise<string>;
|
|
12
|
+
export declare function loadConfig(): Promise<ExtensionConfig>;
|
package/dist/config.js
CHANGED
|
@@ -1,105 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
export function configPath(stateDir) {
|
|
12
|
-
return path.join(stateDir, "config.json");
|
|
13
|
-
}
|
|
14
|
-
function parseConfig(value) {
|
|
15
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
16
|
-
throw new ConfigError("Computer Use config must be an object");
|
|
17
|
-
const record = value;
|
|
18
|
-
if (Object.keys(record).some((key) => !["version", "permissionMode"].includes(key))) {
|
|
19
|
-
throw new ConfigError("Computer Use config contains unknown keys");
|
|
20
|
-
}
|
|
21
|
-
if (record.version !== 1)
|
|
22
|
-
throw new ConfigError("Unsupported Computer Use config version");
|
|
23
|
-
if (record.permissionMode !== "safe" && record.permissionMode !== "full-permissions") {
|
|
24
|
-
throw new ConfigError("permissionMode must be safe or full-permissions");
|
|
25
|
-
}
|
|
26
|
-
return { version: 1, permissionMode: record.permissionMode };
|
|
27
|
-
}
|
|
28
|
-
async function inspectStateDirectory(stateDir, create) {
|
|
29
|
-
try {
|
|
30
|
-
const info = await lstat(stateDir);
|
|
31
|
-
if (info.isSymbolicLink() || !info.isDirectory())
|
|
32
|
-
throw new ConfigError("Computer Use state must be a non-symlink directory");
|
|
33
|
-
if ((info.mode & 0o022) !== 0)
|
|
34
|
-
throw new ConfigError("Computer Use state directory must not be group/world writable");
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
if (error?.code !== "ENOENT")
|
|
39
|
-
throw error;
|
|
40
|
-
if (!create)
|
|
41
|
-
return false;
|
|
42
|
-
await mkdir(stateDir, { recursive: true, mode: 0o700 });
|
|
43
|
-
const info = await lstat(stateDir);
|
|
44
|
-
if (info.isSymbolicLink() || !info.isDirectory())
|
|
45
|
-
throw new ConfigError("Computer Use state must be a non-symlink directory");
|
|
46
|
-
await chmod(stateDir, 0o700);
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
export async function loadConfig(stateDir) {
|
|
51
|
-
if (!(await inspectStateDirectory(stateDir, false)))
|
|
52
|
-
return { ...DEFAULT_CONFIG };
|
|
53
|
-
const file = configPath(stateDir);
|
|
54
|
-
let handle;
|
|
55
|
-
try {
|
|
56
|
-
handle = await open(file, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
57
|
-
const info = await handle.stat();
|
|
58
|
-
if (!info.isFile())
|
|
59
|
-
throw new ConfigError("Computer Use config must be a regular non-symlink file");
|
|
60
|
-
if ((info.mode & 0o777) !== 0o600)
|
|
61
|
-
throw new ConfigError("Computer Use config must have mode 0600");
|
|
62
|
-
return parseConfig(JSON.parse(await handle.readFile("utf8")));
|
|
63
|
-
}
|
|
64
|
-
catch (error) {
|
|
65
|
-
if (error?.code === "ENOENT")
|
|
66
|
-
return { ...DEFAULT_CONFIG };
|
|
67
|
-
if (error instanceof ConfigError)
|
|
68
|
-
throw error;
|
|
69
|
-
throw new ConfigError("Could not read the Computer Use config");
|
|
70
|
-
}
|
|
71
|
-
finally {
|
|
72
|
-
await handle?.close().catch(() => undefined);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
export async function saveConfig(stateDir, config) {
|
|
76
|
-
const validated = parseConfig(config);
|
|
77
|
-
await inspectStateDirectory(stateDir, true);
|
|
78
|
-
await chmod(stateDir, 0o700);
|
|
79
|
-
const destination = configPath(stateDir);
|
|
80
|
-
const temporary = path.join(stateDir, `.config.${process.pid}.${Date.now()}.tmp`);
|
|
81
|
-
let handle;
|
|
82
|
-
try {
|
|
83
|
-
const flags = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW;
|
|
84
|
-
handle = await open(temporary, flags, 0o600);
|
|
85
|
-
await handle.chmod(0o600);
|
|
86
|
-
await handle.writeFile(`${JSON.stringify(validated, null, 2)}\n`, "utf8");
|
|
87
|
-
await handle.sync();
|
|
88
|
-
await handle.close();
|
|
89
|
-
handle = undefined;
|
|
90
|
-
await rename(temporary, destination);
|
|
91
|
-
const directory = await open(stateDir, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
92
|
-
try {
|
|
93
|
-
await directory.sync();
|
|
94
|
-
}
|
|
95
|
-
finally {
|
|
96
|
-
await directory.close();
|
|
97
|
-
}
|
|
98
|
-
return destination;
|
|
99
|
-
}
|
|
100
|
-
finally {
|
|
101
|
-
await handle?.close().catch(() => undefined);
|
|
102
|
-
await rm(temporary, { force: true });
|
|
103
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* The direct interface has one durable policy: no wrapper permission prompts and
|
|
3
|
+
* no wrapper method restrictions. There is intentionally no file, environment,
|
|
4
|
+
* command, tool argument, or per-call override that can select another mode.
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_CONFIG = { version: 2, permissionMode: "no-permissions" };
|
|
7
|
+
export async function loadConfig() {
|
|
8
|
+
return { ...DEFAULT_CONFIG };
|
|
104
9
|
}
|
|
105
10
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAoB,EAAE,OAAO,EAAE,CAAC,EAAE,cAAc,EAAE,gBAAgB,EAAE,CAAC;AAEhG,MAAM,CAAC,KAAK,UAAU,UAAU;IAC/B,OAAO,EAAE,GAAG,cAAc,EAAE,CAAC;AAC9B,CAAC"}
|