clawdex-mobile 5.1.3-internal.9 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +6 -55
  3. package/bin/clawdex.js +2 -2
  4. package/docs/setup-and-operations.md +13 -100
  5. package/docs/troubleshooting.md +2 -71
  6. package/package.json +17 -6
  7. package/scripts/setup-secure-dev.sh +5 -79
  8. package/scripts/setup-wizard.sh +37 -87
  9. package/scripts/start-bridge-secure.js +9 -269
  10. package/services/cursor-app-server/README.md +39 -0
  11. package/services/cursor-app-server/dist/appServer.d.ts +52 -0
  12. package/services/cursor-app-server/dist/appServer.js +780 -0
  13. package/services/cursor-app-server/dist/appServer.js.map +1 -0
  14. package/services/cursor-app-server/dist/cursorWorkspace.d.ts +7 -0
  15. package/services/cursor-app-server/dist/cursorWorkspace.js +126 -0
  16. package/services/cursor-app-server/dist/cursorWorkspace.js.map +1 -0
  17. package/services/cursor-app-server/dist/index.d.ts +4 -0
  18. package/services/cursor-app-server/dist/index.js +4 -0
  19. package/services/cursor-app-server/dist/index.js.map +1 -0
  20. package/services/cursor-app-server/dist/input.d.ts +27 -0
  21. package/services/cursor-app-server/dist/input.js +143 -0
  22. package/services/cursor-app-server/dist/input.js.map +1 -0
  23. package/services/cursor-app-server/dist/jsonRpc.d.ts +15 -0
  24. package/services/cursor-app-server/dist/jsonRpc.js +93 -0
  25. package/services/cursor-app-server/dist/jsonRpc.js.map +1 -0
  26. package/services/cursor-app-server/dist/projection.d.ts +8 -0
  27. package/services/cursor-app-server/dist/projection.js +507 -0
  28. package/services/cursor-app-server/dist/projection.js.map +1 -0
  29. package/services/cursor-app-server/dist/sdkDriver.d.ts +50 -0
  30. package/services/cursor-app-server/dist/sdkDriver.js +166 -0
  31. package/services/cursor-app-server/dist/sdkDriver.js.map +1 -0
  32. package/services/cursor-app-server/dist/stdio.d.ts +2 -0
  33. package/services/cursor-app-server/dist/stdio.js +7 -0
  34. package/services/cursor-app-server/dist/stdio.js.map +1 -0
  35. package/services/cursor-app-server/dist/types.d.ts +218 -0
  36. package/services/cursor-app-server/dist/types.js +2 -0
  37. package/services/cursor-app-server/dist/types.js.map +1 -0
  38. package/services/cursor-app-server/package.json +43 -0
  39. package/services/rust-bridge/Cargo.lock +1 -1
  40. package/services/rust-bridge/Cargo.toml +1 -1
  41. package/services/rust-bridge/src/main.rs +1005 -388
  42. package/vendor/bridge-binaries/darwin-arm64/codex-rust-bridge +0 -0
  43. package/vendor/bridge-binaries/darwin-x64/codex-rust-bridge +0 -0
  44. package/vendor/bridge-binaries/linux-arm64/codex-rust-bridge +0 -0
  45. package/vendor/bridge-binaries/linux-armv7l/codex-rust-bridge +0 -0
  46. package/vendor/bridge-binaries/linux-x64/codex-rust-bridge +0 -0
  47. package/vendor/bridge-binaries/win32-x64/codex-rust-bridge.exe +0 -0
  48. package/scripts/codespaces-bootstrap.js +0 -297
@@ -1129,11 +1129,7 @@ choose_config_action() {
1129
1129
  }
1130
1130
 
1131
1131
  choose_bridge_network_mode() {
1132
- if [[ "${CODESPACES:-}" == "true" ]]; then
1133
- menu_select "Bridge network mode" "GitHub Codespaces" "Local (LAN)" "Tailscale"
1134
- else
1135
- menu_select "Bridge network mode" "Local (LAN)" "Tailscale" "GitHub Codespaces"
1136
- fi
1132
+ menu_select "Bridge network mode" "Local (LAN)" "Tailscale"
1137
1133
  case "$MENU_RESULT" in
1138
1134
  "Local (LAN)")
1139
1135
  NETWORK_MODE="local"
