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.
@@ -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 ! ensure_or_install_command "cargo" "Rust/Cargo toolchain" install_rust_toolchain "Y"; then
1410
- fail "Rust/Cargo is required for the rust bridge."
1411
- exit 1
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 iOS app and use onboarding to connect (mode + URL + token QR)."
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
- ROOT_DIR="${INIT_CWD:-$(cd "$SCRIPT_DIR/.." && pwd -L)}"
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" "$@"
@@ -149,7 +149,7 @@ dependencies = [
149
149
 
150
150
  [[package]]
151
151
  name = "codex-rust-bridge"
152
- version = "3.0.0"
152
+ version = "5.0.0"
153
153
  dependencies = [
154
154
  "axum",
155
155
  "base64",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "codex-rust-bridge"
3
- version = "3.0.0"
3
+ version = "5.0.0"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codex/rust-bridge",
3
- "version": "3.0.0",
3
+ "version": "5.0.0",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "dev": "cargo run",
@@ -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))| (WorkspaceSummary { 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)| {