clawdex-mobile 5.1.3-internal.8 → 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 -99
  5. package/docs/troubleshooting.md +2 -70
  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 +13 -242
  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 +1007 -390
  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
@@ -128,33 +128,13 @@ function normalizeBaseUrl(rawUrl) {
128
128
  }
129
129
  }
130
130
 
131
- function isCodespacesMode(env) {
132
- const networkMode = readNonEmptyEnv(env, "BRIDGE_NETWORK_MODE").toLowerCase();
133
- const rawCodespaces = readNonEmptyEnv(env, "CODESPACES").toLowerCase();
134
- return networkMode === "codespaces" || rawCodespaces === "true";
135
- }
136
-
137
131
  function resolveBridgeBuildProfile(env) {
138
132
  const explicitProfile = readNonEmptyEnv(env, "CLAWDEX_BRIDGE_BUILD_PROFILE").toLowerCase();
139
133
  if (explicitProfile === "debug" || explicitProfile === "release") {
140
134
  return explicitProfile;
141
135
  }
142
136
 
143
- return isCodespacesMode(env) ? "debug" : "release";
144
- }
145
-
146
- function isCodespacesAutoPublicEnabled(env) {
147
- const raw = readNonEmptyEnv(env, "BRIDGE_CODESPACES_AUTO_PUBLIC").toLowerCase();
148
- return raw ? raw !== "false" : true;
149
- }
150
-
151
- function buildCodespacesForwardedUrl(env, port) {
152
- const codespaceName = readNonEmptyEnv(env, "CODESPACE_NAME");
153
- const forwardingDomain = readNonEmptyEnv(env, "GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN");
154
- if (!codespaceName || !forwardingDomain) {
155
- return "";
156
- }
157
- return `https://${codespaceName}-${port}.${forwardingDomain}`;
137
+ return "release";
158
138
  }
159
139
 
160
140
  function resolveBridgeAccessUrl(env, endpoint) {
@@ -163,13 +143,6 @@ function resolveBridgeAccessUrl(env, endpoint) {
163
143
  return configured;
164
144
  }
165
145
 
166
- if (isCodespacesMode(env)) {
167
- const forwarded = normalizeBaseUrl(buildCodespacesForwardedUrl(env, endpoint.port));
168
- if (forwarded) {
169
- return forwarded;
170
- }
171
- }
172
-
173
146
  if (isUnspecifiedBindHost(endpoint.host)) {
174
147
  return "";
175
148
  }
@@ -309,148 +282,9 @@ function printBridgeAccessDetails(env, endpoint) {
309
282
  console.log(`Bridge bind: ${buildBridgeUrl(endpoint.host, endpoint.port)}`);
310
283
  }
311
284
 
312
- if (readNonEmptyEnv(env, "BRIDGE_GITHUB_CODESPACES_AUTH").toLowerCase() === "true") {
313
- console.log("GitHub auth: enabled for Codespaces direct sign-in");
314
- }
315
-
316
285
  return bridgeUrl;
317
286
  }
318
287
 
