codex-control-chrome-mcp 1.2.0 → 1.3.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 +17 -0
- package/README.md +17 -28
- package/docs/architecture.md +15 -14
- package/docs/install.md +17 -14
- package/package.json +1 -1
- package/src/cli.js +30 -7
- package/src/installer.js +76 -16
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,23 @@ This project follows semantic versioning.
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.3.0] - 2026-07-03
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- The native host launcher no longer runs `npx` on every connection. When installed to a stable location (e.g. `npm install -g codex-control-chrome-mcp`), `install-native-host` writes a launcher that execs Node directly against the installed CLI, so the Codex extension connects immediately instead of waiting for npx to resolve/download the package on each Native Messaging connect. A cold or evicted npx cache could take seconds and exceed the extension's connect timeout, which showed up as the bridge "not connecting". The launcher prefers the Node.js used at install time and falls back to `node` on `PATH`, so a Node upgrade (which moves the versioned Homebrew/nvm path) does not strand it.
|
|
14
|
+
- Recommended setup is now a global install followed by `codex-control-chrome-mcp install-native-host`, with the MCP stdio client pointed at the installed `codex-control-chrome-mcp` binary. Running `npx … install-native-host` still works: because that copy lives in npm's garbage-collectable `_npx` cache, the installer detects it and keeps the previous npx launcher as a fallback (install globally and re-run for the fast path).
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- `package-lock.json` recorded a stale `1.1.0` project version; it now tracks the package version.
|
|
19
|
+
|
|
20
|
+
## [1.2.1] - 2026-07-03
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Native host failed to start on 1.2.0. The install launcher runs the host with `--browser <name>` before the extension origin that Chrome appends, so the origin was no longer `args[0]`. The CLI dispatcher only recognized native-host mode when the origin was `args[0]`, so the launcher's argv fell through to `Unknown command: --browser` and the host exited on every connect. The visible symptoms were the Codex extension connection flapping between connected/disconnected and `status` never listing a bridge socket (`sockets: []`). The dispatcher now detects the origin by shape anywhere in the args, and a regression test covers the launcher argv.
|
|
25
|
+
|
|
9
26
|
## [1.2.0] - 2026-06-29
|
|
10
27
|
|
|
11
28
|
### Added
|
package/README.md
CHANGED
|
@@ -15,17 +15,6 @@ This is an independent community project. It is not affiliated with OpenAI, Code
|
|
|
15
15
|
- Chrome Extension: [Codex Chrome Extension](https://chromewebstore.google.com/detail/hehggadaopoacecdllhhajmbjkdcmajg)
|
|
16
16
|
- Skill: [`skills/codex-control-chrome-mcp/SKILL.md`](./skills/codex-control-chrome-mcp/SKILL.md)
|
|
17
17
|
|
|
18
|
-
## Status
|
|
19
|
-
|
|
20
|
-
The current implementation provides:
|
|
21
|
-
|
|
22
|
-
- official `@modelcontextprotocol/sdk` stdio server
|
|
23
|
-
- Chrome and Microsoft Edge Native Messaging host mode
|
|
24
|
-
- install/uninstall commands with manifest backup
|
|
25
|
-
- JSON-RPC bridge socket under `/tmp/codex-browser-use`
|
|
26
|
-
- Chrome tab, navigation, screenshot, CDP, and event MCP tools
|
|
27
|
-
- bundled skill documentation for Agent tool ordering
|
|
28
|
-
|
|
29
18
|
## Platform Support
|
|
30
19
|
|
|
31
20
|
| Platform | Status | Notes |
|
|
@@ -51,40 +40,42 @@ Commands accept `--browser <chrome\|edge\|all>`. Without the flag, `install-nati
|
|
|
51
40
|
|
|
52
41
|
Install and enable the [Codex Chrome Extension](https://chromewebstore.google.com/detail/hehggadaopoacecdllhhajmbjkdcmajg) in the Chrome or Edge profile you want to automate.
|
|
53
42
|
|
|
54
|
-
Install the native host (auto-detects the browsers present):
|
|
43
|
+
Install the package globally, then register the native host (auto-detects the browsers present):
|
|
55
44
|
|
|
56
45
|
```bash
|
|
57
|
-
|
|
46
|
+
npm install -g codex-control-chrome-mcp
|
|
47
|
+
codex-control-chrome-mcp install-native-host
|
|
58
48
|
```
|
|
59
49
|
|
|
60
|
-
To target one browser explicitly:
|
|
50
|
+
A global install gives the native host a stable path, so its launcher execs Node directly against the installed CLI with no per-connection `npx` cost. To target one browser explicitly:
|
|
61
51
|
|
|
62
52
|
```bash
|
|
63
|
-
|
|
53
|
+
codex-control-chrome-mcp install-native-host --browser edge
|
|
64
54
|
```
|
|
65
55
|
|
|
66
|
-
Configure your Agent to use the MCP stdio server:
|
|
56
|
+
Configure your Agent to use the MCP stdio server through the installed binary:
|
|
67
57
|
|
|
68
58
|
```json
|
|
69
59
|
{
|
|
70
60
|
"type": "stdio",
|
|
71
|
-
"command": "
|
|
72
|
-
"args": ["
|
|
61
|
+
"command": "codex-control-chrome-mcp",
|
|
62
|
+
"args": ["--stdio"],
|
|
73
63
|
"startup_timeout_sec": 30
|
|
74
64
|
}
|
|
75
65
|
```
|
|
76
66
|
|
|
77
|
-
|
|
67
|
+
If your MCP client does not inherit a shell `PATH` that includes npm's global bin directory (common for GUI apps), use the absolute path from `npm prefix -g` — for example `/opt/homebrew/bin/codex-control-chrome-mcp` — as `command`.
|
|
68
|
+
|
|
69
|
+
Check status and uninstall:
|
|
78
70
|
|
|
79
71
|
```bash
|
|
80
|
-
|
|
72
|
+
codex-control-chrome-mcp status
|
|
73
|
+
codex-control-chrome-mcp uninstall-native-host # restores the previous Codex native host manifest
|
|
81
74
|
```
|
|
82
75
|
|
|
83
|
-
|
|
76
|
+
### Without a global install
|
|
84
77
|
|
|
85
|
-
|
|
86
|
-
npx -y codex-control-chrome-mcp@latest uninstall-native-host
|
|
87
|
-
```
|
|
78
|
+
`npx -y codex-control-chrome-mcp@latest <command>` still works for every command. Be aware that `npx … install-native-host` writes a launcher that re-runs `npx` on every extension connection; a cold or evicted npx cache can add seconds and may exceed the extension's connect timeout (the bridge then looks stuck "not connecting"). The installer detects the npx temp copy and keeps that launcher as a fallback — install globally and re-run `install-native-host` for the fast, stable path.
|
|
88
79
|
|
|
89
80
|
## Agent Skill
|
|
90
81
|
|
|
@@ -161,16 +152,12 @@ node ./bin/codex-control-chrome-mcp.js --native-host
|
|
|
161
152
|
|
|
162
153
|
## Troubleshooting
|
|
163
154
|
|
|
164
|
-
`status` reports, per browser (under `install.chrome` / `install.edge`), a `registered` flag and a `classification` of the live manifest. When `registered` is `false` and `classification.kind` is `official-openai`, a Codex update has reclaimed that browser's manifest (see below). `classification` identifies OpenAI's genuine host by stable signals — the `com.openai.codexextension` name, the official extension origin, the bundle path, the binary format, and (on macOS) the OpenAI code signature — not by filename.
|
|
165
|
-
|
|
166
155
|
### A Codex update stopped the bridge
|
|
167
156
|
|
|
168
157
|
Codex App/extension updates re-register their own `com.openai.codexextension` native-messaging manifest, which overwrites this project's proxy registration (the bundled host binary has even been renamed across releases). The symptom is `status` showing `sockets: []`, `registered: false`, and a manifest `path` back inside the Codex bundle.
|
|
169
158
|
|
|
170
159
|
The MCP stdio server now **self-heals**: on startup it re-asserts the manifest when it detects it was reverted, so you usually do not need to re-run `install-native-host`. Because Chrome only reads the manifest when the extension reconnects, **reload the Codex Chrome Extension (or restart Chrome) once** after a Codex update to bring the bridge back. Set `CODEX_CONTROL_CHROME_NO_AUTO_REGISTER=1` to disable the auto re-register.
|
|
171
160
|
|
|
172
|
-
If `status` shows no bridge sockets, reload Chrome or the Codex Chrome Extension after installing the native host.
|
|
173
|
-
|
|
174
161
|
If Codex App integration stops working, uninstall this native host to restore the backed-up manifest:
|
|
175
162
|
|
|
176
163
|
```bash
|
|
@@ -179,6 +166,8 @@ npx -y codex-control-chrome-mcp@latest uninstall-native-host
|
|
|
179
166
|
|
|
180
167
|
If tab or CDP tools fail, use the bundled skill workflow: list tabs again, claim only current tab IDs, attach before raw CDP calls, and read CDP events after enabling the relevant domain.
|
|
181
168
|
|
|
169
|
+
See [Install And Uninstall](./docs/install.md#troubleshooting) for the full per-browser checklist (the `registered` flag and host `classification`).
|
|
170
|
+
|
|
182
171
|
## License
|
|
183
172
|
|
|
184
173
|
MIT
|
package/docs/architecture.md
CHANGED
|
@@ -107,7 +107,7 @@ If the MCP stdio server is stopped but the native host proxy is still running, C
|
|
|
107
107
|
```text
|
|
108
108
|
Other Agent
|
|
109
109
|
-> MCP stdio
|
|
110
|
-
->
|
|
110
|
+
-> codex-control-chrome-mcp --stdio
|
|
111
111
|
-> /tmp/codex-browser-use/codex-control-chrome-mcp-*.sock
|
|
112
112
|
-> codex-control-chrome-mcp --native-host
|
|
113
113
|
-> Chrome native messaging stdio
|
|
@@ -189,34 +189,35 @@ This project can shim those APIs and point `nativePipe.createConnection()` at it
|
|
|
189
189
|
|
|
190
190
|
The initial implementation exposes direct MCP tools first. Vendoring `browser-client.mjs` should be a later phase after the native host bridge is stable.
|
|
191
191
|
|
|
192
|
-
##
|
|
192
|
+
## Runtime And Launch Model
|
|
193
193
|
|
|
194
|
-
The
|
|
194
|
+
The package is distributed on npm and run as its installed CLI. A global install (`npm install -g codex-control-chrome-mcp`) gives both entrypoints — the `--stdio` MCP server and the `--native-host` bridge — a stable path.
|
|
195
195
|
|
|
196
|
-
Agent-side MCP config should use:
|
|
196
|
+
Agent-side MCP config should use the installed binary:
|
|
197
197
|
|
|
198
198
|
```json
|
|
199
199
|
{
|
|
200
200
|
"type": "stdio",
|
|
201
|
-
"command": "
|
|
202
|
-
"args": ["
|
|
201
|
+
"command": "codex-control-chrome-mcp",
|
|
202
|
+
"args": ["--stdio"]
|
|
203
203
|
}
|
|
204
204
|
```
|
|
205
205
|
|
|
206
|
-
Chrome Native Messaging manifests cannot store command arguments. Therefore `install-native-host`
|
|
207
|
-
|
|
208
|
-
```sh
|
|
209
|
-
npx -y codex-control-chrome-mcp@latest install-native-host
|
|
210
|
-
```
|
|
206
|
+
Chrome Native Messaging manifests cannot store command arguments. Therefore `install-native-host` writes a stable launcher script at:
|
|
211
207
|
|
|
212
208
|
```text
|
|
213
209
|
~/.codex-control-chrome-mcp/native-host-launcher
|
|
214
210
|
```
|
|
215
211
|
|
|
216
|
-
|
|
212
|
+
The launcher execs Node directly against the installed CLI (no `npx` in the hot path, so the extension connects without waiting on a package resolve/download) and injects the mode and browser. The `--browser` flag tells the host which browser's proxy config to read:
|
|
217
213
|
|
|
218
214
|
```sh
|
|
219
|
-
|
|
215
|
+
#!/usr/bin/env sh
|
|
216
|
+
node_bin='/path/to/node'
|
|
217
|
+
[ -x "$node_bin" ] || node_bin=node
|
|
218
|
+
exec "$node_bin" '/path/to/codex-control-chrome-mcp/bin/codex-control-chrome-mcp.js' --native-host --browser chrome "$@"
|
|
220
219
|
```
|
|
221
220
|
|
|
222
|
-
The native host manifest points to the launcher, not directly
|
|
221
|
+
It prefers the Node.js used at install time and falls back to `node` on `PATH` so a Node upgrade does not strand it. The native host manifest points to the launcher, not to the CLI directly (a manifest cannot pass `--browser`).
|
|
222
|
+
|
|
223
|
+
When the CLI is instead run through `npx` — its copy lives in npm's garbage-collectable `_npx` cache — `install-native-host` detects that and falls back to a launcher that runs `npx -y codex-control-chrome-mcp@latest --native-host …`. That is stable but pays npx's startup cost on every connect; a global install avoids it.
|
package/docs/install.md
CHANGED
|
@@ -8,13 +8,6 @@ Package:
|
|
|
8
8
|
|
|
9
9
|
## Development Status
|
|
10
10
|
|
|
11
|
-
The current implementation provides:
|
|
12
|
-
|
|
13
|
-
- MCP stdio mode: `npx -y codex-control-chrome-mcp@latest --stdio`
|
|
14
|
-
- manifest install: `npx -y codex-control-chrome-mcp@latest install-native-host`
|
|
15
|
-
- manifest uninstall: `npx -y codex-control-chrome-mcp@latest uninstall-native-host`
|
|
16
|
-
- bridge/socket status: `npx -y codex-control-chrome-mcp@latest status`
|
|
17
|
-
|
|
18
11
|
Do not run install on a machine where Codex Chrome integration is important unless you are ready to test proxy mode or restore from backup.
|
|
19
12
|
|
|
20
13
|
This is an independent community project. It is not affiliated with OpenAI, Codex, Google, or Chrome.
|
|
@@ -40,8 +33,11 @@ Commands accept `--browser <chrome|edge|all>`. Without it, `install-native-host`
|
|
|
40
33
|
|
|
41
34
|
First install and enable the [Codex Chrome Extension](https://chromewebstore.google.com/detail/hehggadaopoacecdllhhajmbjkdcmajg) in the Chrome or Edge profile you want to automate.
|
|
42
35
|
|
|
36
|
+
Install the package globally, then register the native host:
|
|
37
|
+
|
|
43
38
|
```bash
|
|
44
|
-
|
|
39
|
+
npm install -g codex-control-chrome-mcp
|
|
40
|
+
codex-control-chrome-mcp install-native-host
|
|
45
41
|
```
|
|
46
42
|
|
|
47
43
|
Without `--browser`, this installs for every supported browser whose profile directory exists. Pass `--browser <chrome|edge|all>` to target one explicitly.
|
|
@@ -53,12 +49,17 @@ This writes a stable per-browser launcher (Edge uses the `-edge` suffix):
|
|
|
53
49
|
~/.codex-control-chrome-mcp/native-host-launcher-edge
|
|
54
50
|
```
|
|
55
51
|
|
|
56
|
-
The launcher
|
|
52
|
+
Chrome/Edge Native Messaging manifests cannot carry arguments, so the manifest points at this launcher rather than the CLI directly. The launcher execs Node against the installed CLI — no `npx` in the hot path, so the extension connects immediately — and injects `--native-host --browser <name>` (the `--browser` flag tells the host which browser's config to read for proxy mode). It prefers the Node.js used at install time and falls back to `node` on `PATH`, so a Node upgrade does not strand it:
|
|
57
53
|
|
|
58
|
-
```
|
|
59
|
-
|
|
54
|
+
```sh
|
|
55
|
+
#!/usr/bin/env sh
|
|
56
|
+
node_bin='/path/to/node'
|
|
57
|
+
[ -x "$node_bin" ] || node_bin=node
|
|
58
|
+
exec "$node_bin" '/usr/local/lib/node_modules/codex-control-chrome-mcp/bin/codex-control-chrome-mcp.js' --native-host --browser chrome "$@"
|
|
60
59
|
```
|
|
61
60
|
|
|
61
|
+
If you instead run `npx … install-native-host`, the package resolves inside npm's garbage-collectable `_npx` cache, so the installer falls back to a launcher that runs `npx -y codex-control-chrome-mcp@latest --native-host …` on every connect. That works but pays npx's startup cost each time (a cold cache can exceed the extension's connect timeout); install globally and re-run for the direct launcher.
|
|
62
|
+
|
|
62
63
|
For each selected browser the installer:
|
|
63
64
|
|
|
64
65
|
1. Reads the existing `com.openai.codexextension.json` in that browser's directory.
|
|
@@ -98,17 +99,19 @@ Example manifest paths on Linux:
|
|
|
98
99
|
|
|
99
100
|
## MCP Client Config
|
|
100
101
|
|
|
101
|
-
|
|
102
|
+
With the package installed globally, point the client at the installed binary:
|
|
102
103
|
|
|
103
104
|
```json
|
|
104
105
|
{
|
|
105
106
|
"type": "stdio",
|
|
106
|
-
"command": "
|
|
107
|
-
"args": ["
|
|
107
|
+
"command": "codex-control-chrome-mcp",
|
|
108
|
+
"args": ["--stdio"],
|
|
108
109
|
"startup_timeout_sec": 30
|
|
109
110
|
}
|
|
110
111
|
```
|
|
111
112
|
|
|
113
|
+
If the client does not inherit a `PATH` that includes npm's global bin directory (common for GUI apps), use the absolute path from `npm prefix -g` (e.g. `/opt/homebrew/bin/codex-control-chrome-mcp`) as `command`. Without a global install, `"command": "npx"` with `"args": ["-y", "codex-control-chrome-mcp@latest", "--stdio"]` also works.
|
|
114
|
+
|
|
112
115
|
If the Agent supports skills, install or reference the bundled skill folder from this package or repository:
|
|
113
116
|
|
|
114
117
|
```text
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -13,21 +13,19 @@ import { probeLengthPrefixedSocket } from "./probe.js";
|
|
|
13
13
|
|
|
14
14
|
export async function runCli(argv) {
|
|
15
15
|
const args = argv.slice(2);
|
|
16
|
-
const
|
|
16
|
+
const invocation = classifyInvocation(args);
|
|
17
17
|
|
|
18
|
-
if (
|
|
18
|
+
if (invocation.mode === "mcp") {
|
|
19
19
|
await runMcpServer();
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
// by shape rather than position (the `--browser` flag shifts the indices).
|
|
26
|
-
const origin = args.find(looksLikeChromeOrigin) ?? argv[3];
|
|
27
|
-
await runNativeHost({ origin, browser: normalizeBrowser(readOption(args, "--browser")) });
|
|
23
|
+
if (invocation.mode === "native-host") {
|
|
24
|
+
await runNativeHost({ origin: invocation.origin ?? argv[3], browser: invocation.browser });
|
|
28
25
|
return;
|
|
29
26
|
}
|
|
30
27
|
|
|
28
|
+
const command = invocation.command;
|
|
31
29
|
switch (command) {
|
|
32
30
|
case "install-native-host": {
|
|
33
31
|
const binPath = readOption(args, "--bin");
|
|
@@ -78,6 +76,31 @@ export async function runCli(argv) {
|
|
|
78
76
|
}
|
|
79
77
|
}
|
|
80
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Decide how an invocation should be handled, purely from the CLI args.
|
|
81
|
+
*
|
|
82
|
+
* Chrome/Edge launch the native host by appending the extension origin as a
|
|
83
|
+
* positional arg, and the install launcher prepends `--browser <name>`. That
|
|
84
|
+
* prefix shifts the origin out of `args[0]`, so both the mode check and the
|
|
85
|
+
* origin lookup must scan by shape rather than position. (A 1.2.0 regression
|
|
86
|
+
* only checked `args[0]` here, so the launcher's `--browser`-prefixed argv fell
|
|
87
|
+
* through to `Unknown command: --browser` and the native host exited on every
|
|
88
|
+
* connect.)
|
|
89
|
+
*/
|
|
90
|
+
export function classifyInvocation(args) {
|
|
91
|
+
if (args.includes("--stdio") || args[0] === "stdio") {
|
|
92
|
+
return { mode: "mcp" };
|
|
93
|
+
}
|
|
94
|
+
if (args.includes("--native-host") || args.some(looksLikeChromeOrigin)) {
|
|
95
|
+
return {
|
|
96
|
+
mode: "native-host",
|
|
97
|
+
origin: args.find(looksLikeChromeOrigin),
|
|
98
|
+
browser: normalizeBrowser(readOption(args, "--browser")),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return { mode: "command", command: args[0] };
|
|
102
|
+
}
|
|
103
|
+
|
|
81
104
|
function looksLikeChromeOrigin(value) {
|
|
82
105
|
return typeof value === "string" && value.startsWith("chrome-extension://");
|
|
83
106
|
}
|
package/src/installer.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
3
4
|
import {
|
|
4
5
|
APP_STATE_DIR,
|
|
5
6
|
CODEX_EXTENSION_ID,
|
|
@@ -26,15 +27,19 @@ export async function installNativeHost({
|
|
|
26
27
|
proxy = true,
|
|
27
28
|
browser = DEFAULT_BROWSER,
|
|
28
29
|
paths = defaultInstallPaths(browser),
|
|
30
|
+
selfBinPath = resolveSelfBinPath(),
|
|
29
31
|
} = {}) {
|
|
30
32
|
const { manifestPath, appStateDir, configPath, installStatePath } = paths;
|
|
31
33
|
const browserId = paths.browser ?? browser;
|
|
32
34
|
await fs.mkdir(path.dirname(manifestPath), { recursive: true });
|
|
33
35
|
await fs.mkdir(appStateDir, { recursive: true });
|
|
34
36
|
|
|
35
|
-
const hostPath =
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
const { hostPath, launchMode } = await createLauncher({
|
|
38
|
+
binPath,
|
|
39
|
+
selfBinPath,
|
|
40
|
+
appStateDir,
|
|
41
|
+
browser: browserId,
|
|
42
|
+
});
|
|
38
43
|
const existing = await readJsonIfExists(manifestPath);
|
|
39
44
|
const previousState = await readJsonIfExists(installStatePath);
|
|
40
45
|
const previousBackup = previousState?.backupPath
|
|
@@ -68,7 +73,7 @@ export async function installNativeHost({
|
|
|
68
73
|
manifestPath,
|
|
69
74
|
hostPath,
|
|
70
75
|
binPath: binPath ? path.resolve(binPath) : null,
|
|
71
|
-
launchMode
|
|
76
|
+
launchMode,
|
|
72
77
|
backupPath,
|
|
73
78
|
officialHostPath,
|
|
74
79
|
proxy,
|
|
@@ -252,6 +257,39 @@ async function backupManifest(manifestPath) {
|
|
|
252
257
|
return backupPath;
|
|
253
258
|
}
|
|
254
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Choose how the Native Messaging host is launched:
|
|
262
|
+
* - `--bin <path>`: a caller-supplied CLI entry (local dev) -> direct launcher.
|
|
263
|
+
* - installed to a stable location (e.g. `npm i -g`): our own resolved entry
|
|
264
|
+
* -> direct launcher, so every connect skips npx's resolve/download cost.
|
|
265
|
+
* - run from npx's temp cache (`npx … install-native-host`): that copy can be
|
|
266
|
+
* garbage-collected, so a launcher must not point at it -> npx launcher
|
|
267
|
+
* (stable, but pays npx startup on every connect). Install globally and
|
|
268
|
+
* re-run for the fast path.
|
|
269
|
+
*/
|
|
270
|
+
async function createLauncher({ binPath, selfBinPath, appStateDir, browser }) {
|
|
271
|
+
if (binPath) {
|
|
272
|
+
const hostPath = await createDirectLauncher(
|
|
273
|
+
path.resolve(binPath),
|
|
274
|
+
appStateDir,
|
|
275
|
+
browser,
|
|
276
|
+
"native-host-local-launcher",
|
|
277
|
+
);
|
|
278
|
+
return { hostPath, launchMode: "direct-bin" };
|
|
279
|
+
}
|
|
280
|
+
if (isNpxCachePath(selfBinPath)) {
|
|
281
|
+
const hostPath = await createNpxLauncher(appStateDir, browser);
|
|
282
|
+
return { hostPath, launchMode: "npx-launcher" };
|
|
283
|
+
}
|
|
284
|
+
const hostPath = await createDirectLauncher(
|
|
285
|
+
selfBinPath,
|
|
286
|
+
appStateDir,
|
|
287
|
+
browser,
|
|
288
|
+
"native-host-launcher",
|
|
289
|
+
);
|
|
290
|
+
return { hostPath, launchMode: "global-bin" };
|
|
291
|
+
}
|
|
292
|
+
|
|
255
293
|
// Each browser gets its own launcher so the native host knows which browser's
|
|
256
294
|
// config to read for proxy mode. Chrome keeps the original unsuffixed filename
|
|
257
295
|
// so manifests written before multi-browser support still resolve.
|
|
@@ -259,35 +297,57 @@ function launcherFileName(base, browser) {
|
|
|
259
297
|
return browser === DEFAULT_BROWSER ? base : `${base}-${browser}`;
|
|
260
298
|
}
|
|
261
299
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
300
|
+
/**
|
|
301
|
+
* A tiny shell shim that execs the installed Node CLI directly (no npx), so the
|
|
302
|
+
* host starts in milliseconds instead of paying npx's resolve/download cost on
|
|
303
|
+
* every Native Messaging connect. It prefers the Node.js used at install time
|
|
304
|
+
* and falls back to `node` on PATH, so a Node upgrade (which moves the versioned
|
|
305
|
+
* Homebrew/nvm path in process.execPath) does not strand the launcher. Chrome/
|
|
306
|
+
* Edge Native Messaging manifests cannot carry arguments, so `--native-host` and
|
|
307
|
+
* `--browser` are injected here.
|
|
308
|
+
*/
|
|
309
|
+
async function createDirectLauncher(binPath, appStateDir, browser, launcherBase) {
|
|
310
|
+
if (process.platform === "win32" || !binPath.endsWith(".js")) {
|
|
311
|
+
return binPath;
|
|
265
312
|
}
|
|
266
|
-
const launcherPath = path.join(appStateDir, launcherFileName(
|
|
313
|
+
const launcherPath = path.join(appStateDir, launcherFileName(launcherBase, browser));
|
|
267
314
|
const script = `#!/usr/bin/env sh
|
|
268
|
-
|
|
315
|
+
node_bin=${shellQuote(process.execPath)}
|
|
316
|
+
[ -x "$node_bin" ] || node_bin=node
|
|
317
|
+
exec "$node_bin" ${shellQuote(binPath)} --native-host --browser ${browser} "$@"
|
|
269
318
|
`;
|
|
270
319
|
await fs.writeFile(launcherPath, script, { encoding: "utf8", mode: 0o755 });
|
|
271
320
|
await fs.chmod(launcherPath, 0o755);
|
|
272
321
|
return launcherPath;
|
|
273
322
|
}
|
|
274
323
|
|
|
275
|
-
async function
|
|
324
|
+
async function createNpxLauncher(appStateDir, browser = DEFAULT_BROWSER) {
|
|
276
325
|
if (process.platform === "win32") {
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
if (!binPath.endsWith(".js")) {
|
|
280
|
-
return binPath;
|
|
326
|
+
throw new Error("NPX native host launcher installation is not implemented for Windows yet.");
|
|
281
327
|
}
|
|
282
|
-
const launcherPath = path.join(appStateDir, launcherFileName("native-host-
|
|
328
|
+
const launcherPath = path.join(appStateDir, launcherFileName("native-host-launcher", browser));
|
|
283
329
|
const script = `#!/usr/bin/env sh
|
|
284
|
-
exec ${
|
|
330
|
+
exec npx -y ${PACKAGE_NAME}@${PACKAGE_VERSION} --native-host --browser ${browser} "$@"
|
|
285
331
|
`;
|
|
286
332
|
await fs.writeFile(launcherPath, script, { encoding: "utf8", mode: 0o755 });
|
|
287
333
|
await fs.chmod(launcherPath, 0o755);
|
|
288
334
|
return launcherPath;
|
|
289
335
|
}
|
|
290
336
|
|
|
337
|
+
/**
|
|
338
|
+
* Absolute path to this package's own CLI entry, resolved from the installed
|
|
339
|
+
* module location so a global (or otherwise stable) install yields a launcher
|
|
340
|
+
* target that survives package upgrades.
|
|
341
|
+
*/
|
|
342
|
+
function resolveSelfBinPath() {
|
|
343
|
+
return fileURLToPath(new URL("../bin/codex-control-chrome-mcp.js", import.meta.url));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** True when a path lives inside npm's `_npx` temp cache (subject to GC). */
|
|
347
|
+
function isNpxCachePath(filePath) {
|
|
348
|
+
return filePath.split(path.sep).includes("_npx");
|
|
349
|
+
}
|
|
350
|
+
|
|
291
351
|
function shellQuote(value) {
|
|
292
352
|
return `'${String(value).replaceAll("'", "'\\''")}'`;
|
|
293
353
|
}
|