@@ -1143,10 +1139,6 @@ choose_bridge_network_mode() {
1143
1139
  NETWORK_MODE="tailscale"
1144
1140
  info "Tailscale mode selected."
1145
1141
  ;;
1146
- "GitHub Codespaces")
1147
- NETWORK_MODE="codespaces"
1148
- info "GitHub Codespaces mode selected."
1149
- ;;
1150
1142
  *)
1151
1143
  abort_wizard "Unexpected bridge network mode."
1152
1144
  ;;
@@ -1196,12 +1188,6 @@ infer_network_mode_from_host() {
1196
1188
 
1197
1189
  infer_network_mode() {
1198
1190
  local host="$1"
1199
- local connect_url="$2"
1200
- connect_url="$(printf '%s' "$connect_url" | tr -d '[:space:]')"
1201
- if [[ "$connect_url" == https://*.app.github.dev* ]]; then
1202
- printf '%s' "codespaces"
1203
- return 0
1204
- fi
1205
1191
  infer_network_mode_from_host "$host"
1206
1192
  }
1207
1193
 
@@ -1307,32 +1293,40 @@ ensure_opencode_cli() {
1307
1293
  }
1308
1294
 
1309
1295
  ensure_cursor_app_server() {
1296
+ local existing_app_server_bin=""
1310
1297
  local existing_api_key=""
1311
1298
  local existing_model=""
1299
+ local provided_app_server_bin="${CURSOR_APP_SERVER_BIN:-}"
1312
1300
  local provided_api_key="${CURSOR_API_KEY:-}"
1301
+ local resolved_cursor_app_server_bin=""
1302
+
1303
+ existing_app_server_bin="$(extract_env_value "$SECURE_ENV_FILE" "CURSOR_APP_SERVER_BIN")"
1313
1304
 
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
1305
+ if [[ -n "$provided_app_server_bin" ]]; then
1306
+ resolved_cursor_app_server_bin="$CURSOR_APP_SERVER_BIN"
1307
+ elif [[ -n "$existing_app_server_bin" ]]; then
1308
+ resolved_cursor_app_server_bin="$existing_app_server_bin"
1309
+ elif command -v cursor-app-server >/dev/null 2>&1; then
1310
+ resolved_cursor_app_server_bin="$(command -v cursor-app-server)"
1318
1311
  fi
1319
1312
 
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
1313
+ if [[ -z "$resolved_cursor_app_server_bin" ]]; then
1314
+ abort_wizard "cursor-app-server was not found. Upgrade clawdex-mobile so npm links the bundled command, then rerun: clawdex init --engine cursor"
1315
+ fi
1329
1316
 
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"
1317
+ if [[ "$resolved_cursor_app_server_bin" == */* ]]; then
1318
+ if [[ ! -x "$resolved_cursor_app_server_bin" ]]; then
1319
+ abort_wizard "cursor-app-server was configured as '$resolved_cursor_app_server_bin', but that file is not executable."
1332
1320
  fi
1333
- done
1321
+ elif ! command -v "$resolved_cursor_app_server_bin" >/dev/null 2>&1; then
1322
+ abort_wizard "cursor-app-server command '$resolved_cursor_app_server_bin' was not found in PATH."
1323
+ fi
1334
1324
 
1335
- ok "Found cursor-app-server: $(command -v cursor-app-server)"
1325
+ CURSOR_APP_SERVER_BIN="$resolved_cursor_app_server_bin"
1326
+ ok "Found cursor-app-server: $CURSOR_APP_SERVER_BIN"
1327
+ if [[ "$CURSOR_APP_SERVER_BIN" != "$existing_app_server_bin" ]]; then
1328
+ CURSOR_CONFIG_NEEDS_WRITE="true"
1329
+ fi
1336
1330
 
1337
1331
  existing_api_key="$(extract_env_value "$SECURE_ENV_FILE" "CURSOR_API_KEY")"
1338
1332
  existing_model="$(extract_env_value "$SECURE_ENV_FILE" "CURSOR_MODEL")"
@@ -1349,12 +1343,20 @@ ensure_cursor_app_server() {
1349
1343
  ok "Cursor API key: configured in secure bridge config."
1350
1344
  fi
1351
1345
  if [[ "$FLOW" == "manual" ]] && confirm_prompt "Replace saved Cursor API key now?" "N"; then
1346
+ print_note_box "Cursor API key" "Create a Cursor account API key from Cursor Dashboard > Integrations > User API Keys, then paste it here.
1347
+ Cursor docs: https://docs.cursor.com/en/cli/reference/authentication
1348
+
1349
+ This is the Cursor agent/SDK key used by Clawdex. It is not the OpenAI, Anthropic, or other provider key configured inside the Cursor editor."
1352
1350
  CURSOR_API_KEY="$(prompt_secret_value "Enter Cursor API key:")"
1353
1351
  CURSOR_CONFIG_NEEDS_WRITE="true"
1354
1352
  ok "Cursor API key will be updated in $SECURE_ENV_FILE."
1355
1353
  fi
1356
1354
  else
1357
1355
  warn "Cursor requires a Cursor API key on the bridge host."
1356
+ print_note_box "Cursor API key" "Create a Cursor account API key from Cursor Dashboard > Integrations > User API Keys, then paste it here.
1357
+ Cursor docs: https://docs.cursor.com/en/cli/reference/authentication
1358
+
1359
+ This is the Cursor agent/SDK key used by Clawdex. It is not the OpenAI, Anthropic, or other provider key configured inside the Cursor editor."
1358
1360
  if ! confirm_prompt "Add Cursor API key now?" "Y"; then
1359
1361
  abort_wizard "Cursor was selected but CURSOR_API_KEY is missing. Re-run clawdex init after creating a Cursor API key."
1360
1362
  fi
@@ -1363,7 +1365,7 @@ ensure_cursor_app_server() {
1363
1365
  ok "Cursor API key will be saved in $SECURE_ENV_FILE."
1364
1366
  fi
1365
1367
 
1366
- export CURSOR_API_KEY CURSOR_MODEL
1368
+ export CURSOR_APP_SERVER_BIN CURSOR_API_KEY CURSOR_MODEL
1367
1369
  }
1368
1370
 
1369
1371
  ensure_selected_engine_clis() {
@@ -1641,16 +1643,6 @@ Steps:
1641
1643
  - Scan the bridge token QR from the bridge terminal."
1642
1644
  }
1643
1645
 
1644
- print_phone_codespaces_note() {
1645
- print_note_box "Phone setup (GitHub Codespaces)" "Use the forwarded HTTPS bridge URL from this codespace.
1646
-
1647
- Steps:
1648
- - Keep the codespace running while using Clawdex.
1649
- - The bridge startup script will try to make bridge ports public automatically on each start.
1650
- - Pair in the mobile app with the shown HTTPS bridge URL and bridge token.
1651
- - If pairing fails after a codespace restart, rerun the bridge start command or set the bridge + preview forwarded ports public again with 'gh codespace ports visibility ...'."
1652
- }
1653
-
1654
1646
  confirm_phone_local_quickstart() {
1655
1647
  local note_shown="false"
1656
1648
 
@@ -1721,34 +1713,6 @@ confirm_phone_local_ready() {
1721
1713
  confirm_phone_local_manual
1722
1714
  }
1723
1715
 
1724
- confirm_phone_codespaces_ready() {
1725
- print_phone_codespaces_note
1726
- if [[ "$FLOW" == "quickstart" ]]; then
1727
- if ! confirm_prompt "Will you pair from the forwarded HTTPS bridge URL shown after startup?" "Y"; then
1728
- abort_wizard "Use the forwarded GitHub Codespaces bridge URL, then rerun: npm run setup:wizard"
1729
- fi
1730
- return 0
1731
- fi
1732
-
1733
- menu_select "Phone Codespaces status" \
1734
- "Ready to pair from forwarded URL" \
1735
- "Show instructions again" \
1736
- "Abort"
1737
-
1738
- case "$MENU_RESULT" in
1739
- "Ready to pair from forwarded URL")
1740
- return 0
1741
- ;;
1742
- "Show instructions again")
1743
- confirm_phone_codespaces_ready
1744
- return 0
1745
- ;;
1746
- *)
1747
- abort_wizard "Use the forwarded GitHub Codespaces bridge URL, then rerun: npm run setup:wizard"
1748
- ;;
1749
- esac
1750
- }
1751
-
1752
1716
  confirm_phone_network_ready() {
1753
1717
  case "$NETWORK_MODE" in
1754
1718
  tailscale)
@@ -1757,9 +1721,6 @@ confirm_phone_network_ready() {
1757
1721
  local)
1758
1722
  confirm_phone_local_ready
1759
1723
  ;;
1760
- codespaces)
1761
- confirm_phone_codespaces_ready
1762
- ;;
1763
1724
  *)
1764
1725
  abort_wizard "Unknown network mode '$NETWORK_MODE'."
1765
1726
  ;;
@@ -1798,8 +1759,7 @@ ensure_core_tools
1798
1759
  if has_packaged_bridge_binary; then
1799
1760
  ok "Found packaged Rust bridge binary for this host."
1800
1761
  else
1801
- info "No packaged bridge binary found for this host. Falling back to local Rust build."
1802
- ensure_local_rust_build_toolchain
1762
+ abort_wizard "No packaged bridge binary found for this host. Reinstall clawdex-mobile so npm installs the bundled bridge binary, then rerun setup."
1803
1763
  fi
1804
1764
 
1805
1765
  section "Config handling"
@@ -1846,14 +1806,6 @@ if [[ "$CONFIG_ACTION" != "keep" ]]; then
1846
1806
  BRIDGE_HOST="$(resolve_local_ip)"
1847
1807
  ok "Local LAN IPv4 detected: $BRIDGE_HOST"
1848
1808
  ;;
1849
- codespaces)
1850
- section "GitHub Codespaces connectivity"
1851
- if [[ "${CODESPACES:-}" != "true" ]]; then
1852
- warn "GitHub Codespaces env was not detected. This mode expects to run inside an active codespace."
1853
- fi
1854
- BRIDGE_HOST="127.0.0.1"
1855
- ok "Codespaces mode will use forwarded HTTPS bridge URLs."
1856
- ;;
1857
1809
  *)
1858
1810
  abort_wizard "Unknown network mode '$NETWORK_MODE'."
1859
1811
  ;;
@@ -1870,7 +1822,7 @@ else
1870
1822
  NETWORK_MODE="$(infer_network_mode "$BRIDGE_HOST" "$BRIDGE_CONNECT_URL")"
1871
1823
  fi
1872
1824
 
1873
- if ([[ "$BRIDGE_HOST" == "0.0.0.0" ]] || [[ "$BRIDGE_HOST" == "::" ]] || [[ "$BRIDGE_HOST" == "[::]" ]]) && [[ "$NETWORK_MODE" != "codespaces" ]]; then
1825
+ if [[ "$BRIDGE_HOST" == "0.0.0.0" ]] || [[ "$BRIDGE_HOST" == "::" ]] || [[ "$BRIDGE_HOST" == "[::]" ]]; then
1874
1826
  warn "Existing BRIDGE_HOST=$BRIDGE_HOST is a bind address, not a phone-connectable host."
1875
1827
  if [[ "$NETWORK_MODE" == "tailscale" ]]; then
1876
1828
  section "Tailscale connectivity"
@@ -1900,8 +1852,6 @@ section "Phone pairing"
1900
1852
  confirm_phone_network_ready
1901
1853
  if [[ "$NETWORK_MODE" == "tailscale" ]]; then
1902
1854
  ok "Phone Tailscale readiness confirmed."
1903
- elif [[ "$NETWORK_MODE" == "codespaces" ]]; then
1904
- ok "Phone Codespaces pairing readiness confirmed."
1905
1855
  else
1906
1856
  ok "Phone local network readiness confirmed."
1907
1857
  fi
@@ -16,7 +16,6 @@ const {
16
16
  } = require("./bridge-binary");
17
17
 
18
18
  const DEFAULT_HEALTH_TIMEOUT_MS = 15000;
19
- const CODESPACES_HEALTH_TIMEOUT_MS = 60000;
20
19
  const DEV_HEALTH_TIMEOUT_MS = 60000;
21
20
  let qrcodeTerminal = null;
22
21
  let qrcodeTerminalLoaded = false;
@@ -129,33 +128,13 @@ function normalizeBaseUrl(rawUrl) {
129
128
  }
130
129
  }
131
130
 
132
- function isCodespacesMode(env) {
133
- const networkMode = readNonEmptyEnv(env, "BRIDGE_NETWORK_MODE").toLowerCase();
134
- const rawCodespaces = readNonEmptyEnv(env, "CODESPACES").toLowerCase();
135
- return networkMode === "codespaces" || rawCodespaces === "true";
136
- }
137
-
138
131
  function resolveBridgeBuildProfile(env) {
139
132
  const explicitProfile = readNonEmptyEnv(env, "CLAWDEX_BRIDGE_BUILD_PROFILE").toLowerCase();
140
133
  if (explicitProfile === "debug" || explicitProfile === "release") {
141
134
  return explicitProfile;
142
135
  }
143
136
 
144
- return isCodespacesMode(env) ? "debug" : "release";
145
- }
146
-
147
- function isCodespacesAutoPublicEnabled(env) {
148
- const raw = readNonEmptyEnv(env, "BRIDGE_CODESPACES_AUTO_PUBLIC").toLowerCase();
149
- return raw ? raw !== "false" : true;
150
- }
151
-
152
- function buildCodespacesForwardedUrl(env, port) {
153
- const codespaceName = readNonEmptyEnv(env, "CODESPACE_NAME");
154
- const forwardingDomain = readNonEmptyEnv(env, "GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN");
155
- if (!codespaceName || !forwardingDomain) {
156
- return "";
157
- }
158
- return `https://${codespaceName}-${port}.${forwardingDomain}`;
137
+ return "release";
159
138
  }
160
139
 
161
140
  function resolveBridgeAccessUrl(env, endpoint) {
@@ -164,13 +143,6 @@ function resolveBridgeAccessUrl(env, endpoint) {
164
143
  return configured;
165
144
  }
166
145
 
167
- if (isCodespacesMode(env)) {
168
- const forwarded = normalizeBaseUrl(buildCodespacesForwardedUrl(env, endpoint.port));
169
- if (forwarded) {
170
- return forwarded;
171
- }
172
- }
173
-
174
146
  if (isUnspecifiedBindHost(endpoint.host)) {
175
147
  return "";
176
148
  }
@@ -310,174 +282,9 @@ function printBridgeAccessDetails(env, endpoint) {
310
282
  console.log(`Bridge bind: ${buildBridgeUrl(endpoint.host, endpoint.port)}`);
311
283
  }
312
284
 
313
- if (readNonEmptyEnv(env, "BRIDGE_GITHUB_CODESPACES_AUTH").toLowerCase() === "true") {
314
- console.log("GitHub auth: enabled for Codespaces direct sign-in");
315
- }
316
-
317
285
  return bridgeUrl;
318
286
  }
319
287
 
320
- function ghAuthEnv(env) {
321
- const githubToken =
322
- readNonEmptyEnv(process.env, "GH_TOKEN") ||
323
- readNonEmptyEnv(process.env, "GITHUB_TOKEN") ||
324
- readNonEmptyEnv(env, "GH_TOKEN") ||
325
- readNonEmptyEnv(env, "GITHUB_TOKEN");
326
-
327
- if (!githubToken) {
328
- return { ...process.env, ...env };
329
- }
330
-
331
- return {
332
- ...process.env,
333
- ...env,
334
- GH_TOKEN: githubToken,
335
- };
336
- }
337
-
338
- function codespaceSelectionArgs(env) {
339
- const name = readNonEmptyEnv(env, "CODESPACE_NAME");
340
- return name ? ["-c", name] : [];
341
- }
342
-
343
- function formatCodespacesVisibilityCommand(env, ports) {
344
- const uniquePorts = [...new Set(ports.filter((value) => Number.isFinite(value) && value > 0))];
345
- const visibilityArgs = uniquePorts.map((port) => `${port}:public`);
346
- const selectionArgs = codespaceSelectionArgs(env);
347
- return ["gh", "codespace", "ports", "visibility", ...visibilityArgs, ...selectionArgs].join(" ");
348
- }
349
-
350
- function sleepSync(ms) {
351
- if (!Number.isFinite(ms) || ms <= 0) {
352
- return;
353
- }
354
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
355
- }
356
-
357
- function updateCodespacesBrowseUrls(env, ports) {
358
- if (!commandExists("gh")) {
359
- return;
360
- }
361
-
362
- const result = spawnSync(
363
- "gh",
364
- ["codespace", "ports", "--json", "sourcePort,browseUrl", ...codespaceSelectionArgs(env)],
365
- {
366
- encoding: "utf8",
367
- env: ghAuthEnv(env),
368
- stdio: ["ignore", "pipe", "pipe"],
369
- }
370
- );
371
- if ((result.status ?? 1) !== 0 || !result.stdout) {
372
- return;
373
- }
374
-
375
- let rows;
376
- try {
377
- rows = JSON.parse(result.stdout);
378
- } catch {
379
- return;
380
- }
381
-
382
- if (!Array.isArray(rows)) {
383
- return;
384
- }
385
-
386
- for (const row of rows) {
387
- if (!row || typeof row !== "object") {
388
- continue;
389
- }
390
- const sourcePort = parsePort(row.sourcePort, 0);
391
- const browseUrl = normalizeBaseUrl(row.browseUrl);
392
- if (!sourcePort || !browseUrl || !ports.includes(sourcePort)) {
393
- continue;
394
- }
395
-
396
- if (sourcePort === parsePort(env.BRIDGE_PORT, 8787)) {
397
- env.BRIDGE_CONNECT_URL = browseUrl;
398
- }
399
- if (sourcePort === parsePort(env.BRIDGE_PREVIEW_PORT, sourcePort + 1)) {
400
- env.BRIDGE_PREVIEW_CONNECT_URL = browseUrl;
401
- }
402
- }
403
- }
404
-
405
- function ensureCodespacesPortsArePublic(env, ports) {
406
- if (!isCodespacesMode(env) || !isCodespacesAutoPublicEnabled(env)) {
407
- return [];
408
- }
409
-
410
- const uniquePorts = [...new Set(ports.filter((value) => Number.isFinite(value) && value > 0))];
411
- const notes = [];
412
-
413
- if (uniquePorts.length === 0) {
414
- return notes;
415
- }
416
-
417
- if (!commandExists("gh")) {
418
- notes.push(
419
- `Codespaces mode detected, but GitHub CLI is unavailable. Run '${formatCodespacesVisibilityCommand(
420
- env,
421
- uniquePorts
422
- )}' or mark those forwarded ports public in the Ports panel.`
423
- );
424
- return notes;
425
- }
426
-
427
- const publishedPorts = [];
428
- const failedPorts = [];
429
- const selectionArgs = codespaceSelectionArgs(env);
430
-
431
- for (const port of uniquePorts) {
432
- let lastDetail = "";
433
- for (let attempt = 1; attempt <= 8; attempt += 1) {
434
- const result = spawnSync(
435
- "gh",
436
- ["codespace", "ports", "visibility", `${port}:public`, ...selectionArgs],
437
- {
438
- encoding: "utf8",
439
- env: ghAuthEnv(env),
440
- stdio: ["ignore", "pipe", "pipe"],
441
- }
442
- );
443
-
444
- if ((result.status ?? 1) === 0) {
445
- publishedPorts.push(port);
446
- lastDetail = "";
447
- break;
448
- }
449
-
450
- lastDetail = (result.stderr || result.stdout || "").trim();
451
- if (attempt < 8) {
452
- sleepSync(1_500);
453
- }
454
- }
455
-
456
- if (lastDetail) {
457
- failedPorts.push({ port, detail: lastDetail });
458
- }
459
- }
460
-
461
- if (publishedPorts.length > 0) {
462
- updateCodespacesBrowseUrls(env, publishedPorts);
463
- notes.push(
464
- `Codespaces forwarded ports are set to public for bridge access: ${publishedPorts.join(", ")}.`
465
- );
466
- }
467
-
468
- for (const failure of failedPorts) {
469
- const suffix = failure.detail ? ` (${failure.detail.split(/\r?\n/, 1)[0]})` : "";
470
- notes.push(
471
- `Could not set Codespaces forwarded port ${failure.port} public automatically${suffix}. Run '${formatCodespacesVisibilityCommand(
472
- env,
473
- [failure.port]
474
- )}' or update the Ports panel manually.`
475
- );
476
- }
477
-
478
- return notes;
479
- }
480
-
481
288
  function bridgePidFile(rootDir) {
482
289
  return path.join(rootDir, ".bridge.pid");
483
290
  }
@@ -570,48 +377,6 @@ function commandExists(command) {
570
377
  return result.status === 0;
571
378
  }
572
379
 
573
- function walkFiles(directory) {
574
- const entries = fs.readdirSync(directory, { withFileTypes: true });
575
- const files = [];
576
-
577
- for (const entry of entries) {
578
- const entryPath = path.join(directory, entry.name);
579
- if (entry.isDirectory()) {
580
- files.push(...walkFiles(entryPath));
581
- continue;
582
- }
583
- if (entry.isFile()) {
584
- files.push(entryPath);
585
- }
586
- }
587
-
588
- return files;
589
- }
590
-
591
- function isBuiltBinaryFresh(packageDir, binaryPath) {
592
- if (!fs.existsSync(binaryPath)) {
593
- return false;
594
- }
595
-
596
- const binaryMtime = fs.statSync(binaryPath).mtimeMs;
597
- const watchPaths = [
598
- path.join(packageDir, "services", "rust-bridge", "Cargo.toml"),
599
- path.join(packageDir, "services", "rust-bridge", "Cargo.lock"),
600
- ];
601
- const sourceDir = path.join(packageDir, "services", "rust-bridge", "src");
602
-
603
- if (fs.existsSync(sourceDir)) {
604
- watchPaths.push(...walkFiles(sourceDir));
605
- }
606
-
607
- return watchPaths.every((watchPath) => {
608
- if (!fs.existsSync(watchPath)) {
609
- return true;
610
- }
611
- return fs.statSync(watchPath).mtimeMs <= binaryMtime;
612
- });
613
- }
614
-
615
380
  function printMissingCompilerHint() {
616
381
  if (process.platform === "win32") {
617
382
  console.error("Install Visual Studio Build Tools (Desktop development with C++) and Rust, then retry.");
@@ -653,16 +418,7 @@ function spawnAndRelay(command, args, options) {
653
418
  });
654
419
 
655
420
  if (child.pid) {
656
- const bridgePort = parsePort(env.BRIDGE_PORT, 8787);
657
- const previewPort = parsePort(env.BRIDGE_PREVIEW_PORT, bridgePort + 1);
658
- void waitForHealth(env, child.pid, healthTimeoutMs)
659
- .then(() => {
660
- const notes = ensureCodespacesPortsArePublic(env, [bridgePort, previewPort]);
661
- for (const note of notes) {
662
- console.log(note);
663
- }
664
- })
665
- .catch(() => {});
421
+ void waitForHealth(env, child.pid, healthTimeoutMs).catch(() => {});
666
422
  }
667
423
 
668
424
  child.on("exit", (code, signal) => {
@@ -679,7 +435,6 @@ async function spawnDetachedAndWait(command, args, options) {
679
435
  const logPath = bridgeLogFile(rootDir);
680
436
  const host = env.BRIDGE_HOST || "127.0.0.1";
681
437
  const port = env.BRIDGE_PORT || "8787";
682
- const previewPort = parsePort(env.BRIDGE_PREVIEW_PORT, parsePort(port, 8787) + 1);
683
438
  const healthUrl = new URL(`http://${formatHostForUrl(host)}:${port}/health`);
684
439
  const existingPid = readPidFile(rootDir);
685
440
 
@@ -689,11 +444,7 @@ async function spawnDetachedAndWait(command, args, options) {
689
444
  console.log(`Logs: ${logPath}`);
690
445
  console.log("Bridge is healthy.");
691
446
  const endpoint = { host, port };
692
- const notes = ensureCodespacesPortsArePublic(env, [parsePort(port, 8787), previewPort]);
693
447
  printBridgeAccessDetails(env, endpoint);
694
- for (const note of notes) {
695
- console.log(note);
696
- }
697
448
  if (shouldShowPairingQr(env) && !printPairingQr(env, endpoint)) {
698
449
  printPairingQrUnavailableMessage(env);
699
450
  }
@@ -740,11 +491,7 @@ async function spawnDetachedAndWait(command, args, options) {
740
491
  try {
741
492
  const endpoint = await waitForHealth(env, child.pid, healthTimeoutMs);
742
493
  console.log("Bridge is healthy.");
743
- const notes = ensureCodespacesPortsArePublic(env, [parsePort(endpoint.port, 8787), previewPort]);
744
494
  printBridgeAccessDetails(env, endpoint);
745
- for (const note of notes) {
746
- console.log(note);
747
- }
748
495
 
749
496
  if (shouldShowPairingQr(env) && !printPairingQr(env, endpoint)) {
750
497
  printPairingQrUnavailableMessage(env);
@@ -779,9 +526,7 @@ function buildBridgeFromSource(packageDir, env, profile) {
779
526
  }
780
527
 
781
528
  function resolveLaunch(workspaceDir, packageDir, env, { devMode, forceSourceBuild }) {
782
- const defaultHealthTimeoutMs = isCodespacesMode(env)
783
- ? CODESPACES_HEALTH_TIMEOUT_MS
784
- : DEFAULT_HEALTH_TIMEOUT_MS;
529
+ const defaultHealthTimeoutMs = DEFAULT_HEALTH_TIMEOUT_MS;
785
530
 
786
531
  if (devMode) {
787
532
  if (!commandExists("cargo")) {
@@ -828,20 +573,15 @@ function resolveLaunch(workspaceDir, packageDir, env, { devMode, forceSourceBuil
828
573
  }
829
574
 
830
575
  const builtBinary = builtBinaryPath(packageDir, os.platform(), buildProfile);
831
- if (isBuiltBinaryFresh(packageDir, builtBinary)) {
832
- ensureExecutable(builtBinary);
833
- return {
834
- command: builtBinary,
835
- args: [],
836
- cwd: workspaceDir,
837
- env,
838
- healthTimeoutMs: defaultHealthTimeoutMs,
839
- };
576
+
577
+ if (!forceSourceBuild) {
578
+ console.error("error: no packaged bridge binary was found for this host.");
579
+ console.error("Reinstall a published clawdex-mobile package with bundled bridge binaries.");
580
+ process.exit(1);
840
581
  }
841
582
 
842
583
  if (!commandExists("cargo")) {
843
- console.error("error: no packaged bridge binary was found for this host, and cargo is not installed.");
844
- console.error("Reinstall a published clawdex-mobile package with bundled bridge binaries, or install Rust and retry.");
584
+ console.error("error: CLAWDEX_BRIDGE_FORCE_SOURCE_BUILD=true was set, but cargo is not installed.");
845
585
  process.exit(1);
846
586
  }
847
587
 
@@ -0,0 +1,39 @@
1
+ # @clawdex/cursor-app-server
2
+
3
+ App-server shaped JSON-RPC adapter for Cursor agents.
4
+
5
+ This package is intentionally strict. It does not fall back to Codex, OpenCode,
6
+ mock agents, empty model lists, or implicit workspace directories. Missing
7
+ Cursor runtime configuration is surfaced as an error.
8
+
9
+ ## Contract
10
+
11
+ Supported JSON-RPC methods:
12
+
13
+ - `thread/list`
14
+ - `thread/loaded/list`
15
+ - `thread/read`
16
+ - `thread/start`
17
+ - `turn/start`
18
+ - `turn/interrupt`
19
+ - `model/list`
20
+
21
+ Emitted notifications:
22
+
23
+ - `thread/started`
24
+ - `thread/status/changed`
25
+ - `turn/started`
26
+ - `item/agentMessage/delta`
27
+ - `item/reasoning/textDelta`
28
+ - `item/started`
29
+ - `turn/completed`
30
+
31
+ ## Run
32
+
33
+ ```bash
34
+ CURSOR_WORKDIR="$PWD" CURSOR_MODEL="cursor-small" CURSOR_API_KEY="..." cursor-app-server
35
+ ```
36
+
37
+ `CURSOR_WORKDIR` is required for local agents. `CURSOR_MODEL` is required unless
38
+ a request provides an explicit model. `CURSOR_API_KEY` is required for Cursor SDK
39
+ operations and model listing.
@@ -0,0 +1,52 @@
1
+ import type { AppServerNotification, CursorAppServerOptions, CursorStreamMessage } from './types.js';
2
+ type NotificationListener = (notification: AppServerNotification) => void;
3
+ export declare class CursorAppServer {
4
+ private readonly runtime;
5
+ private readonly driver;
6
+ private readonly configuredCwd;
7
+ private readonly apiKey;
8
+ private readonly defaultModel;
9
+ private readonly cursorProjectsDir;
10
+ private readonly events;
11
+ private readonly liveThreads;
12
+ private readonly knownThreadCwds;
13
+ private readonly knownThreadStoreCwds;
14
+ constructor(options: CursorAppServerOptions);
15
+ onNotification(listener: NotificationListener): () => void;
16
+ request(method: string, params?: unknown): Promise<Record<string, unknown>>;
17
+ private listThreads;
18
+ private readThread;
19
+ private listLoadedThreads;
20
+ private startThread;
21
+ private startTurn;
22
+ private interruptTurn;
23
+ private listModels;
24
+ private initialize;
25
+ private getOrResumeLiveThread;
26
+ private consumeRun;
27
+ private applyStreamMessage;
28
+ private applyRunResult;
29
+ private findOrCreateTextItem;
30
+ private upsertToolCallItem;
31
+ private findAssistantItem;
32
+ private requireCwd;
33
+ private resolveKnownThreadWorkspace;
34
+ private rememberAgentCwd;
35
+ private listKnownWorkspaceCwds;
36
+ private resolveAgentEffectiveCwd;
37
+ private readPersistedThread;
38
+ private resumePersistedThread;
39
+ private candidateStoreCwds;
40
+ private requireModel;
41
+ private requireTurnModel;
42
+ private configuredModelOrUndefined;
43
+ private latestPersistedRunModel;
44
+ private requireApiKey;
45
+ private buildUserMessage;
46
+ private buildUserThreadContent;
47
+ private toModelSelection;
48
+ private emit;
49
+ }
50
+ export declare function createCursorAppServerFromEnv(env?: NodeJS.ProcessEnv): CursorAppServer;
51
+ export declare function cursorStreamMessageText(message: CursorStreamMessage): string;
52
+ export {};