319
- function ghAuthEnv(env) {
320
- const githubToken =
321
- readNonEmptyEnv(process.env, "GH_TOKEN") ||
322
- readNonEmptyEnv(process.env, "GITHUB_TOKEN") ||
323
- readNonEmptyEnv(env, "GH_TOKEN") ||
324
- readNonEmptyEnv(env, "GITHUB_TOKEN");
325
-
326
- if (!githubToken) {
327
- return { ...process.env, ...env };
328
- }
329
-
330
- return {
331
- ...process.env,
332
- ...env,
333
- GH_TOKEN: githubToken,
334
- };
335
- }
336
-
337
- function codespaceSelectionArgs(env) {
338
- const name = readNonEmptyEnv(env, "CODESPACE_NAME");
339
- return name ? ["-c", name] : [];
340
- }
341
-
342
- function formatCodespacesVisibilityCommand(env, ports) {
343
- const uniquePorts = [...new Set(ports.filter((value) => Number.isFinite(value) && value > 0))];
344
- const visibilityArgs = uniquePorts.map((port) => `${port}:public`);
345
- const selectionArgs = codespaceSelectionArgs(env);
346
- return ["gh", "codespace", "ports", "visibility", ...visibilityArgs, ...selectionArgs].join(" ");
347
- }
348
-
349
- function updateCodespacesBrowseUrls(env, ports) {
350
- if (!commandExists("gh")) {
351
- return;
352
- }
353
-
354
- const result = spawnSync(
355
- "gh",
356
- ["codespace", "ports", "--json", "sourcePort,browseUrl", ...codespaceSelectionArgs(env)],
357
- {
358
- encoding: "utf8",
359
- env: ghAuthEnv(env),
360
- stdio: ["ignore", "pipe", "pipe"],
361
- }
362
- );
363
- if ((result.status ?? 1) !== 0 || !result.stdout) {
364
- return;
365
- }
366
-
367
- let rows;
368
- try {
369
- rows = JSON.parse(result.stdout);
370
- } catch {
371
- return;
372
- }
373
-
374
- if (!Array.isArray(rows)) {
375
- return;
376
- }
377
-
378
- for (const row of rows) {
379
- if (!row || typeof row !== "object") {
380
- continue;
381
- }
382
- const sourcePort = parsePort(row.sourcePort, 0);
383
- const browseUrl = normalizeBaseUrl(row.browseUrl);
384
- if (!sourcePort || !browseUrl || !ports.includes(sourcePort)) {
385
- continue;
386
- }
387
-
388
- if (sourcePort === parsePort(env.BRIDGE_PORT, 8787)) {
389
- env.BRIDGE_CONNECT_URL = browseUrl;
390
- }
391
- if (sourcePort === parsePort(env.BRIDGE_PREVIEW_PORT, sourcePort + 1)) {
392
- env.BRIDGE_PREVIEW_CONNECT_URL = browseUrl;
393
- }
394
- }
395
- }
396
-
397
- function ensureCodespacesPortsArePublic(env, ports) {
398
- if (!isCodespacesMode(env) || !isCodespacesAutoPublicEnabled(env)) {
399
- return [];
400
- }
401
-
402
- const uniquePorts = [...new Set(ports.filter((value) => Number.isFinite(value) && value > 0))];
403
- const notes = [];
404
-
405
- if (uniquePorts.length === 0) {
406
- return notes;
407
- }
408
-
409
- if (!commandExists("gh")) {
410
- notes.push(
411
- `Codespaces mode detected, but GitHub CLI is unavailable. Run '${formatCodespacesVisibilityCommand(
412
- env,
413
- uniquePorts
414
- )}' or mark those forwarded ports public in the Ports panel.`
415
- );
416
- return notes;
417
- }
418
-
419
- const result = spawnSync(
420
- "gh",
421
- [
422
- "codespace",
423
- "ports",
424
- "visibility",
425
- ...uniquePorts.map((port) => `${port}:public`),
426
- ...codespaceSelectionArgs(env),
427
- ],
428
- {
429
- encoding: "utf8",
430
- env: ghAuthEnv(env),
431
- stdio: ["ignore", "pipe", "pipe"],
432
- }
433
- );
434
-
435
- if ((result.status ?? 1) !== 0) {
436
- const detail = (result.stderr || result.stdout || "").trim();
437
- const suffix = detail ? ` (${detail.split(/\r?\n/, 1)[0]})` : "";
438
- notes.push(
439
- `Could not set Codespaces forwarded ports public automatically${suffix}. Run '${formatCodespacesVisibilityCommand(
440
- env,
441
- uniquePorts
442
- )}' or update the Ports panel manually.`
443
- );
444
- return notes;
445
- }
446
-
447
- updateCodespacesBrowseUrls(env, uniquePorts);
448
- notes.push(
449
- `Codespaces forwarded ports are set to public for bridge access: ${uniquePorts.join(", ")}.`
450
- );
451
- return notes;
452
- }
453
-
454
288
  function bridgePidFile(rootDir) {
455
289
  return path.join(rootDir, ".bridge.pid");
456
290
  }
@@ -543,48 +377,6 @@ function commandExists(command) {
543
377
  return result.status === 0;
544
378
  }
545
379
 
