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.
- package/CHANGELOG.md +15 -0
- package/README.md +6 -55
- package/bin/clawdex.js +2 -2
- package/docs/setup-and-operations.md +13 -99
- package/docs/troubleshooting.md +2 -70
- 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 +13 -242
- 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 +1007 -390
- 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
|
|
@@ -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
|
|
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
|
-
|
|
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:
|
|
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:
|
|
571
|
+
healthTimeoutMs: defaultHealthTimeoutMs,
|
|
796
572
|
};
|
|
797
573
|
}
|
|
798
574
|
|
|
799
575
|
const builtBinary = builtBinaryPath(packageDir, os.platform(), buildProfile);
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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:
|
|
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:
|
|
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 {};
|