clawdex-mobile 2.0.1 → 4.0.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/.github/workflows/ci.yml +4 -3
- package/.github/workflows/npm-release.yml +62 -2
- package/.github/workflows/pages.yml +41 -0
- package/AGENTS.md +263 -110
- package/README.md +15 -4
- package/apps/mobile/.env.example +2 -2
- package/apps/mobile/App.tsx +175 -14
- package/apps/mobile/app.json +27 -9
- package/apps/mobile/eas.json +14 -4
- package/apps/mobile/package.json +14 -13
- package/apps/mobile/src/api/__tests__/chatMapping.test.ts +219 -0
- package/apps/mobile/src/api/__tests__/client.test.ts +587 -6
- package/apps/mobile/src/api/__tests__/ws.test.ts +27 -0
- package/apps/mobile/src/api/account.ts +47 -0
- package/apps/mobile/src/api/chatMapping.ts +435 -18
- package/apps/mobile/src/api/client.ts +321 -36
- package/apps/mobile/src/api/rateLimits.ts +143 -0
- package/apps/mobile/src/api/types.ts +107 -0
- package/apps/mobile/src/api/ws.ts +10 -1
- package/apps/mobile/src/components/ChatHeader.tsx +12 -12
- package/apps/mobile/src/components/ChatInput.tsx +154 -88
- package/apps/mobile/src/components/ChatMessage.tsx +548 -93
- package/apps/mobile/src/components/ComposerUsageLimits.tsx +167 -0
- package/apps/mobile/src/components/SelectionSheet.tsx +466 -0
- package/apps/mobile/src/components/ToolBlock.tsx +17 -15
- package/apps/mobile/src/components/VoiceRecordingWaveform.tsx +181 -0
- package/apps/mobile/src/components/WorkspacePickerModal.tsx +812 -0
- package/apps/mobile/src/components/__tests__/chat-input-layout.test.ts +35 -0
- package/apps/mobile/src/components/__tests__/chatImageSource.test.ts +44 -0
- package/apps/mobile/src/components/__tests__/composerUsageLimits.test.ts +138 -0
- package/apps/mobile/src/components/__tests__/voiceWaveform.test.ts +31 -0
- package/apps/mobile/src/components/chat-input-layout.ts +59 -0
- package/apps/mobile/src/components/chatImageSource.ts +86 -0
- package/apps/mobile/src/components/usageLimitBadges.ts +109 -0
- package/apps/mobile/src/components/voiceWaveform.ts +46 -0
- package/apps/mobile/src/config.ts +9 -2
- package/apps/mobile/src/hooks/useVoiceRecorder.ts +8 -1
- package/apps/mobile/src/navigation/DrawerContent.tsx +607 -457
- package/apps/mobile/src/navigation/__tests__/chatThreadTree.test.ts +89 -0
- package/apps/mobile/src/navigation/__tests__/drawerChats.test.ts +65 -0
- package/apps/mobile/src/navigation/chatThreadTree.ts +191 -0
- package/apps/mobile/src/navigation/drawerChats.ts +9 -0
- package/apps/mobile/src/screens/GitScreen.tsx +2 -0
- package/apps/mobile/src/screens/MainScreen.tsx +4239 -1237
- package/apps/mobile/src/screens/OnboardingScreen.tsx +924 -310
- package/apps/mobile/src/screens/SettingsScreen.tsx +256 -226
- package/apps/mobile/src/screens/TerminalScreen.tsx +2 -5
- package/apps/mobile/src/screens/__tests__/agentThreadDisplay.test.ts +80 -0
- package/apps/mobile/src/screens/__tests__/agentThreads.test.ts +170 -0
- package/apps/mobile/src/screens/__tests__/planCardState.test.ts +88 -0
- package/apps/mobile/src/screens/__tests__/subAgentTranscript.test.ts +102 -0
- package/apps/mobile/src/screens/__tests__/transcriptMessages.test.ts +97 -0
- package/apps/mobile/src/screens/agentThreadDisplay.ts +261 -0
- package/apps/mobile/src/screens/agentThreads.ts +167 -0
- package/apps/mobile/src/screens/planCardState.ts +40 -0
- package/apps/mobile/src/screens/subAgentTranscript.ts +149 -0
- package/apps/mobile/src/screens/transcriptMessages.ts +102 -0
- package/apps/mobile/src/theme.ts +6 -12
- package/bin/clawdex.js +7 -6
- package/codex-rust-bridge +0 -0
- package/codex-rust-bridge.exe +0 -0
- package/docs/codex-app-server-cli-gap-tracker.md +14 -5
- package/docs/privacy-policy.md +54 -0
- package/docs/setup-and-operations.md +21 -15
- package/docs/terms-of-service.md +33 -0
- package/docs/troubleshooting.md +15 -19
- package/package.json +6 -5
- package/scripts/bridge-binary.js +194 -0
- package/scripts/setup-wizard.sh +17 -186
- package/scripts/start-bridge-secure.js +240 -0
- package/scripts/start-bridge-secure.sh +1 -40
- package/services/mac-bridge/package.json +6 -6
- package/services/rust-bridge/Cargo.lock +56 -47
- package/services/rust-bridge/Cargo.toml +1 -1
- package/services/rust-bridge/package.json +1 -1
- package/services/rust-bridge/src/main.rs +517 -9
- package/site/index.html +54 -0
- package/site/privacy/index.html +80 -0
- package/site/styles.css +135 -0
- package/site/support/index.html +51 -0
- package/site/terms/index.html +68 -0
package/scripts/setup-wizard.sh
CHANGED
|
@@ -28,16 +28,7 @@ NETWORK_MODE=""
|
|
|
28
28
|
TAILSCALE_IP=""
|
|
29
29
|
BRIDGE_HOST=""
|
|
30
30
|
BRIDGE_PORT=""
|
|
31
|
-
EXPO_MODE="mobile"
|
|
32
31
|
AUTO_START="true"
|
|
33
|
-
TARGET_PLATFORM="mobile"
|
|
34
|
-
BRIDGE_PID=""
|
|
35
|
-
EXPO_PID=""
|
|
36
|
-
BRIDGE_LOG="$ROOT_DIR/.bridge.log"
|
|
37
|
-
EXPO_LOG="$ROOT_DIR/.expo.log"
|
|
38
|
-
BRIDGE_PID_FILE="$ROOT_DIR/.bridge.pid"
|
|
39
|
-
EXPO_PID_FILE="$ROOT_DIR/.expo.pid"
|
|
40
|
-
KEEP_SERVICES_RUNNING="false"
|
|
41
32
|
SECURE_ENV_FILE="$ROOT_DIR/.env.secure"
|
|
42
33
|
MENU_RESULT=""
|
|
43
34
|
SECTION_COUNT=0
|
|
@@ -45,8 +36,6 @@ RAIL_GLYPH="${DIM}│${RESET}"
|
|
|
45
36
|
RAIL_BRANCH="${DIM}├─${RESET}"
|
|
46
37
|
RAIL_CHILD="${DIM}│${RESET}"
|
|
47
38
|
OS_NAME="$(uname -s)"
|
|
48
|
-
EXPO_STOP_PATTERN="$ROOT_DIR/.*/expo start|$ROOT_DIR/node_modules/.bin/expo start"
|
|
49
|
-
BRIDGE_STOP_PATTERN="$ROOT_DIR/services/rust-bridge|codex-rust-bridge|@codex/rust-bridge"
|
|
50
39
|
|
|
51
40
|
rail_echo() { printf "%s %s\n" "$RAIL_GLYPH" "$1"; }
|
|
52
41
|
rail_blank() { printf "%s\n" "$RAIL_GLYPH"; }
|
|
@@ -81,46 +70,6 @@ run_quiet_command() {
|
|
|
81
70
|
return 1
|
|
82
71
|
}
|
|
83
72
|
|
|
84
|
-
list_matching_pids() {
|
|
85
|
-
local pattern="$1"
|
|
86
|
-
pgrep -f "$pattern" 2>/dev/null || true
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
stop_process_group_by_pattern() {
|
|
90
|
-
local label="$1"
|
|
91
|
-
local pattern="$2"
|
|
92
|
-
local pids=""
|
|
93
|
-
local remaining=""
|
|
94
|
-
local pid=""
|
|
95
|
-
|
|
96
|
-
pids="$(list_matching_pids "$pattern")"
|
|
97
|
-
if [[ -z "$pids" ]]; then
|
|
98
|
-
return 0
|
|
99
|
-
fi
|
|
100
|
-
|
|
101
|
-
info "Stopping $label process group: $pids"
|
|
102
|
-
while IFS= read -r pid; do
|
|
103
|
-
[[ -z "$pid" ]] && continue
|
|
104
|
-
kill -TERM "$pid" >/dev/null 2>&1 || true
|
|
105
|
-
done <<<"$pids"
|
|
106
|
-
|
|
107
|
-
sleep 1
|
|
108
|
-
|
|
109
|
-
while IFS= read -r pid; do
|
|
110
|
-
[[ -z "$pid" ]] && continue
|
|
111
|
-
if kill -0 "$pid" >/dev/null 2>&1; then
|
|
112
|
-
remaining+="$pid "
|
|
113
|
-
kill -KILL "$pid" >/dev/null 2>&1 || true
|
|
114
|
-
fi
|
|
115
|
-
done <<<"$pids"
|
|
116
|
-
|
|
117
|
-
if [[ -n "${remaining// }" ]]; then
|
|
118
|
-
warn "Force-stopped $label processes: $remaining"
|
|
119
|
-
fi
|
|
120
|
-
|
|
121
|
-
return 0
|
|
122
|
-
}
|
|
123
|
-
|
|
124
73
|
print_usage() {
|
|
125
74
|
cat <<EOF
|
|
126
75
|
Usage: $(basename "$0") [options]
|
|
@@ -823,11 +772,22 @@ ensure_core_tools() {
|
|
|
823
772
|
fail "openssl is required."
|
|
824
773
|
exit 1
|
|
825
774
|
fi
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
has_packaged_bridge_binary() {
|
|
778
|
+
node "$SCRIPT_DIR/bridge-binary.js" has-current-packaged >/dev/null 2>&1
|
|
779
|
+
}
|
|
826
780
|
|
|
781
|
+
ensure_local_rust_build_toolchain() {
|
|
827
782
|
if ! ensure_or_install_command "cc" "C compiler/linker (cc)" install_c_toolchain_cli "Y"; then
|
|
828
783
|
fail "C compiler/linker is required for Rust crate builds."
|
|
829
784
|
exit 1
|
|
830
785
|
fi
|
|
786
|
+
|
|
787
|
+
if ! ensure_or_install_command "cargo" "Rust/Cargo toolchain" install_rust_toolchain "Y"; then
|
|
788
|
+
fail "Rust/Cargo is required for the rust bridge."
|
|
789
|
+
exit 1
|
|
790
|
+
fi
|
|
831
791
|
}
|
|
832
792
|
|
|
833
793
|
ensure_codex_cli() {
|
|
@@ -1245,137 +1205,6 @@ install_project_dependencies() {
|
|
|
1245
1205
|
fi
|
|
1246
1206
|
}
|
|
1247
1207
|
|
|
1248
|
-
cleanup_bridge() {
|
|
1249
|
-
if [[ "$KEEP_SERVICES_RUNNING" == "true" ]]; then
|
|
1250
|
-
return
|
|
1251
|
-
fi
|
|
1252
|
-
|
|
1253
|
-
stop_process_group_by_pattern "Expo" "$EXPO_STOP_PATTERN"
|
|
1254
|
-
stop_process_group_by_pattern "Rust bridge" "$BRIDGE_STOP_PATTERN"
|
|
1255
|
-
|
|
1256
|
-
rm -f "$BRIDGE_PID_FILE" "$EXPO_PID_FILE"
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
wait_for_bridge_health() {
|
|
1260
|
-
local host="$1"
|
|
1261
|
-
local port="$2"
|
|
1262
|
-
local max_wait_secs="${BRIDGE_HEALTH_WAIT_SECS:-300}"
|
|
1263
|
-
local elapsed=0
|
|
1264
|
-
|
|
1265
|
-
if ! command -v curl >/dev/null 2>&1; then
|
|
1266
|
-
return 0
|
|
1267
|
-
fi
|
|
1268
|
-
|
|
1269
|
-
while true; do
|
|
1270
|
-
if curl --max-time 1 -fsS "http://$host:$port/health" >/dev/null 2>&1; then
|
|
1271
|
-
return 0
|
|
1272
|
-
fi
|
|
1273
|
-
|
|
1274
|
-
if [[ -n "$BRIDGE_PID" ]] && ! kill -0 "$BRIDGE_PID" >/dev/null 2>&1; then
|
|
1275
|
-
return 1
|
|
1276
|
-
fi
|
|
1277
|
-
|
|
1278
|
-
sleep 1
|
|
1279
|
-
elapsed=$((elapsed + 1))
|
|
1280
|
-
if (( elapsed % 5 == 0 )); then
|
|
1281
|
-
info "Waiting for bridge health... ${elapsed}s elapsed"
|
|
1282
|
-
fi
|
|
1283
|
-
|
|
1284
|
-
if (( elapsed >= max_wait_secs )); then
|
|
1285
|
-
return 2
|
|
1286
|
-
fi
|
|
1287
|
-
done
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
stream_expo_output_until_enter() {
|
|
1291
|
-
local tail_pid=""
|
|
1292
|
-
local _user_input=""
|
|
1293
|
-
local waited=0
|
|
1294
|
-
local max_wait_secs="${EXPO_OUTPUT_WAIT_SECS:-90}"
|
|
1295
|
-
local -a spinner_frames=("-" "\\" "|" "/")
|
|
1296
|
-
local frame="-"
|
|
1297
|
-
|
|
1298
|
-
rail_echo "Expo output is live below."
|
|
1299
|
-
rail_echo "Press Enter to exit onboarding and keep Expo + bridge running (Ctrl+D also detaches)."
|
|
1300
|
-
echo ""
|
|
1301
|
-
|
|
1302
|
-
if [[ ! -s "$EXPO_LOG" ]]; then
|
|
1303
|
-
while true; do
|
|
1304
|
-
if [[ -s "$EXPO_LOG" ]]; then
|
|
1305
|
-
printf "\r\033[2K%s Expo output started.\n" "$RAIL_GLYPH"
|
|
1306
|
-
break
|
|
1307
|
-
fi
|
|
1308
|
-
|
|
1309
|
-
if [[ -n "$EXPO_PID" ]] && ! kill -0 "$EXPO_PID" >/dev/null 2>&1; then
|
|
1310
|
-
printf "\r\033[2K%s Expo process exited before output appeared.\n" "$RAIL_GLYPH"
|
|
1311
|
-
return 1
|
|
1312
|
-
fi
|
|
1313
|
-
|
|
1314
|
-
if (( waited >= max_wait_secs )); then
|
|
1315
|
-
printf "\r\033[2K%s Still waiting for Expo output...\n" "$RAIL_GLYPH"
|
|
1316
|
-
break
|
|
1317
|
-
fi
|
|
1318
|
-
|
|
1319
|
-
frame="${spinner_frames[$((waited % ${#spinner_frames[@]}))]}"
|
|
1320
|
-
printf "\r\033[2K%s Waiting for Expo output %s %ss" "$RAIL_GLYPH" "$frame" "$waited"
|
|
1321
|
-
sleep 1
|
|
1322
|
-
waited=$((waited + 1))
|
|
1323
|
-
done
|
|
1324
|
-
fi
|
|
1325
|
-
|
|
1326
|
-
tail -n +1 -f "$EXPO_LOG" &
|
|
1327
|
-
tail_pid="$!"
|
|
1328
|
-
if ! IFS= read -r _user_input 2>/dev/null; then
|
|
1329
|
-
rail_echo "Input stream closed; detaching onboarding."
|
|
1330
|
-
fi
|
|
1331
|
-
kill -TERM "$tail_pid" >/dev/null 2>&1 || true
|
|
1332
|
-
wait "$tail_pid" 2>/dev/null || true
|
|
1333
|
-
return 0
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
start_expo_process_background() {
|
|
1337
|
-
local log_file="$1"
|
|
1338
|
-
|
|
1339
|
-
if command -v script >/dev/null 2>&1; then
|
|
1340
|
-
if [[ "$OS_NAME" == "Darwin" ]]; then
|
|
1341
|
-
# Feed script from a never-ending pipe to avoid EOF-triggered Expo shutdown.
|
|
1342
|
-
nohup bash -lc "cd \"$ROOT_DIR\" && tail -f /dev/null | script -q \"$log_file\" npm run \"$EXPO_MODE\"" >/dev/null 2>&1 &
|
|
1343
|
-
EXPO_PID="$!"
|
|
1344
|
-
return 0
|
|
1345
|
-
fi
|
|
1346
|
-
|
|
1347
|
-
# util-linux script uses -c for command mode.
|
|
1348
|
-
nohup bash -lc "cd \"$ROOT_DIR\" && tail -f /dev/null | script -q -f -c \"npm run $EXPO_MODE\" \"$log_file\"" >/dev/null 2>&1 &
|
|
1349
|
-
EXPO_PID="$!"
|
|
1350
|
-
return 0
|
|
1351
|
-
fi
|
|
1352
|
-
|
|
1353
|
-
nohup bash -lc "cd \"$ROOT_DIR\" && npm run \"$EXPO_MODE\"" >"$log_file" 2>&1 </dev/null &
|
|
1354
|
-
EXPO_PID="$!"
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
start_expo_background() {
|
|
1358
|
-
info "Starting Expo ($EXPO_MODE) in background..."
|
|
1359
|
-
: >"$EXPO_LOG"
|
|
1360
|
-
start_expo_process_background "$EXPO_LOG"
|
|
1361
|
-
echo "$EXPO_PID" > "$EXPO_PID_FILE"
|
|
1362
|
-
|
|
1363
|
-
sleep 1
|
|
1364
|
-
if ! kill -0 "$EXPO_PID" >/dev/null 2>&1; then
|
|
1365
|
-
fail "Expo failed to start. Recent logs:"
|
|
1366
|
-
tail -n 80 "$EXPO_LOG" || true
|
|
1367
|
-
exit 1
|
|
1368
|
-
fi
|
|
1369
|
-
|
|
1370
|
-
KEEP_SERVICES_RUNNING="true"
|
|
1371
|
-
ok "Bridge + Expo are running in background."
|
|
1372
|
-
rail_echo "Bridge logs: $BRIDGE_LOG"
|
|
1373
|
-
rail_echo "Expo logs: $EXPO_LOG"
|
|
1374
|
-
rail_echo "To stop later:"
|
|
1375
|
-
rail_echo "pkill -TERM -f '$EXPO_STOP_PATTERN'; pkill -TERM -f '$BRIDGE_STOP_PATTERN'"
|
|
1376
|
-
stream_expo_output_until_enter
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
1208
|
start_bridge_foreground() {
|
|
1380
1209
|
rail_echo "Starting bridge in foreground."
|
|
1381
1210
|
rail_echo "Press Ctrl+C to stop the bridge."
|
|
@@ -1406,9 +1235,11 @@ choose_flow
|
|
|
1406
1235
|
|
|
1407
1236
|
section "Prerequisites"
|
|
1408
1237
|
ensure_core_tools
|
|
1409
|
-
if
|
|
1410
|
-
|
|
1411
|
-
|
|
1238
|
+
if has_packaged_bridge_binary; then
|
|
1239
|
+
ok "Found packaged Rust bridge binary for this host."
|
|
1240
|
+
else
|
|
1241
|
+
info "No packaged bridge binary found for this host. Falling back to local Rust build."
|
|
1242
|
+
ensure_local_rust_build_toolchain
|
|
1412
1243
|
fi
|
|
1413
1244
|
ensure_codex_cli
|
|
1414
1245
|
install_project_dependencies
|
|
@@ -1517,6 +1348,6 @@ fi
|
|
|
1517
1348
|
|
|
1518
1349
|
section "Next steps"
|
|
1519
1350
|
rail_echo "1) cd $ROOT_DIR && npm run secure:bridge"
|
|
1520
|
-
rail_echo "2) Open the
|
|
1351
|
+
rail_echo "2) Open the mobile app and use onboarding to connect (URL + token QR)."
|
|
1521
1352
|
rail_blank
|
|
1522
1353
|
rail_echo "${DIM}You can rerun this anytime: npm run setup:wizard${RESET}"
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { spawn, spawnSync } = require("node:child_process");
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
const os = require("node:os");
|
|
7
|
+
const path = require("node:path");
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
builtBinaryPath,
|
|
11
|
+
ensureExecutable,
|
|
12
|
+
packagedBinaryPath,
|
|
13
|
+
resolveRuntimeTarget,
|
|
14
|
+
} = require("./bridge-binary");
|
|
15
|
+
|
|
16
|
+
function resolveRootDir() {
|
|
17
|
+
let rootDir = process.env.INIT_CWD ? path.resolve(process.env.INIT_CWD) : path.resolve(__dirname, "..");
|
|
18
|
+
if (!fs.existsSync(path.join(rootDir, "package.json"))) {
|
|
19
|
+
rootDir = path.resolve(__dirname, "..");
|
|
20
|
+
}
|
|
21
|
+
return rootDir;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readEnvFile(filePath) {
|
|
25
|
+
const contents = fs.readFileSync(filePath, "utf8");
|
|
26
|
+
const nextEnv = {};
|
|
27
|
+
|
|
28
|
+
for (const rawLine of contents.split(/\r?\n/)) {
|
|
29
|
+
const line = rawLine.trim();
|
|
30
|
+
if (!line || line.startsWith("#")) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const match = line.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
35
|
+
if (!match) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const [, key, rawValue] = match;
|
|
40
|
+
let value = rawValue;
|
|
41
|
+
if (
|
|
42
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
43
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
44
|
+
) {
|
|
45
|
+
value = value.slice(1, -1);
|
|
46
|
+
}
|
|
47
|
+
nextEnv[key] = value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return nextEnv;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function commandExists(command) {
|
|
54
|
+
const checker = process.platform === "win32" ? "where" : "which";
|
|
55
|
+
const result = spawnSync(checker, [command], { stdio: "ignore" });
|
|
56
|
+
return result.status === 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function walkFiles(directory) {
|
|
60
|
+
const entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
61
|
+
const files = [];
|
|
62
|
+
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
const entryPath = path.join(directory, entry.name);
|
|
65
|
+
if (entry.isDirectory()) {
|
|
66
|
+
files.push(...walkFiles(entryPath));
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (entry.isFile()) {
|
|
70
|
+
files.push(entryPath);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return files;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isBuiltBinaryFresh(rootDir, binaryPath) {
|
|
78
|
+
if (!fs.existsSync(binaryPath)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const binaryMtime = fs.statSync(binaryPath).mtimeMs;
|
|
83
|
+
const watchPaths = [
|
|
84
|
+
path.join(rootDir, "services", "rust-bridge", "Cargo.toml"),
|
|
85
|
+
path.join(rootDir, "services", "rust-bridge", "Cargo.lock"),
|
|
86
|
+
];
|
|
87
|
+
const sourceDir = path.join(rootDir, "services", "rust-bridge", "src");
|
|
88
|
+
|
|
89
|
+
if (fs.existsSync(sourceDir)) {
|
|
90
|
+
watchPaths.push(...walkFiles(sourceDir));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return watchPaths.every((watchPath) => {
|
|
94
|
+
if (!fs.existsSync(watchPath)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return fs.statSync(watchPath).mtimeMs <= binaryMtime;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function printMissingCompilerHint() {
|
|
102
|
+
if (process.platform === "win32") {
|
|
103
|
+
console.error("Install Visual Studio Build Tools (Desktop development with C++) and Rust, then retry.");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (commandExists("apt-get")) {
|
|
107
|
+
console.error("Install on Ubuntu/Debian: sudo apt-get update && sudo apt-get install -y build-essential");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (commandExists("dnf")) {
|
|
111
|
+
console.error("Install on Fedora/RHEL: sudo dnf install -y gcc gcc-c++ make");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (commandExists("yum")) {
|
|
115
|
+
console.error("Install on CentOS/RHEL: sudo yum install -y gcc gcc-c++ make");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (commandExists("apk")) {
|
|
119
|
+
console.error("Install on Alpine: sudo apk add build-base");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (commandExists("xcode-select")) {
|
|
123
|
+
console.error("Install on macOS: xcode-select --install");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function spawnAndRelay(command, args, options) {
|
|
128
|
+
const child = spawn(command, args, {
|
|
129
|
+
stdio: "inherit",
|
|
130
|
+
...options,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
child.on("error", (error) => {
|
|
134
|
+
console.error(`error: failed to start ${command}: ${error.message}`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
child.on("exit", (code, signal) => {
|
|
139
|
+
if (signal) {
|
|
140
|
+
process.kill(process.pid, signal);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
process.exit(code ?? 0);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function buildBridgeFromSource(rootDir, env) {
|
|
148
|
+
const cargoCmd = "cargo";
|
|
149
|
+
const args = ["build", "--release", "--locked"];
|
|
150
|
+
const result = spawnSync(cargoCmd, args, {
|
|
151
|
+
cwd: path.join(rootDir, "services", "rust-bridge"),
|
|
152
|
+
env,
|
|
153
|
+
stdio: "inherit",
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (result.error) {
|
|
157
|
+
console.error(`error: failed to run cargo build: ${result.error.message}`);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if ((result.status ?? 1) !== 0) {
|
|
162
|
+
process.exit(result.status ?? 1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function start() {
|
|
167
|
+
const rootDir = resolveRootDir();
|
|
168
|
+
const secureEnvFile = path.join(rootDir, ".env.secure");
|
|
169
|
+
if (!fs.existsSync(secureEnvFile)) {
|
|
170
|
+
console.error(`error: ${secureEnvFile} not found. Run: npm run secure:setup`);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const fileEnv = readEnvFile(secureEnvFile);
|
|
175
|
+
const env = { ...fileEnv, ...process.env };
|
|
176
|
+
const devMode = process.argv.includes("--dev") || env.BRIDGE_RUN_MODE === "dev";
|
|
177
|
+
const forceSourceBuild = env.CLAWDEX_BRIDGE_FORCE_SOURCE_BUILD === "true";
|
|
178
|
+
|
|
179
|
+
if (devMode) {
|
|
180
|
+
if (!commandExists("cargo")) {
|
|
181
|
+
console.error("error: missing Rust/Cargo toolchain for dev bridge mode.");
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
spawnAndRelay("cargo", ["run"], {
|
|
186
|
+
cwd: path.join(rootDir, "services", "rust-bridge"),
|
|
187
|
+
env,
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const overrideBinary = env.CLAWDEX_BRIDGE_BINARY ? path.resolve(env.CLAWDEX_BRIDGE_BINARY) : "";
|
|
193
|
+
if (overrideBinary) {
|
|
194
|
+
if (!fs.existsSync(overrideBinary)) {
|
|
195
|
+
console.error(`error: CLAWDEX_BRIDGE_BINARY not found at ${overrideBinary}`);
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
ensureExecutable(overrideBinary);
|
|
199
|
+
spawnAndRelay(overrideBinary, [], { cwd: rootDir, env });
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const packagedBinary = packagedBinaryPath(rootDir, resolveRuntimeTarget());
|
|
204
|
+
if (!forceSourceBuild && packagedBinary && fs.existsSync(packagedBinary)) {
|
|
205
|
+
ensureExecutable(packagedBinary);
|
|
206
|
+
spawnAndRelay(packagedBinary, [], { cwd: rootDir, env });
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const builtBinary = builtBinaryPath(rootDir, os.platform());
|
|
211
|
+
if (isBuiltBinaryFresh(rootDir, builtBinary)) {
|
|
212
|
+
ensureExecutable(builtBinary);
|
|
213
|
+
spawnAndRelay(builtBinary, [], { cwd: rootDir, env });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!commandExists("cargo")) {
|
|
218
|
+
console.error("error: no packaged bridge binary was found for this host, and cargo is not installed.");
|
|
219
|
+
console.error("Reinstall a published clawdex-mobile package with bundled bridge binaries, or install Rust and retry.");
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (process.platform !== "win32" && !commandExists("cc")) {
|
|
224
|
+
console.error("error: missing system C compiler/linker ('cc'). Rust bridge cannot compile without it.");
|
|
225
|
+
printMissingCompilerHint();
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
buildBridgeFromSource(rootDir, env);
|
|
230
|
+
|
|
231
|
+
if (!fs.existsSync(builtBinary)) {
|
|
232
|
+
console.error(`error: expected built bridge binary at ${builtBinary}, but it was not created.`);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
ensureExecutable(builtBinary);
|
|
237
|
+
spawnAndRelay(builtBinary, [], { cwd: rootDir, env });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
start();
|
|
@@ -2,43 +2,4 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -L)"
|
|
5
|
-
|
|
6
|
-
if [[ ! -f "$ROOT_DIR/package.json" ]]; then
|
|
7
|
-
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd -L)"
|
|
8
|
-
fi
|
|
9
|
-
SECURE_ENV_FILE="$ROOT_DIR/.env.secure"
|
|
10
|
-
|
|
11
|
-
if [[ ! -f "$SECURE_ENV_FILE" ]]; then
|
|
12
|
-
echo "error: $SECURE_ENV_FILE not found. Run: npm run secure:setup" >&2
|
|
13
|
-
exit 1
|
|
14
|
-
fi
|
|
15
|
-
|
|
16
|
-
if ! command -v cc >/dev/null 2>&1; then
|
|
17
|
-
echo "error: missing system C compiler/linker ('cc'). Rust bridge cannot compile without it." >&2
|
|
18
|
-
if command -v apt-get >/dev/null 2>&1; then
|
|
19
|
-
echo "Install on Ubuntu/Debian: sudo apt-get update && sudo apt-get install -y build-essential" >&2
|
|
20
|
-
elif command -v dnf >/dev/null 2>&1; then
|
|
21
|
-
echo "Install on Fedora/RHEL: sudo dnf install -y gcc gcc-c++ make" >&2
|
|
22
|
-
elif command -v yum >/dev/null 2>&1; then
|
|
23
|
-
echo "Install on CentOS/RHEL: sudo yum install -y gcc gcc-c++ make" >&2
|
|
24
|
-
elif command -v apk >/dev/null 2>&1; then
|
|
25
|
-
echo "Install on Alpine: sudo apk add build-base" >&2
|
|
26
|
-
elif command -v xcode-select >/dev/null 2>&1; then
|
|
27
|
-
echo "Install on macOS: xcode-select --install" >&2
|
|
28
|
-
fi
|
|
29
|
-
exit 1
|
|
30
|
-
fi
|
|
31
|
-
|
|
32
|
-
set -a
|
|
33
|
-
# shellcheck disable=SC1090
|
|
34
|
-
source "$SECURE_ENV_FILE"
|
|
35
|
-
set +a
|
|
36
|
-
|
|
37
|
-
BRIDGE_RUN_MODE="${BRIDGE_RUN_MODE:-release}"
|
|
38
|
-
|
|
39
|
-
cd "$ROOT_DIR"
|
|
40
|
-
if [[ "$BRIDGE_RUN_MODE" == "dev" ]]; then
|
|
41
|
-
exec npm run -w @codex/rust-bridge dev
|
|
42
|
-
fi
|
|
43
|
-
|
|
44
|
-
exec npm run -w @codex/rust-bridge start
|
|
5
|
+
exec node "$SCRIPT_DIR/start-bridge-secure.js" "$@"
|
|
@@ -14,17 +14,17 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@fastify/cors": "^10.0.2",
|
|
16
16
|
"@fastify/websocket": "^11.0.2",
|
|
17
|
-
"fastify": "^5.2
|
|
17
|
+
"fastify": "^5.8.2",
|
|
18
18
|
"zod": "^3.24.1"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@types/node": "^22.
|
|
22
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
23
|
-
"@typescript-eslint/parser": "^8.
|
|
24
|
-
"eslint": "^10.0.
|
|
21
|
+
"@types/node": "^22.19.15",
|
|
22
|
+
"@typescript-eslint/eslint-plugin": "^8.57.1",
|
|
23
|
+
"@typescript-eslint/parser": "^8.57.1",
|
|
24
|
+
"eslint": "^10.0.3",
|
|
25
25
|
"globals": "^16.5.0",
|
|
26
26
|
"tsx": "^4.19.2",
|
|
27
27
|
"typescript": "^5.7.2",
|
|
28
|
-
"vitest": "^4.0
|
|
28
|
+
"vitest": "^4.1.0"
|
|
29
29
|
}
|
|
30
30
|
}
|