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.
- package/CHANGELOG.md +15 -0
- package/README.md +6 -55
- package/bin/clawdex.js +2 -2
- package/docs/setup-and-operations.md +13 -100
- package/docs/troubleshooting.md +2 -71
- package/package.json +17 -6
- package/scripts/setup-secure-dev.sh +5 -79
- package/scripts/setup-wizard.sh +37 -87
- package/scripts/start-bridge-secure.js +9 -269
- package/services/cursor-app-server/README.md +39 -0
- package/services/cursor-app-server/dist/appServer.d.ts +52 -0
- package/services/cursor-app-server/dist/appServer.js +780 -0
- package/services/cursor-app-server/dist/appServer.js.map +1 -0
- package/services/cursor-app-server/dist/cursorWorkspace.d.ts +7 -0
- package/services/cursor-app-server/dist/cursorWorkspace.js +126 -0
- package/services/cursor-app-server/dist/cursorWorkspace.js.map +1 -0
- package/services/cursor-app-server/dist/index.d.ts +4 -0
- package/services/cursor-app-server/dist/index.js +4 -0
- package/services/cursor-app-server/dist/index.js.map +1 -0
- package/services/cursor-app-server/dist/input.d.ts +27 -0
- package/services/cursor-app-server/dist/input.js +143 -0
- package/services/cursor-app-server/dist/input.js.map +1 -0
- package/services/cursor-app-server/dist/jsonRpc.d.ts +15 -0
- package/services/cursor-app-server/dist/jsonRpc.js +93 -0
- package/services/cursor-app-server/dist/jsonRpc.js.map +1 -0
- package/services/cursor-app-server/dist/projection.d.ts +8 -0
- package/services/cursor-app-server/dist/projection.js +507 -0
- package/services/cursor-app-server/dist/projection.js.map +1 -0
- package/services/cursor-app-server/dist/sdkDriver.d.ts +50 -0
- package/services/cursor-app-server/dist/sdkDriver.js +166 -0
- package/services/cursor-app-server/dist/sdkDriver.js.map +1 -0
- package/services/cursor-app-server/dist/stdio.d.ts +2 -0
- package/services/cursor-app-server/dist/stdio.js +7 -0
- package/services/cursor-app-server/dist/stdio.js.map +1 -0
- package/services/cursor-app-server/dist/types.d.ts +218 -0
- package/services/cursor-app-server/dist/types.js +2 -0
- package/services/cursor-app-server/dist/types.js.map +1 -0
- package/services/cursor-app-server/package.json +43 -0
- package/services/rust-bridge/Cargo.lock +1 -1
- package/services/rust-bridge/Cargo.toml +1 -1
- package/services/rust-bridge/src/main.rs +1005 -388
- package/vendor/bridge-binaries/darwin-arm64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/darwin-x64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/linux-arm64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/linux-armv7l/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/linux-x64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/win32-x64/codex-rust-bridge.exe +0 -0
- package/scripts/codespaces-bootstrap.js +0 -297
package/scripts/setup-wizard.sh
CHANGED
|
@@ -1129,11 +1129,7 @@ choose_config_action() {
|
|
|
1129
1129
|
}
|
|
1130
1130
|
|
|
1131
1131
|
choose_bridge_network_mode() {
|
|
1132
|
-
|
|
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 [[ -
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
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
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
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
|
-
|
|
1331
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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:
|
|
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 {};
|