546
- function walkFiles(directory) {
547
- const entries = fs.readdirSync(directory, { withFileTypes: true });
548
- const files = [];
549
-
550
- for (const entry of entries) {
551
- const entryPath = path.join(directory, entry.name);
552
- if (entry.isDirectory()) {
553
- files.push(...walkFiles(entryPath));
554
- continue;
555
- }
556
- if (entry.isFile()) {
557
- files.push(entryPath);
558
- }
559
- }
560
-
561
- return files;
562
- }
563
-
564
- function isBuiltBinaryFresh(packageDir, binaryPath) {
565
- if (!fs.existsSync(binaryPath)) {
566
- return false;
567
- }
568
-
569
- const binaryMtime = fs.statSync(binaryPath).mtimeMs;
570
- const watchPaths = [
571
- path.join(packageDir, "services", "rust-bridge", "Cargo.toml"),
572
- path.join(packageDir, "services", "rust-bridge", "Cargo.lock"),
573
- ];
574
- const sourceDir = path.join(packageDir, "services", "rust-bridge", "src");
575
-
576
- if (fs.existsSync(sourceDir)) {
577
- watchPaths.push(...walkFiles(sourceDir));
578
- }
579
-
580
- return watchPaths.every((watchPath) => {
581
- if (!fs.existsSync(watchPath)) {
582
- return true;
583
- }
584
- return fs.statSync(watchPath).mtimeMs <= binaryMtime;
585
- });
586
- }
587
-
588
380
  function printMissingCompilerHint() {
589
381
  if (process.platform === "win32") {
590
382
  console.error("Install Visual Studio Build Tools (Desktop development with C++) and Rust, then retry.");
@@ -626,16 +418,7 @@ function spawnAndRelay(command, args, options) {
626
418
  });
627
419
 
628
420
  if (child.pid) {
629
- const bridgePort = parsePort(env.BRIDGE_PORT, 8787);
630
- const previewPort = parsePort(env.BRIDGE_PREVIEW_PORT, bridgePort + 1);
631
- void waitForHealth(env, child.pid, healthTimeoutMs)
632
- .then(() => {
633
- const notes = ensureCodespacesPortsArePublic(env, [bridgePort, previewPort]);
634
- for (const note of notes) {
635
- console.log(note);
636
- }
637
- })
638
- .catch(() => {});
421
+ void waitForHealth(env, child.pid, healthTimeoutMs).catch(() => {});
639
422
  }
640
423
 
641
424
  child.on("exit", (code, signal) => {
@@ -652,7 +435,6 @@ async function spawnDetachedAndWait(command, args, options) {
652
435
  const logPath = bridgeLogFile(rootDir);
653
436
  const host = env.BRIDGE_HOST || "127.0.0.1";
654
437
  const port = env.BRIDGE_PORT || "8787";
655
- const previewPort = parsePort(env.BRIDGE_PREVIEW_PORT, parsePort(port, 8787) + 1);
656
438
  const healthUrl = new URL(`http://${formatHostForUrl(host)}:${port}/health`);
657
439
  const existingPid = readPidFile(rootDir);
658
440
 
@@ -662,11 +444,7 @@ async function spawnDetachedAndWait(command, args, options) {
662
444
  console.log(`Logs: ${logPath}`);
663
445
  console.log("Bridge is healthy.");
664
446
  const endpoint = { host, port };
665
- const notes = ensureCodespacesPortsArePublic(env, [parsePort(port, 8787), previewPort]);
666
447
  printBridgeAccessDetails(env, endpoint);
667
- for (const note of notes) {
668
- console.log(note);
669
- }
670
448
  if (shouldShowPairingQr(env) && !printPairingQr(env, endpoint)) {
671
449
  printPairingQrUnavailableMessage(env);
672
450
  }
@@ -713,11 +491,7 @@ async function spawnDetachedAndWait(command, args, options) {
713
491
  try {
714
492
  const endpoint = await waitForHealth(env, child.pid, healthTimeoutMs);
715
493
  console.log("Bridge is healthy.");
716
- const notes = ensureCodespacesPortsArePublic(env, [parsePort(endpoint.port, 8787), previewPort]);
717
494
  printBridgeAccessDetails(env, endpoint);
718
- for (const note of notes) {
719
- console.log(note);
720
- }
721
495
 
722
496
  if (shouldShowPairingQr(env) && !printPairingQr(env, endpoint)) {
723
497
  printPairingQrUnavailableMessage(env);
@@ -752,6 +526,8 @@ function buildBridgeFromSource(packageDir, env, profile) {
752
526
  }
753
527
 
754
528
  function resolveLaunch(workspaceDir, packageDir, env, { devMode, forceSourceBuild }) {
529
+ const defaultHealthTimeoutMs = DEFAULT_HEALTH_TIMEOUT_MS;
530
+
755
531
  if (devMode) {
756
532
  if (!commandExists("cargo")) {
757
533
  console.error("error: missing Rust/Cargo toolchain for dev bridge mode.");
@@ -779,7 +555,7 @@ function resolveLaunch(workspaceDir, packageDir, env, { devMode, forceSourceBuil
779
555
  args: [],
780
556
  cwd: workspaceDir,
781
557
  env,
782
- healthTimeoutMs: DEFAULT_HEALTH_TIMEOUT_MS,
558
+ healthTimeoutMs: defaultHealthTimeoutMs,
783
559
  };
784
560
  }
785
561
 
@@ -792,25 +568,20 @@ function resolveLaunch(workspaceDir, packageDir, env, { devMode, forceSourceBuil
792
568
  args: [],
793
569
  cwd: workspaceDir,
794
570
  env,
795
- healthTimeoutMs: DEFAULT_HEALTH_TIMEOUT_MS,
571
+ healthTimeoutMs: defaultHealthTimeoutMs,
796
572
  };
797
573
  }
798
574
 
799
575
  const builtBinary = builtBinaryPath(packageDir, os.platform(), buildProfile);
800
- if (isBuiltBinaryFresh(packageDir, builtBinary)) {
801
- ensureExecutable(builtBinary);
802
- return {
803
- command: builtBinary,
804
- args: [],
805
- cwd: workspaceDir,
806
- env,
807
- healthTimeoutMs: DEFAULT_HEALTH_TIMEOUT_MS,
808
- };
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);
809
581
  }
810
582
 
811
583
  if (!commandExists("cargo")) {
812
- console.error("error: no packaged bridge binary was found for this host, and cargo is not installed.");
813
- 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.");
814
585
  process.exit(1);
815
586
  }
816
587
 
@@ -833,7 +604,7 @@ function resolveLaunch(workspaceDir, packageDir, env, { devMode, forceSourceBuil
833
604
  args: [],
834
605
  cwd: workspaceDir,
835
606
  env,
836
- healthTimeoutMs: DEFAULT_HEALTH_TIMEOUT_MS,
607
+ healthTimeoutMs: defaultHealthTimeoutMs,
837
608
  };
838
609
  }
839
610
 
@@ -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 {};