clawdex-mobile 5.1.3-internal.6 → 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 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`, `EXPO_PUBLIC_GITHUB_APP_SLUG`, and `EXPO_PUBLIC_GITHUB_APP_AUTH_BASE_URL`, users can now tap `Use GitHub Codespaces` in onboarding/settings, complete one in-app GitHub App install/auth flow, pick a Codespace, and connect without manually copying the bridge token.
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 enable `Request user authorization (OAuth) during installation`.
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 Codex 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 only enabled engine and starts the bridge in the background. You can rerun either command manually any time.
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
- ## OpenCode Setup
104
+ ## Extra Harness Setup
96
105
 
97
- OpenCode is supported directly from the CLI now.
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 both harnesses from one bridge.
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 opencode` if you want a single-harness setup.
111
- - Use `clawdex init --engines codex,opencode` if you want both non-interactively.
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 both Codex and OpenCode:
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 both backends and the mobile app can control both from one UI.
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` or `--engine opencode`.
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`, `EXPO_PUBLIC_GITHUB_APP_SLUG`, and `EXPO_PUBLIC_GITHUB_APP_AUTH_BASE_URL`, onboarding/settings can now open one GitHub App install/auth flow, approve only the repositories they want, start the Codespace, and connect directly with the same GitHub App user token instead of copying `BRIDGE_AUTH_TOKEN`
67
- - That same in-app GitHub sign-in also bootstraps GitHub git auth inside the Codespace so `git clone`, `git push`, GitHub HTTPS remotes, and common `git@github.com:...` SSH-style remotes can reuse the app login without extra account setup
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` with `BRIDGE_ACTIVE_ENGINE=codex`, `BRIDGE_ENABLED_ENGINES=codex`, and `BRIDGE_GITHUB_CODESPACES_AUTH=true`
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 OpenCode-first config instead:
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 OpenCode launch without rewriting `.env.secure`:
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 both CLIs are selected, the bridge starts both backends and merges chat lists while still routing each thread by engine.
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 both) |
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 used to open install/manage-access pages for repository selection |
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 |
@@ -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 either `OPENAI_API_KEY`, `BRIDGE_CHATGPT_ACCESS_TOKEN`, or the same ChatGPT auth tokens already used for Codex login.
104
- - In GitHub Codespaces, finish the ChatGPT/Codex login step from the app first. The bridge will persist those tokens to `BRIDGE_WORKDIR/.clawdex-chatgpt-auth.json` and reuse them for voice transcription.
105
- - If you still see the error after logging in, restart the bridge once so it reloads the persisted auth cache:
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 the bridge captured the token bundle:
111
+ - You can inspect whether Codex saved auth in the Codespace:
112
112
 
113
113
  ```bash
114
- ls -la .clawdex-chatgpt-auth.json
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.6",
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 = "codex";
121
- setupEnv.BRIDGE_ENABLED_ENGINES = "codex";
142
+ setupEnv.BRIDGE_ACTIVE_ENGINE = enabledEngines[0];
143
+ setupEnv.BRIDGE_ENABLED_ENGINES = enabledEngines.join(",");
122
144
 
123
- console.log("Preparing .env.secure for GitHub Codespaces...");
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
- ensureCodexCliInstalled(secureEnv);
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 'opencode'." >&2
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 'opencode'." >&2
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
 
@@ -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 'opencode')." >&2
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 'opencode'." >&2
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 'opencode'." >&2
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
 
@@ -848,8 +848,8 @@ async function start() {
848
848
 
849
849
  const fileEnv = readEnvFile(secureEnvFile);
850
850
  const env = {
851
- ...fileEnv,
852
851
  ...process.env,
852
+ ...fileEnv,
853
853
  CLAWDEX_WORKSPACE_ROOT: workspaceDir,
854
854
  INIT_CWD: process.env.INIT_CWD || workspaceDir,
855
855
  };
@@ -149,7 +149,7 @@ dependencies = [
149
149
 
150
150
  [[package]]
151
151
  name = "codex-rust-bridge"
152
- version = "5.1.3-internal.6"
152
+ version = "5.1.3-internal.7"
153
153
  dependencies = [
154
154
  "axum",
155
155
  "base64",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "codex-rust-bridge"
3
- version = "5.1.3-internal.6"
3
+ version = "5.1.3-internal.7"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]