clawdex-mobile 3.0.0 → 5.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 +66 -2
- package/.github/workflows/pages.yml +1 -1
- package/README.md +16 -3
- package/apps/mobile/app.json +1 -1
- package/apps/mobile/package.json +2 -1
- package/apps/mobile/src/api/__tests__/client.test.ts +13 -5
- package/apps/mobile/src/api/client.ts +25 -0
- package/apps/mobile/src/api/types.ts +1 -0
- package/apps/mobile/src/components/WorkspacePickerModal.tsx +555 -315
- package/apps/mobile/src/screens/MainScreen.tsx +0 -5
- package/apps/mobile/src/screens/OnboardingScreen.tsx +924 -312
- package/bin/clawdex.js +7 -6
- package/codex-rust-bridge +0 -0
- package/codex-rust-bridge.exe +0 -0
- package/docs/setup-and-operations.md +19 -12
- package/docs/troubleshooting.md +16 -19
- package/package.json +4 -3
- package/scripts/bridge-binary.js +200 -0
- package/scripts/setup-wizard.sh +17 -240
- package/scripts/start-bridge-secure.js +240 -0
- package/scripts/start-bridge-secure.sh +1 -40
- package/services/rust-bridge/Cargo.lock +1 -1
- package/services/rust-bridge/Cargo.toml +1 -1
- package/services/rust-bridge/package.json +1 -1
- package/services/rust-bridge/src/main.rs +11 -1
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
|
+
}
|
|
826
776
|
|
|
777
|
+
has_packaged_bridge_binary() {
|
|
778
|
+
node "$SCRIPT_DIR/bridge-binary.js" has-current-packaged >/dev/null 2>&1
|
|
779
|
+
}
|
|
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() {
|
|
@@ -1192,190 +1152,6 @@ confirm_phone_network_ready() {
|
|
|
1192
1152
|
esac
|
|
1193
1153
|
}
|
|
1194
1154
|
|
|
1195
|
-
has_mobile_react_native_runtime() {
|
|
1196
|
-
local root_touchable="$ROOT_DIR/node_modules/react-native/Libraries/Components/Touchable/BoundingDimensions.js"
|
|
1197
|
-
local workspace_touchable="$ROOT_DIR/apps/mobile/node_modules/react-native/Libraries/Components/Touchable/BoundingDimensions.js"
|
|
1198
|
-
local root_devtools="$ROOT_DIR/node_modules/react-native/src/private/devsupport/rndevtools/specs/NativeReactDevToolsRuntimeSettingsModule.js"
|
|
1199
|
-
local workspace_devtools="$ROOT_DIR/apps/mobile/node_modules/react-native/src/private/devsupport/rndevtools/specs/NativeReactDevToolsRuntimeSettingsModule.js"
|
|
1200
|
-
|
|
1201
|
-
local touchable_ok="false"
|
|
1202
|
-
local devtools_ok="false"
|
|
1203
|
-
|
|
1204
|
-
if [[ -f "$root_touchable" ]] || [[ -f "$workspace_touchable" ]]; then
|
|
1205
|
-
touchable_ok="true"
|
|
1206
|
-
fi
|
|
1207
|
-
if [[ -f "$root_devtools" ]] || [[ -f "$workspace_devtools" ]]; then
|
|
1208
|
-
devtools_ok="true"
|
|
1209
|
-
fi
|
|
1210
|
-
|
|
1211
|
-
[[ "$touchable_ok" == "true" ]] && [[ "$devtools_ok" == "true" ]]
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
repair_mobile_runtime_dependencies() {
|
|
1215
|
-
info "Repairing mobile runtime dependencies (React Native + Expo toolchain)..."
|
|
1216
|
-
run_quiet_command "React Native dependency repair" bash -lc "cd \"$ROOT_DIR\" && npm install --include=dev --force && npm install --include=dev --force -w apps/mobile && npm dedupe"
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
install_project_dependencies() {
|
|
1220
|
-
local should_install="false"
|
|
1221
|
-
local need_install="false"
|
|
1222
|
-
|
|
1223
|
-
if [[ ! -d "$ROOT_DIR/node_modules" ]] || [[ ! -d "$ROOT_DIR/node_modules/@codex" ]]; then
|
|
1224
|
-
need_install="true"
|
|
1225
|
-
fi
|
|
1226
|
-
|
|
1227
|
-
if [[ "$need_install" == "true" ]]; then
|
|
1228
|
-
if confirm_prompt "Install project npm dependencies now?" "Y"; then
|
|
1229
|
-
should_install="true"
|
|
1230
|
-
else
|
|
1231
|
-
if [[ "$AUTO_START" == "true" ]]; then
|
|
1232
|
-
abort_wizard "Dependencies are required before starting the bridge."
|
|
1233
|
-
fi
|
|
1234
|
-
fi
|
|
1235
|
-
else
|
|
1236
|
-
if [[ "$FLOW" == "manual" ]] && confirm_prompt "Refresh npm dependencies now?" "N"; then
|
|
1237
|
-
should_install="true"
|
|
1238
|
-
fi
|
|
1239
|
-
fi
|
|
1240
|
-
|
|
1241
|
-
if [[ "$should_install" == "true" ]]; then
|
|
1242
|
-
info "Installing npm dependencies (including dev tooling; this can take a few minutes)..."
|
|
1243
|
-
run_quiet_command "Project dependency install" bash -lc "cd \"$ROOT_DIR\" && npm install --include=dev && npm dedupe"
|
|
1244
|
-
ok "Dependencies installed."
|
|
1245
|
-
fi
|
|
1246
|
-
}
|
|
1247
|
-
|
|
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
1155
|
start_bridge_foreground() {
|
|
1380
1156
|
rail_echo "Starting bridge in foreground."
|
|
1381
1157
|
rail_echo "Press Ctrl+C to stop the bridge."
|
|
@@ -1406,12 +1182,13 @@ choose_flow
|
|
|
1406
1182
|
|
|
1407
1183
|
section "Prerequisites"
|
|
1408
1184
|
ensure_core_tools
|
|
1409
|
-
if
|
|
1410
|
-
|
|
1411
|
-
|
|
1185
|
+
if has_packaged_bridge_binary; then
|
|
1186
|
+
ok "Found packaged Rust bridge binary for this host."
|
|
1187
|
+
else
|
|
1188
|
+
info "No packaged bridge binary found for this host. Falling back to local Rust build."
|
|
1189
|
+
ensure_local_rust_build_toolchain
|
|
1412
1190
|
fi
|
|
1413
1191
|
ensure_codex_cli
|
|
1414
|
-
install_project_dependencies
|
|
1415
1192
|
|
|
1416
1193
|
section "Config handling"
|
|
1417
1194
|
choose_config_action
|
|
@@ -1517,6 +1294,6 @@ fi
|
|
|
1517
1294
|
|
|
1518
1295
|
section "Next steps"
|
|
1519
1296
|
rail_echo "1) cd $ROOT_DIR && npm run secure:bridge"
|
|
1520
|
-
rail_echo "2) Open the
|
|
1297
|
+
rail_echo "2) Open the mobile app and use onboarding to connect (URL + token QR)."
|
|
1521
1298
|
rail_blank
|
|
1522
1299
|
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" "$@"
|
|
@@ -1907,6 +1907,7 @@ struct WorkspaceListRequest {
|
|
|
1907
1907
|
struct WorkspaceSummary {
|
|
1908
1908
|
path: String,
|
|
1909
1909
|
chat_count: usize,
|
|
1910
|
+
updated_at: Option<u64>,
|
|
1910
1911
|
}
|
|
1911
1912
|
|
|
1912
1913
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
@@ -2806,7 +2807,16 @@ async fn list_workspace_roots(
|
|
|
2806
2807
|
|
|
2807
2808
|
let mut workspaces = workspaces_by_path
|
|
2808
2809
|
.into_iter()
|
|
2809
|
-
.map(|(path, (chat_count, updated_at))|
|
|
2810
|
+
.map(|(path, (chat_count, updated_at))| {
|
|
2811
|
+
(
|
|
2812
|
+
WorkspaceSummary {
|
|
2813
|
+
path,
|
|
2814
|
+
chat_count,
|
|
2815
|
+
updated_at: (updated_at > 0).then_some(updated_at),
|
|
2816
|
+
},
|
|
2817
|
+
updated_at,
|
|
2818
|
+
)
|
|
2819
|
+
})
|
|
2810
2820
|
.collect::<Vec<_>>();
|
|
2811
2821
|
|
|
2812
2822
|
workspaces.sort_by(|(left, left_updated_at), (right, right_updated_at)| {
|