clawdex-mobile 5.1.3-internal.5 → 5.1.3-internal.7
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/README.md +20 -10
- package/docs/setup-and-operations.md +25 -16
- package/docs/troubleshooting.md +12 -5
- package/package.json +2 -2
- package/scripts/codespaces-bootstrap.js +70 -4
- package/scripts/setup-secure-dev.sh +15 -4
- package/scripts/setup-wizard.sh +106 -8
- package/scripts/start-bridge-secure.js +1 -1
- package/services/rust-bridge/Cargo.lock +1 -1
- package/services/rust-bridge/Cargo.toml +1 -1
- package/services/rust-bridge/src/main.rs +1391 -127
- package/services/rust-bridge/src/services/git.rs +260 -6
- package/vendor/bridge-binaries/darwin-arm64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/darwin-x64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/linux-arm64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/linux-armv7l/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/linux-x64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/win32-x64/codex-rust-bridge.exe +0 -0
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@ Before you start:
|
|
|
25
25
|
- `git`
|
|
26
26
|
- `codex` in `PATH` for the default Codex flow
|
|
27
27
|
- `opencode` in `PATH` if you want the OpenCode flow
|
|
28
|
+
- `cursor-app-server` in `PATH` if you want the Cursor SDK flow
|
|
28
29
|
|
|
29
30
|
Install the mobile app:
|
|
30
31
|
|
|
@@ -70,8 +71,8 @@ Notes:
|
|
|
70
71
|
- Browser preview uses a second forwarded port (`8788` by default), so both ports need public visibility.
|
|
71
72
|
- GitHub resets public forwarded ports back to private when a codespace restarts. Restarting the bridge reruns the visibility step.
|
|
72
73
|
- If automatic visibility setup fails, run `gh codespace ports visibility 8787:public 8788:public`.
|
|
73
|
-
- If the mobile app is built with `EXPO_PUBLIC_GITHUB_APP_CLIENT_ID
|
|
74
|
-
- The tiny auth backend for that flow lives in `services/github-app-auth-worker`. Point the GitHub App callback URL at `https://<your-domain>/github/callback` and
|
|
74
|
+
- If the mobile app is built with `EXPO_PUBLIC_GITHUB_APP_CLIENT_ID` and `EXPO_PUBLIC_GITHUB_APP_AUTH_BASE_URL`, users can now tap `Use GitHub Codespaces` in onboarding/settings, complete one in-app GitHub sign-in, pick a Codespace, and connect without manually copying the bridge token.
|
|
75
|
+
- The tiny auth backend for that flow lives in `services/github-app-auth-worker`. Point the GitHub App callback URL at `https://<your-domain>/github/callback` and configure the worker with the GitHub App client ID/secret.
|
|
75
76
|
- The app can also create a new repo-backed Codespace directly. It prefers `<signed-in-user>/<EXPO_PUBLIC_GITHUB_CODESPACES_REPO_NAME>` first. If that repo does not exist, it automatically forks `EXPO_PUBLIC_GITHUB_CODESPACES_SOURCE_OWNER/<EXPO_PUBLIC_GITHUB_CODESPACES_REPO_NAME>` into the signed-in user account, then creates the Codespace there.
|
|
76
77
|
|
|
77
78
|
This repo now also includes a Codespaces bootstrap flow. On Codespace start/resume, `.devcontainer/devcontainer.json` runs:
|
|
@@ -86,29 +87,38 @@ During initial Codespace creation, the devcontainer also runs:
|
|
|
86
87
|
npm run codespaces:bootstrap -- --prepare-only
|
|
87
88
|
```
|
|
88
89
|
|
|
89
|
-
That pre-installs
|
|
90
|
+
That pre-installs the selected engines and prebuilds the Rust bridge binary so the later startup path is faster. The normal bootstrap command rewrites `.env.secure` for Codespaces with `codex` as the default enabled engine and starts the bridge in the background. You can rerun either command manually any time.
|
|
91
|
+
|
|
92
|
+
To enable more engines in Codespaces, set `CLAWDEX_CODESPACES_ENGINES` before bootstrap:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
CLAWDEX_CODESPACES_ENGINES=codex,opencode,cursor npm run codespaces:bootstrap
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
When `opencode` or `cursor` are selected, bootstrap installs their CLI/server package if the command is missing. Cursor still needs `CURSOR_API_KEY` in the environment or `.env.secure`.
|
|
90
99
|
|
|
91
100
|
The published npm package now includes that bootstrap script too, so a minimal Codespaces template repo can install `clawdex-mobile@latest` in `postCreateCommand` and call the packaged bootstrap without vendoring bridge source into the template itself.
|
|
92
101
|
|
|
93
102
|
In Codespaces mode, the bootstrap also enables bridge-side GitHub bearer auth for the current `CODESPACE_NAME`, so the mobile app can authenticate with the same GitHub App user token it used to discover and start the Codespace.
|
|
94
103
|
|
|
95
|
-
##
|
|
104
|
+
## Extra Harness Setup
|
|
96
105
|
|
|
97
|
-
OpenCode
|
|
106
|
+
OpenCode and Cursor can run beside Codex from the same bridge.
|
|
98
107
|
|
|
99
108
|
```bash
|
|
100
109
|
npm install -g opencode-ai
|
|
110
|
+
npm install -g @clawdex/cursor-app-server
|
|
101
111
|
npm install -g clawdex-mobile@latest
|
|
102
|
-
clawdex init --engines codex,opencode
|
|
112
|
+
clawdex init --engines codex,opencode,cursor
|
|
103
113
|
```
|
|
104
114
|
|
|
105
|
-
That writes `BRIDGE_ENABLED_ENGINES=codex,opencode` to `.env.secure`, so the mobile app can control
|
|
115
|
+
That writes `BRIDGE_ENABLED_ENGINES=codex,opencode,cursor` to `.env.secure`, so the mobile app can control the selected harnesses from one bridge. When Cursor is selected, `clawdex init` asks for the Cursor API key and saves it in `.env.secure`.
|
|
106
116
|
|
|
107
117
|
Notes:
|
|
108
118
|
|
|
109
119
|
- `clawdex init` without flags now lets you multi-select harnesses in the wizard with Space, then Enter to continue.
|
|
110
|
-
- Use `clawdex init --engine codex` or `clawdex init --engine
|
|
111
|
-
-
|
|
120
|
+
- Use `clawdex init --engine codex`, `clawdex init --engine opencode`, or `clawdex init --engine cursor` if you want a single-harness setup.
|
|
121
|
+
- For non-interactive host automation, set `CURSOR_API_KEY` before running setup. `CURSOR_MODEL` is optional; the app model picker sends the model for normal chats.
|
|
112
122
|
|
|
113
123
|
## Monorepo Development
|
|
114
124
|
|
|
@@ -140,7 +150,7 @@ Use `npm run setup:wizard -- --no-start` if you only want to write config.
|
|
|
140
150
|
|
|
141
151
|
## Main Commands
|
|
142
152
|
|
|
143
|
-
- `clawdex init [--engine codex|opencode] [--engines codex,opencode] [--no-start]`
|
|
153
|
+
- `clawdex init [--engine codex|opencode|cursor] [--engines codex,opencode,cursor] [--no-start]`
|
|
144
154
|
- `clawdex stop`
|
|
145
155
|
- `clawdex upgrade` / `clawdex update`
|
|
146
156
|
- `clawdex version`
|
|
@@ -6,21 +6,23 @@ This guide is the detailed companion to the top-level `README.md`.
|
|
|
6
6
|
|
|
7
7
|
The setup wizard now lets you choose which harnesses the phone should control.
|
|
8
8
|
|
|
9
|
-
If you want
|
|
9
|
+
If you want Codex, OpenCode, and Cursor:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
clawdex init --engines codex,opencode
|
|
12
|
+
clawdex init --engines codex,opencode,cursor
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
From a source checkout, the equivalent command is:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm run setup:wizard -- --engines codex,opencode
|
|
18
|
+
npm run setup:wizard -- --engines codex,opencode,cursor
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
That writes `BRIDGE_ENABLED_ENGINES=codex,opencode` into `.env.secure`, so the bridge starts
|
|
21
|
+
That writes `BRIDGE_ENABLED_ENGINES=codex,opencode,cursor` into `.env.secure`, so the bridge starts the selected backends and the mobile app can control them from one UI. When Cursor is selected, `clawdex init` asks for the Cursor API key and saves it in `.env.secure`.
|
|
22
22
|
|
|
23
|
-
If you want only one harness, use `--engine codex
|
|
23
|
+
If you want only one harness, use `--engine codex`, `--engine opencode`, or `--engine cursor`.
|
|
24
|
+
|
|
25
|
+
Cursor usage limits are not exposed by Cursor's public API today. The app shows key status, key metadata, runtime state, and models from Cursor; plan or weekly usage details remain in Cursor.
|
|
24
26
|
|
|
25
27
|
## Onboarding Output Cues
|
|
26
28
|
|
|
@@ -63,8 +65,8 @@ Important constraints:
|
|
|
63
65
|
- Browser preview uses the preview port (`8788` by default), so that forwarded port must also be public
|
|
64
66
|
- GitHub resets public forwarded ports back to private whenever the codespace restarts
|
|
65
67
|
- Keep bridge auth enabled and use Codespaces only for repos you trust, because public forwarded ports are internet-reachable
|
|
66
|
-
- If the mobile app build sets `EXPO_PUBLIC_GITHUB_APP_CLIENT_ID
|
|
67
|
-
-
|
|
68
|
+
- If the mobile app build sets `EXPO_PUBLIC_GITHUB_APP_CLIENT_ID` and `EXPO_PUBLIC_GITHUB_APP_AUTH_BASE_URL`, onboarding/settings can now open one GitHub sign-in, start the Codespace, and connect directly with the same GitHub App user token instead of copying `BRIDGE_AUTH_TOKEN`
|
|
69
|
+
- Users do not need to grant the GitHub App repository installation access to the `clawdex-codespace` template for the normal Codespaces flow
|
|
68
70
|
- The same in-app GitHub flow can create a new Codespace. It prefers `<signed-in-user>/<EXPO_PUBLIC_GITHUB_CODESPACES_REPO_NAME>`. If that repo does not exist yet, Clawdex automatically forks `EXPO_PUBLIC_GITHUB_CODESPACES_SOURCE_OWNER/<EXPO_PUBLIC_GITHUB_CODESPACES_REPO_NAME>` into the signed-in user account and creates the Codespace from that fork
|
|
69
71
|
- Older saved GitHub Codespaces sessions may need one fresh sign-in from the app so the stored GitHub App token and refresh token are updated
|
|
70
72
|
|
|
@@ -72,7 +74,6 @@ For the one-flow GitHub App setup:
|
|
|
72
74
|
|
|
73
75
|
- deploy the tiny auth service under `services/github-app-auth-worker`
|
|
74
76
|
- set the GitHub App `Callback URL` to `https://<your-domain>/github/callback`
|
|
75
|
-
- enable `Request user authorization (OAuth) during installation`
|
|
76
77
|
- set the mobile env `EXPO_PUBLIC_GITHUB_APP_AUTH_BASE_URL=https://<your-domain>`
|
|
77
78
|
|
|
78
79
|
Manual recovery if port visibility does not update automatically:
|
|
@@ -92,9 +93,12 @@ The repo devcontainer now includes:
|
|
|
92
93
|
|
|
93
94
|
- installs the Codex CLI via `npm install -g @openai/codex` if it is missing
|
|
94
95
|
- in `--prepare-only` mode, prebuilds the Rust bridge binary without starting it
|
|
95
|
-
- rewrites `.env.secure` for `BRIDGE_NETWORK_MODE=codespaces
|
|
96
|
+
- rewrites `.env.secure` for `BRIDGE_NETWORK_MODE=codespaces`, `BRIDGE_GITHUB_CODESPACES_AUTH=true`, and the selected engine list
|
|
97
|
+
- writes `CODEX_HOME=$HOME/.codex` in Codespaces so Codex-managed ChatGPT auth survives bridge restarts and Codespace wakes
|
|
96
98
|
- starts the bridge in the background unless you set `CLAWDEX_CODESPACES_SKIP_START=true` or pass `--no-start`
|
|
97
99
|
|
|
100
|
+
Clawdex-created Codespaces request a 45-minute idle timeout. The bridge emits a lightweight active-turn keepalive while a Codex, OpenCode, or Cursor turn is running, so active work has activity even if a long step is otherwise quiet. When no turn is running, the keepalive stops and GitHub can pause the Codespace normally to save cost.
|
|
101
|
+
|
|
98
102
|
That means the first Codespace create now front-loads the expensive bridge compile during `postCreateCommand`, so the later `postStartCommand` can usually start the bridge much faster.
|
|
99
103
|
|
|
100
104
|
The same bootstrap script is included in the published `clawdex-mobile` npm package. That lets the `clawdex-codespace` template stay minimal: it can install `clawdex-mobile@latest` globally in the devcontainer and invoke the packaged bootstrap against the current workspace instead of copying `scripts/*` and `services/rust-bridge/*` into the template repo.
|
|
@@ -105,6 +109,7 @@ Manual examples:
|
|
|
105
109
|
npm run codespaces:bootstrap -- --prepare-only
|
|
106
110
|
npm run codespaces:bootstrap
|
|
107
111
|
npm run codespaces:bootstrap -- --no-start
|
|
112
|
+
CLAWDEX_CODESPACES_ENGINES=codex,opencode,cursor npm run codespaces:bootstrap
|
|
108
113
|
```
|
|
109
114
|
|
|
110
115
|
Minimal template equivalent:
|
|
@@ -129,10 +134,10 @@ npm install
|
|
|
129
134
|
npm run secure:setup
|
|
130
135
|
```
|
|
131
136
|
|
|
132
|
-
To generate
|
|
137
|
+
To generate multi-harness config instead:
|
|
133
138
|
|
|
134
139
|
```bash
|
|
135
|
-
BRIDGE_ENABLED_ENGINES=codex,opencode npm run secure:setup
|
|
140
|
+
BRIDGE_ENABLED_ENGINES=codex,opencode,cursor npm run secure:setup
|
|
136
141
|
```
|
|
137
142
|
|
|
138
143
|
Creates/updates:
|
|
@@ -146,13 +151,13 @@ Creates/updates:
|
|
|
146
151
|
npm run secure:bridge
|
|
147
152
|
```
|
|
148
153
|
|
|
149
|
-
If you want a one-off
|
|
154
|
+
If you want a one-off multi-harness launch without rewriting `.env.secure`:
|
|
150
155
|
|
|
151
156
|
```bash
|
|
152
|
-
BRIDGE_ENABLED_ENGINES=codex,opencode npm run secure:bridge
|
|
157
|
+
BRIDGE_ENABLED_ENGINES=codex,opencode,cursor npm run secure:bridge
|
|
153
158
|
```
|
|
154
159
|
|
|
155
|
-
When
|
|
160
|
+
When multiple harnesses are selected, the bridge starts each backend and merges chat lists while still routing each thread by engine.
|
|
156
161
|
|
|
157
162
|
### 4) Pair from the mobile app
|
|
158
163
|
|
|
@@ -274,10 +279,14 @@ npm run teardown -- --yes
|
|
|
274
279
|
| `BRIDGE_GITHUB_CODESPACES_AUTH` | accept GitHub bearer tokens for the current codespace |
|
|
275
280
|
| `BRIDGE_GITHUB_CODESPACE_NAME` | codespace name used when validating GitHub bearer tokens |
|
|
276
281
|
| `BRIDGE_GITHUB_API_URL` | GitHub REST API base URL for Codespaces auth checks |
|
|
282
|
+
| `CODEX_HOME` | Codex auth/config home; set to `$HOME/.codex` in Codespaces so ChatGPT auth persists across bridge restarts |
|
|
277
283
|
| `CODEX_CLI_BIN` | codex executable |
|
|
278
284
|
| `BRIDGE_ACTIVE_ENGINE` | internal preferred routing backend used when multiple harnesses are enabled |
|
|
279
|
-
| `BRIDGE_ENABLED_ENGINES` | selected harnesses to expose (`codex`, `opencode`, or
|
|
285
|
+
| `BRIDGE_ENABLED_ENGINES` | selected harnesses to expose (`codex`, `opencode`, `cursor`, or a comma-separated mix) |
|
|
280
286
|
| `OPENCODE_CLI_BIN` | opencode executable for dual-engine startup |
|
|
287
|
+
| `CURSOR_APP_SERVER_BIN` | Cursor app-server executable, usually `cursor-app-server` |
|
|
288
|
+
| `CURSOR_API_KEY` | Cursor API key used by the Cursor SDK harness; collected by `clawdex init` when Cursor is selected |
|
|
289
|
+
| `CURSOR_MODEL` | optional Cursor model id for non-interactive host defaults; normal mobile chats send the selected model |
|
|
281
290
|
| `BRIDGE_OPENCODE_HOST` | loopback host for spawned opencode server |
|
|
282
291
|
| `BRIDGE_OPENCODE_PORT` | loopback port for spawned opencode server |
|
|
283
292
|
| `BRIDGE_OPENCODE_SERVER_USERNAME` | basic-auth username passed to opencode server |
|
|
@@ -291,7 +300,7 @@ npm run teardown -- --yes
|
|
|
291
300
|
|---|---|
|
|
292
301
|
| `EXPO_PUBLIC_HOST_BRIDGE_TOKEN` | token used by local mobile dev builds |
|
|
293
302
|
| `EXPO_PUBLIC_GITHUB_APP_CLIENT_ID` | GitHub App client ID for in-app Codespaces sign-in |
|
|
294
|
-
| `EXPO_PUBLIC_GITHUB_APP_SLUG` | GitHub App slug
|
|
303
|
+
| `EXPO_PUBLIC_GITHUB_APP_SLUG` | optional GitHub App slug, reserved for future manage-access flows |
|
|
295
304
|
| `EXPO_PUBLIC_GITHUB_APP_AUTH_BASE_URL` | HTTPS origin for the GitHub App auth worker (`/api/github/exchange`, `/api/github/refresh`, `/github/callback`) |
|
|
296
305
|
| `EXPO_PUBLIC_GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN` | forwarded port domain used to derive Codespaces bridge URLs (`app.github.dev` by default) |
|
|
297
306
|
| `EXPO_PUBLIC_GITHUB_CODESPACES_REPO_NAME` | repository name to sort matching Codespaces first in the in-app picker |
|
package/docs/troubleshooting.md
CHANGED
|
@@ -100,20 +100,27 @@ npm run codespaces:bootstrap -- --no-start
|
|
|
100
100
|
|
|
101
101
|
## Voice transcription says no credentials were found
|
|
102
102
|
|
|
103
|
-
- The bridge can transcribe with
|
|
104
|
-
- In GitHub Codespaces, finish the
|
|
105
|
-
- If you still see the error after logging in, restart the bridge once so
|
|
103
|
+
- The bridge can transcribe with `OPENAI_API_KEY`, `BRIDGE_CHATGPT_ACCESS_TOKEN`, a legacy bridge token cache, or the Codex-managed ChatGPT token in `$CODEX_HOME/auth.json`.
|
|
104
|
+
- In GitHub Codespaces, finish the Codex login step from the app first. Codex writes the login to `$HOME/.codex/auth.json`, and `.env.secure` sets `CODEX_HOME` to that persistent location.
|
|
105
|
+
- If you still see the error after logging in, restart the bridge once so app-server reloads the Codex auth home:
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
108
|
npm run secure:bridge
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
- You can inspect whether
|
|
111
|
+
- You can inspect whether Codex saved auth in the Codespace:
|
|
112
112
|
|
|
113
113
|
```bash
|
|
114
|
-
ls -la
|
|
114
|
+
ls -la "${CODEX_HOME:-$HOME/.codex}/auth.json"
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
+
## Codespace wakes but Codex says account authentication is required
|
|
118
|
+
|
|
119
|
+
- Codespaces should use Codex-managed auth, the same as a local bridge. The login is stored in `${CODEX_HOME:-$HOME/.codex}/auth.json`.
|
|
120
|
+
- The app starts Codex's normal ChatGPT web login first. It captures the OAuth callback on the phone and forwards that callback to the Codex loopback server inside the Codespace, so device-code login is only a fallback.
|
|
121
|
+
- Run `source .env.secure && ls -la "$CODEX_HOME/auth.json"` in the Codespace to confirm the auth file exists.
|
|
122
|
+
- If the file is missing, reopen GitHub Codespaces setup in the app and complete the Codex login step once. After that, bridge restarts and Codespace wakes should not require reauthentication.
|
|
123
|
+
|
|
117
124
|
## Local browser preview does not open
|
|
118
125
|
|
|
119
126
|
- The in-app browser only supports loopback targets from the bridge host: `localhost`, `127.0.0.1`, or `::1`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawdex-mobile",
|
|
3
|
-
"version": "5.1.3-internal.
|
|
3
|
+
"version": "5.1.3-internal.7",
|
|
4
4
|
"description": "Private-network mobile bridge and CLI for Codex and OpenCode",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codex",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"build": "npm run --workspaces build",
|
|
68
68
|
"typecheck": "npm run --workspaces typecheck",
|
|
69
69
|
"lint": "npm run --workspaces lint",
|
|
70
|
-
"test": "npm run test -w @codex/mac-bridge && npm run test -w apps/mobile && npm run test -w @codex/rust-bridge",
|
|
70
|
+
"test": "npm run test -w @codex/mac-bridge && npm run test -w apps/mobile && npm run test -w @codex/rust-bridge && npm run test -w @clawdex/cursor-app-server",
|
|
71
71
|
"teardown": "./scripts/teardown.sh"
|
|
72
72
|
}
|
|
73
73
|
}
|
|
@@ -90,6 +90,25 @@ function parseArgs(argv) {
|
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
function parseEngineList(value) {
|
|
94
|
+
const raw = typeof value === "string" && value.trim() ? value : "codex";
|
|
95
|
+
const engines = [];
|
|
96
|
+
for (const entry of raw.split(",")) {
|
|
97
|
+
const engine = entry.trim().toLowerCase();
|
|
98
|
+
if (!engine) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (!["codex", "opencode", "cursor"].includes(engine)) {
|
|
102
|
+
console.error(`error: unsupported Codespaces engine '${engine}'. Use codex, opencode, or cursor.`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
if (!engines.includes(engine)) {
|
|
106
|
+
engines.push(engine);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return engines.length > 0 ? engines : ["codex"];
|
|
110
|
+
}
|
|
111
|
+
|
|
93
112
|
function ensureCodespacesContext({ force }) {
|
|
94
113
|
if (force) {
|
|
95
114
|
return;
|
|
@@ -104,6 +123,9 @@ function ensureCodespacesContext({ force }) {
|
|
|
104
123
|
}
|
|
105
124
|
|
|
106
125
|
function runSetup(rootDir, workspaceDir) {
|
|
126
|
+
const enabledEngines = parseEngineList(
|
|
127
|
+
process.env.CLAWDEX_CODESPACES_ENGINES || process.env.BRIDGE_ENABLED_ENGINES || "codex"
|
|
128
|
+
);
|
|
107
129
|
const setupEnv = {
|
|
108
130
|
...process.env,
|
|
109
131
|
};
|
|
@@ -117,10 +139,10 @@ function runSetup(rootDir, workspaceDir) {
|
|
|
117
139
|
setupEnv.CLAWDEX_WORKSPACE_ROOT = workspaceDir;
|
|
118
140
|
setupEnv.BRIDGE_NETWORK_MODE = "codespaces";
|
|
119
141
|
setupEnv.BRIDGE_HOST_OVERRIDE = "127.0.0.1";
|
|
120
|
-
setupEnv.BRIDGE_ACTIVE_ENGINE =
|
|
121
|
-
setupEnv.BRIDGE_ENABLED_ENGINES = "
|
|
142
|
+
setupEnv.BRIDGE_ACTIVE_ENGINE = enabledEngines[0];
|
|
143
|
+
setupEnv.BRIDGE_ENABLED_ENGINES = enabledEngines.join(",");
|
|
122
144
|
|
|
123
|
-
console.log(
|
|
145
|
+
console.log(`Preparing .env.secure for GitHub Codespaces (${setupEnv.BRIDGE_ENABLED_ENGINES})...`);
|
|
124
146
|
const result = runCommand(path.join(rootDir, "scripts", "setup-secure-dev.sh"), [], {
|
|
125
147
|
cwd: workspaceDir,
|
|
126
148
|
env: setupEnv,
|
|
@@ -156,6 +178,50 @@ function ensureCodexCliInstalled(secureEnv) {
|
|
|
156
178
|
console.log("Codex CLI installed.");
|
|
157
179
|
}
|
|
158
180
|
|
|
181
|
+
function ensureNpmGlobalBinary({ binary, packageName, label }) {
|
|
182
|
+
if (commandExists(binary)) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.log(`${label} not found in this codespace. Installing via npm...`);
|
|
187
|
+
const installResult = runCommand("npm", ["install", "-g", packageName], {
|
|
188
|
+
cwd: resolveWorkspaceDir(),
|
|
189
|
+
env: process.env,
|
|
190
|
+
});
|
|
191
|
+
if ((installResult.status ?? 1) !== 0) {
|
|
192
|
+
console.error(`error: failed to install ${label} automatically.`);
|
|
193
|
+
process.exit(installResult.status ?? 1);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!commandExists(binary)) {
|
|
197
|
+
console.error(`error: ${label} still not found after install attempt: ${binary}`);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
console.log(`${label} installed.`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function ensureSelectedEngineTools(secureEnv) {
|
|
205
|
+
const enabledEngines = parseEngineList(readNonEmptyEnv(secureEnv, "BRIDGE_ENABLED_ENGINES") || "codex");
|
|
206
|
+
if (enabledEngines.includes("codex")) {
|
|
207
|
+
ensureCodexCliInstalled(secureEnv);
|
|
208
|
+
}
|
|
209
|
+
if (enabledEngines.includes("opencode")) {
|
|
210
|
+
ensureNpmGlobalBinary({
|
|
211
|
+
binary: readNonEmptyEnv(secureEnv, "OPENCODE_CLI_BIN") || "opencode",
|
|
212
|
+
packageName: "opencode-ai",
|
|
213
|
+
label: "OpenCode CLI",
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
if (enabledEngines.includes("cursor")) {
|
|
217
|
+
ensureNpmGlobalBinary({
|
|
218
|
+
binary: readNonEmptyEnv(secureEnv, "CURSOR_APP_SERVER_BIN") || "cursor-app-server",
|
|
219
|
+
packageName: "@clawdex/cursor-app-server",
|
|
220
|
+
label: "Cursor app server",
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
159
225
|
function prepareBridgeBinary(rootDir, workspaceDir, secureEnv) {
|
|
160
226
|
console.log("Prebuilding bridge binary for faster Codespaces startup...");
|
|
161
227
|
const prepareEnv = {
|
|
@@ -219,7 +285,7 @@ function main() {
|
|
|
219
285
|
return;
|
|
220
286
|
}
|
|
221
287
|
|
|
222
|
-
|
|
288
|
+
ensureSelectedEngineTools(secureEnv);
|
|
223
289
|
if (options.prepareOnly) {
|
|
224
290
|
prepareBridgeBinary(rootDir, workspaceDir, secureEnv);
|
|
225
291
|
console.log("Codespaces bootstrap prepared Codex and the bridge binary without starting the bridge.");
|
|
@@ -13,6 +13,9 @@ MOBILE_ENV_EXAMPLE="$ROOT_DIR/apps/mobile/.env.example"
|
|
|
13
13
|
BRIDGE_ACTIVE_ENGINE="${BRIDGE_ACTIVE_ENGINE:-codex}"
|
|
14
14
|
BRIDGE_ENABLED_ENGINES="${BRIDGE_ENABLED_ENGINES:-$BRIDGE_ACTIVE_ENGINE}"
|
|
15
15
|
OPENCODE_CLI_BIN="${OPENCODE_CLI_BIN:-opencode}"
|
|
16
|
+
CURSOR_APP_SERVER_BIN="${CURSOR_APP_SERVER_BIN:-cursor-app-server}"
|
|
17
|
+
CURSOR_API_KEY="${CURSOR_API_KEY:-}"
|
|
18
|
+
CURSOR_MODEL="${CURSOR_MODEL:-}"
|
|
16
19
|
BRIDGE_CONNECT_URL=""
|
|
17
20
|
BRIDGE_PREVIEW_CONNECT_URL=""
|
|
18
21
|
|
|
@@ -278,10 +281,10 @@ case "$BRIDGE_NETWORK_MODE" in
|
|
|
278
281
|
esac
|
|
279
282
|
|
|
280
283
|
case "$BRIDGE_ACTIVE_ENGINE" in
|
|
281
|
-
codex|opencode)
|
|
284
|
+
codex|opencode|cursor)
|
|
282
285
|
;;
|
|
283
286
|
*)
|
|
284
|
-
echo "error: BRIDGE_ACTIVE_ENGINE must be 'codex' or '
|
|
287
|
+
echo "error: BRIDGE_ACTIVE_ENGINE must be 'codex', 'opencode', or 'cursor'." >&2
|
|
285
288
|
exit 1
|
|
286
289
|
;;
|
|
287
290
|
esac
|
|
@@ -301,7 +304,7 @@ validate_enabled_engines() {
|
|
|
301
304
|
continue
|
|
302
305
|
fi
|
|
303
306
|
case "$normalized" in
|
|
304
|
-
codex|opencode)
|
|
307
|
+
codex|opencode|cursor)
|
|
305
308
|
;;
|
|
306
309
|
*)
|
|
307
310
|
return 1
|
|
@@ -323,7 +326,7 @@ validate_enabled_engines() {
|
|
|
323
326
|
}
|
|
324
327
|
|
|
325
328
|
if ! validate_enabled_engines "$BRIDGE_ENABLED_ENGINES"; then
|
|
326
|
-
echo "error: BRIDGE_ENABLED_ENGINES must contain one or more of 'codex' and '
|
|
329
|
+
echo "error: BRIDGE_ENABLED_ENGINES must contain one or more of 'codex', 'opencode', and 'cursor'." >&2
|
|
327
330
|
exit 1
|
|
328
331
|
fi
|
|
329
332
|
|
|
@@ -382,9 +385,13 @@ fi
|
|
|
382
385
|
|
|
383
386
|
GITHUB_CODESPACES_AUTH_ENABLED="false"
|
|
384
387
|
GITHUB_CODESPACES_NAME=""
|
|
388
|
+
CODEX_HOME_ENV_BLOCK=""
|
|
385
389
|
if [[ "$BRIDGE_NETWORK_MODE" == "codespaces" ]]; then
|
|
386
390
|
GITHUB_CODESPACES_AUTH_ENABLED="true"
|
|
387
391
|
GITHUB_CODESPACES_NAME="$(printf '%s' "${CODESPACE_NAME:-}" | tr -d '[:space:]')"
|
|
392
|
+
CODEX_AUTH_HOME="${CODEX_HOME:-$HOME/.codex}"
|
|
393
|
+
mkdir -p "$CODEX_AUTH_HOME"
|
|
394
|
+
CODEX_HOME_ENV_BLOCK="CODEX_HOME=$CODEX_AUTH_HOME"
|
|
388
395
|
fi
|
|
389
396
|
|
|
390
397
|
cat > "$SECURE_ENV_FILE" <<EOT
|
|
@@ -401,8 +408,12 @@ BRIDGE_GITHUB_CODESPACE_NAME=$GITHUB_CODESPACES_NAME
|
|
|
401
408
|
BRIDGE_GITHUB_API_URL=https://api.github.com
|
|
402
409
|
BRIDGE_ACTIVE_ENGINE=$BRIDGE_ACTIVE_ENGINE
|
|
403
410
|
BRIDGE_ENABLED_ENGINES=$BRIDGE_ENABLED_ENGINES
|
|
411
|
+
$CODEX_HOME_ENV_BLOCK
|
|
404
412
|
CODEX_CLI_BIN=codex
|
|
405
413
|
OPENCODE_CLI_BIN=$OPENCODE_CLI_BIN
|
|
414
|
+
CURSOR_APP_SERVER_BIN=$CURSOR_APP_SERVER_BIN
|
|
415
|
+
CURSOR_API_KEY=$CURSOR_API_KEY
|
|
416
|
+
CURSOR_MODEL=$CURSOR_MODEL
|
|
406
417
|
BRIDGE_WORKDIR=$ROOT_DIR
|
|
407
418
|
EOT
|
|
408
419
|
|
package/scripts/setup-wizard.sh
CHANGED
|
@@ -37,6 +37,7 @@ declare -a SELECTED_ENGINES=()
|
|
|
37
37
|
ENGINE_SELECTION_PRESET="false"
|
|
38
38
|
AUTO_START="true"
|
|
39
39
|
SECURE_ENV_FILE="$ROOT_DIR/.env.secure"
|
|
40
|
+
CURSOR_CONFIG_NEEDS_WRITE="false"
|
|
40
41
|
MENU_RESULT=""
|
|
41
42
|
MENU_MULTI_RESULT=""
|
|
42
43
|
MENU_MULTI_PRESELECTED=""
|
|
@@ -86,9 +87,9 @@ Usage: $(basename "$0") [options]
|
|
|
86
87
|
|
|
87
88
|
Options:
|
|
88
89
|
--no-start Configure everything but do not start bridge
|
|
89
|
-
--engine <codex|opencode>
|
|
90
|
+
--engine <codex|opencode|cursor>
|
|
90
91
|
Select a single harness non-interactively
|
|
91
|
-
--engines <codex,opencode>
|
|
92
|
+
--engines <codex,opencode,cursor>
|
|
92
93
|
Select one or more harnesses non-interactively
|
|
93
94
|
-h, --help Show this help
|
|
94
95
|
EOF
|
|
@@ -96,7 +97,7 @@ EOF
|
|
|
96
97
|
|
|
97
98
|
validate_engine_name() {
|
|
98
99
|
case "$1" in
|
|
99
|
-
codex|opencode)
|
|
100
|
+
codex|opencode|cursor)
|
|
100
101
|
return 0
|
|
101
102
|
;;
|
|
102
103
|
*)
|
|
@@ -110,6 +111,9 @@ format_engine_name() {
|
|
|
110
111
|
opencode)
|
|
111
112
|
printf 'OpenCode'
|
|
112
113
|
;;
|
|
114
|
+
cursor)
|
|
115
|
+
printf 'Cursor'
|
|
116
|
+
;;
|
|
113
117
|
*)
|
|
114
118
|
printf 'Codex'
|
|
115
119
|
;;
|
|
@@ -251,6 +255,9 @@ engine_from_menu_label() {
|
|
|
251
255
|
"OpenCode")
|
|
252
256
|
printf 'opencode'
|
|
253
257
|
;;
|
|
258
|
+
"Cursor")
|
|
259
|
+
printf 'cursor'
|
|
260
|
+
;;
|
|
254
261
|
*)
|
|
255
262
|
return 1
|
|
256
263
|
;;
|
|
@@ -295,12 +302,12 @@ parse_args() {
|
|
|
295
302
|
;;
|
|
296
303
|
--engine)
|
|
297
304
|
if (($# < 2)); then
|
|
298
|
-
echo "error: --engine requires a value ('codex' or '
|
|
305
|
+
echo "error: --engine requires a value ('codex', 'opencode', or 'cursor')." >&2
|
|
299
306
|
print_usage >&2
|
|
300
307
|
exit 1
|
|
301
308
|
fi
|
|
302
309
|
if ! validate_engine_name "$2"; then
|
|
303
|
-
echo "error: unsupported engine '$2'. Use 'codex' or '
|
|
310
|
+
echo "error: unsupported engine '$2'. Use 'codex', 'opencode', or 'cursor'." >&2
|
|
304
311
|
print_usage >&2
|
|
305
312
|
exit 1
|
|
306
313
|
fi
|
|
@@ -311,12 +318,12 @@ parse_args() {
|
|
|
311
318
|
;;
|
|
312
319
|
--engines)
|
|
313
320
|
if (($# < 2)); then
|
|
314
|
-
echo "error: --engines requires a comma-separated value (for example: codex,opencode)." >&2
|
|
321
|
+
echo "error: --engines requires a comma-separated value (for example: codex,opencode,cursor)." >&2
|
|
315
322
|
print_usage >&2
|
|
316
323
|
exit 1
|
|
317
324
|
fi
|
|
318
325
|
if ! parse_engine_list_csv "$2"; then
|
|
319
|
-
echo "error: unsupported --engines value '$2'. Use one or more of 'codex' and '
|
|
326
|
+
echo "error: unsupported --engines value '$2'. Use one or more of 'codex', 'opencode', and 'cursor'." >&2
|
|
320
327
|
print_usage >&2
|
|
321
328
|
exit 1
|
|
322
329
|
fi
|
|
@@ -916,6 +923,25 @@ prompt_manual_ipv4() {
|
|
|
916
923
|
done
|
|
917
924
|
}
|
|
918
925
|
|
|
926
|
+
prompt_secret_value() {
|
|
927
|
+
local prompt="$1"
|
|
928
|
+
local value=""
|
|
929
|
+
|
|
930
|
+
while true; do
|
|
931
|
+
printf "%s %s %s " "$RAIL_GLYPH" "$RAIL_BRANCH" "$prompt" >&2
|
|
932
|
+
IFS= read -rs value || abort_wizard
|
|
933
|
+
printf "\n" >&2
|
|
934
|
+
value="$(printf '%s' "$value" | tr -d '\r\n')"
|
|
935
|
+
|
|
936
|
+
if [[ -n "$value" ]]; then
|
|
937
|
+
printf '%s' "$value"
|
|
938
|
+
return 0
|
|
939
|
+
fi
|
|
940
|
+
|
|
941
|
+
printf "%s %s %s\n" "$RAIL_GLYPH" "$RAIL_CHILD" "${YELLOW}Value cannot be empty.${RESET}" >&2
|
|
942
|
+
done
|
|
943
|
+
}
|
|
944
|
+
|
|
919
945
|
extract_env_value() {
|
|
920
946
|
local file="$1"
|
|
921
947
|
local key="$2"
|
|
@@ -1141,9 +1167,15 @@ choose_runtime_engine() {
|
|
|
1141
1167
|
fi
|
|
1142
1168
|
MENU_MULTI_PRESELECTED+="OpenCode"
|
|
1143
1169
|
fi
|
|
1170
|
+
if selected_engines_contains "cursor"; then
|
|
1171
|
+
if [[ -n "$MENU_MULTI_PRESELECTED" ]]; then
|
|
1172
|
+
MENU_MULTI_PRESELECTED+=","
|
|
1173
|
+
fi
|
|
1174
|
+
MENU_MULTI_PRESELECTED+="Cursor"
|
|
1175
|
+
fi
|
|
1144
1176
|
|
|
1145
1177
|
info "Select the harnesses this phone should control."
|
|
1146
|
-
menu_multiselect "Harnesses to control" "Codex" "OpenCode"
|
|
1178
|
+
menu_multiselect "Harnesses to control" "Codex" "OpenCode" "Cursor"
|
|
1147
1179
|
SELECTED_ENGINES=()
|
|
1148
1180
|
for label in "${MENU_MULTI_RESULT_ITEMS[@]}"; do
|
|
1149
1181
|
SELECTED_ENGINES+=("$(engine_from_menu_label "$label")")
|
|
@@ -1274,6 +1306,66 @@ ensure_opencode_cli() {
|
|
|
1274
1306
|
fi
|
|
1275
1307
|
}
|
|
1276
1308
|
|
|
1309
|
+
ensure_cursor_app_server() {
|
|
1310
|
+
local existing_api_key=""
|
|
1311
|
+
local existing_model=""
|
|
1312
|
+
local provided_api_key="${CURSOR_API_KEY:-}"
|
|
1313
|
+
|
|
1314
|
+
if [[ -f "$ROOT_DIR/services/cursor-app-server/package.json" ]]; then
|
|
1315
|
+
info "Building Cursor app-server package."
|
|
1316
|
+
npm run build -w @clawdex/cursor-app-server
|
|
1317
|
+
hash -r
|
|
1318
|
+
fi
|
|
1319
|
+
|
|
1320
|
+
while ! command -v cursor-app-server >/dev/null 2>&1; do
|
|
1321
|
+
warn "Cursor app-server command not found in PATH."
|
|
1322
|
+
if confirm_prompt "Try installing @clawdex/cursor-app-server via npm now?" "N"; then
|
|
1323
|
+
if npm install -g @clawdex/cursor-app-server; then
|
|
1324
|
+
hash -r
|
|
1325
|
+
else
|
|
1326
|
+
warn "Automatic install failed."
|
|
1327
|
+
fi
|
|
1328
|
+
fi
|
|
1329
|
+
|
|
1330
|
+
if ! command -v cursor-app-server >/dev/null 2>&1 && ! confirm_prompt "Retry Cursor app-server check?" "Y"; then
|
|
1331
|
+
abort_wizard "Install @clawdex/cursor-app-server and rerun: clawdex init --engine cursor"
|
|
1332
|
+
fi
|
|
1333
|
+
done
|
|
1334
|
+
|
|
1335
|
+
ok "Found cursor-app-server: $(command -v cursor-app-server)"
|
|
1336
|
+
|
|
1337
|
+
existing_api_key="$(extract_env_value "$SECURE_ENV_FILE" "CURSOR_API_KEY")"
|
|
1338
|
+
existing_model="$(extract_env_value "$SECURE_ENV_FILE" "CURSOR_MODEL")"
|
|
1339
|
+
CURSOR_API_KEY="${CURSOR_API_KEY:-$existing_api_key}"
|
|
1340
|
+
CURSOR_MODEL="${CURSOR_MODEL:-$existing_model}"
|
|
1341
|
+
|
|
1342
|
+
if [[ -n "$CURSOR_API_KEY" ]]; then
|
|
1343
|
+
if [[ -n "$provided_api_key" ]]; then
|
|
1344
|
+
ok "Cursor API key: provided from environment."
|
|
1345
|
+
if [[ "$provided_api_key" != "$existing_api_key" ]]; then
|
|
1346
|
+
CURSOR_CONFIG_NEEDS_WRITE="true"
|
|
1347
|
+
fi
|
|
1348
|
+
else
|
|
1349
|
+
ok "Cursor API key: configured in secure bridge config."
|
|
1350
|
+
fi
|
|
1351
|
+
if [[ "$FLOW" == "manual" ]] && confirm_prompt "Replace saved Cursor API key now?" "N"; then
|
|
1352
|
+
CURSOR_API_KEY="$(prompt_secret_value "Enter Cursor API key:")"
|
|
1353
|
+
CURSOR_CONFIG_NEEDS_WRITE="true"
|
|
1354
|
+
ok "Cursor API key will be updated in $SECURE_ENV_FILE."
|
|
1355
|
+
fi
|
|
1356
|
+
else
|
|
1357
|
+
warn "Cursor requires a Cursor API key on the bridge host."
|
|
1358
|
+
if ! confirm_prompt "Add Cursor API key now?" "Y"; then
|
|
1359
|
+
abort_wizard "Cursor was selected but CURSOR_API_KEY is missing. Re-run clawdex init after creating a Cursor API key."
|
|
1360
|
+
fi
|
|
1361
|
+
CURSOR_API_KEY="$(prompt_secret_value "Enter Cursor API key:")"
|
|
1362
|
+
CURSOR_CONFIG_NEEDS_WRITE="true"
|
|
1363
|
+
ok "Cursor API key will be saved in $SECURE_ENV_FILE."
|
|
1364
|
+
fi
|
|
1365
|
+
|
|
1366
|
+
export CURSOR_API_KEY CURSOR_MODEL
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1277
1369
|
ensure_selected_engine_clis() {
|
|
1278
1370
|
local engine=""
|
|
1279
1371
|
|
|
@@ -1289,6 +1381,9 @@ ensure_selected_engine_clis() {
|
|
|
1289
1381
|
opencode)
|
|
1290
1382
|
ensure_opencode_cli
|
|
1291
1383
|
;;
|
|
1384
|
+
cursor)
|
|
1385
|
+
ensure_cursor_app_server
|
|
1386
|
+
;;
|
|
1292
1387
|
*)
|
|
1293
1388
|
abort_wizard "Unsupported harness '$engine'."
|
|
1294
1389
|
;;
|
|
@@ -1795,6 +1890,9 @@ else
|
|
|
1795
1890
|
elif [[ "$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_ENABLED_ENGINES")" != "$(selected_engines_csv)" ]]; then
|
|
1796
1891
|
section "Write secure config"
|
|
1797
1892
|
BRIDGE_NETWORK_MODE="$NETWORK_MODE" BRIDGE_HOST_OVERRIDE="$BRIDGE_HOST" BRIDGE_ACTIVE_ENGINE="$ACTIVE_ENGINE" BRIDGE_ENABLED_ENGINES="$(selected_engines_csv)" "$SCRIPT_DIR/setup-secure-dev.sh"
|
|
1893
|
+
elif [[ "$CURSOR_CONFIG_NEEDS_WRITE" == "true" ]]; then
|
|
1894
|
+
section "Write secure config"
|
|
1895
|
+
BRIDGE_NETWORK_MODE="$NETWORK_MODE" BRIDGE_HOST_OVERRIDE="$BRIDGE_HOST" BRIDGE_ACTIVE_ENGINE="$ACTIVE_ENGINE" BRIDGE_ENABLED_ENGINES="$(selected_engines_csv)" "$SCRIPT_DIR/setup-secure-dev.sh"
|
|
1798
1896
|
fi
|
|
1799
1897
|
fi
|
|
1800
1898
|
|