clawdex-mobile 5.0.7 → 5.0.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawdex-mobile",
3
- "version": "5.0.7",
3
+ "version": "5.0.8",
4
4
  "description": "Private-network mobile bridge and CLI for Codex and OpenCode",
5
5
  "keywords": [
6
6
  "codex",
@@ -19,6 +19,9 @@
19
19
  "publishConfig": {
20
20
  "access": "public"
21
21
  },
22
+ "dependencies": {
23
+ "qrcode-terminal": "^0.12.0"
24
+ },
22
25
  "bin": {
23
26
  "clawdex": "./bin/clawdex.js"
24
27
  },
@@ -194,7 +194,7 @@ sync_active_engine_from_selection() {
194
194
  return
195
195
  fi
196
196
 
197
- if engine_list_contains "$ACTIVE_ENGINE" "${SELECTED_ENGINES[@]}"; then
197
+ if selected_engines_contains "$ACTIVE_ENGINE"; then
198
198
  return
199
199
  fi
200
200
 
@@ -215,6 +215,30 @@ format_engine_list() {
215
215
  printf '%s' "$result"
216
216
  }
217
217
 
218
+ selected_engines_contains() {
219
+ local needle="$1"
220
+
221
+ if (( ${#SELECTED_ENGINES[@]} == 0 )); then
222
+ return 1
223
+ fi
224
+
225
+ engine_list_contains "$needle" "${SELECTED_ENGINES[@]}"
226
+ }
227
+
228
+ format_selected_engines() {
229
+ if (( ${#SELECTED_ENGINES[@]} == 0 )); then
230
+ printf ''
231
+ return 0
232
+ fi
233
+
234
+ format_engine_list "${SELECTED_ENGINES[@]}"
235
+ }
236
+
237
+ selected_engines_csv() {
238
+ local IFS=','
239
+ printf '%s' "${SELECTED_ENGINES[*]-}"
240
+ }
241
+
218
242
  engine_from_menu_label() {
219
243
  case "$1" in
220
244
  "Codex")
@@ -957,7 +981,11 @@ print_existing_setup_summary() {
957
981
  local harnesses=""
958
982
  local source_path=""
959
983
  local saved_active_engine="$ACTIVE_ENGINE"
960
- local -a saved_selected_engines=("${SELECTED_ENGINES[@]}")
984
+ local -a saved_selected_engines=()
985
+
986
+ if (( ${#SELECTED_ENGINES[@]} > 0 )); then
987
+ saved_selected_engines=("${SELECTED_ENGINES[@]}")
988
+ fi
961
989
 
962
990
  if [[ ! -f "$SECURE_ENV_FILE" ]]; then
963
991
  return 1
@@ -971,7 +999,7 @@ print_existing_setup_summary() {
971
999
  if [[ -n "$harnesses" ]] && ! parse_existing_engine_list_csv "$harnesses"; then
972
1000
  harnesses=""
973
1001
  elif [[ -n "$harnesses" ]]; then
974
- harnesses="$(IFS=,; printf '%s' "${SELECTED_ENGINES[*]}")"
1002
+ harnesses="$(selected_engines_csv)"
975
1003
  fi
976
1004
  if [[ -z "$harnesses" ]]; then
977
1005
  harnesses="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_ACTIVE_ENGINE")"
@@ -999,7 +1027,10 @@ print_existing_setup_summary() {
999
1027
  fi
1000
1028
  echo "source: $source_path"
1001
1029
 
1002
- SELECTED_ENGINES=("${saved_selected_engines[@]}")
1030
+ SELECTED_ENGINES=()
1031
+ if (( ${#saved_selected_engines[@]} > 0 )); then
1032
+ SELECTED_ENGINES=("${saved_selected_engines[@]}")
1033
+ fi
1003
1034
  ACTIVE_ENGINE="$saved_active_engine"
1004
1035
  }
1005
1036
 
@@ -1082,12 +1113,12 @@ choose_bridge_network_mode() {
1082
1113
  choose_runtime_engine() {
1083
1114
  local label=""
1084
1115
  MENU_MULTI_PRESELECTED="Codex"
1085
- if engine_list_contains "codex" "${SELECTED_ENGINES[@]}"; then
1116
+ if selected_engines_contains "codex"; then
1086
1117
  MENU_MULTI_PRESELECTED="Codex"
1087
1118
  else
1088
1119
  MENU_MULTI_PRESELECTED=""
1089
1120
  fi
1090
- if engine_list_contains "opencode" "${SELECTED_ENGINES[@]}"; then
1121
+ if selected_engines_contains "opencode"; then
1091
1122
  if [[ -n "$MENU_MULTI_PRESELECTED" ]]; then
1092
1123
  MENU_MULTI_PRESELECTED+=","
1093
1124
  fi
@@ -1101,7 +1132,7 @@ choose_runtime_engine() {
1101
1132
  SELECTED_ENGINES+=("$(engine_from_menu_label "$label")")
1102
1133
  done
1103
1134
  sync_active_engine_from_selection
1104
- info "Selected harnesses: $(format_engine_list "${SELECTED_ENGINES[@]}")."
1135
+ info "Selected harnesses: $(format_selected_engines)."
1105
1136
  }
1106
1137
 
1107
1138
  infer_network_mode_from_host() {
@@ -1217,6 +1248,11 @@ ensure_opencode_cli() {
1217
1248
 
1218
1249
  ensure_selected_engine_clis() {
1219
1250
  local engine=""
1251
+
1252
+ if (( ${#SELECTED_ENGINES[@]} == 0 )); then
1253
+ abort_wizard "Select at least one harness before continuing."
1254
+ fi
1255
+
1220
1256
  for engine in "${SELECTED_ENGINES[@]}"; do
1221
1257
  case "$engine" in
1222
1258
  codex)
@@ -1613,16 +1649,16 @@ fi
1613
1649
  load_existing_engine_selection
1614
1650
  if [[ "$CONFIG_ACTION" == "keep" ]]; then
1615
1651
  if [[ "$ENGINE_SELECTION_PRESET" == "false" ]]; then
1616
- info "Keeping existing harnesses: $(format_engine_list "${SELECTED_ENGINES[@]}")."
1652
+ info "Keeping existing harnesses: $(format_selected_engines)."
1617
1653
  else
1618
- info "Harness selection preset via flag: $(format_engine_list "${SELECTED_ENGINES[@]}")."
1654
+ info "Harness selection preset via flag: $(format_selected_engines)."
1619
1655
  fi
1620
1656
  else
1621
1657
  section "Harnesses"
1622
1658
  if [[ "$ENGINE_SELECTION_PRESET" == "false" ]]; then
1623
1659
  choose_runtime_engine
1624
1660
  else
1625
- info "Harness selection preset via flag: $(format_engine_list "${SELECTED_ENGINES[@]}")."
1661
+ info "Harness selection preset via flag: $(format_selected_engines)."
1626
1662
  fi
1627
1663
  fi
1628
1664
 
@@ -1652,7 +1688,7 @@ if [[ "$CONFIG_ACTION" != "keep" ]]; then
1652
1688
  esac
1653
1689
 
1654
1690
  section "Write secure config"
1655
- BRIDGE_NETWORK_MODE="$NETWORK_MODE" BRIDGE_HOST_OVERRIDE="$BRIDGE_HOST" BRIDGE_ACTIVE_ENGINE="$ACTIVE_ENGINE" BRIDGE_ENABLED_ENGINES="$(IFS=,; printf '%s' "${SELECTED_ENGINES[*]}")" "$SCRIPT_DIR/setup-secure-dev.sh"
1691
+ BRIDGE_NETWORK_MODE="$NETWORK_MODE" BRIDGE_HOST_OVERRIDE="$BRIDGE_HOST" BRIDGE_ACTIVE_ENGINE="$ACTIVE_ENGINE" BRIDGE_ENABLED_ENGINES="$(selected_engines_csv)" "$SCRIPT_DIR/setup-secure-dev.sh"
1656
1692
  else
1657
1693
  ok "Keeping existing secure config."
1658
1694
  NETWORK_MODE="$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_NETWORK_MODE")"
@@ -1677,10 +1713,10 @@ else
1677
1713
  fi
1678
1714
 
1679
1715
  section "Write secure config"
1680
- BRIDGE_NETWORK_MODE="$NETWORK_MODE" BRIDGE_HOST_OVERRIDE="$BRIDGE_HOST" BRIDGE_ACTIVE_ENGINE="$ACTIVE_ENGINE" BRIDGE_ENABLED_ENGINES="$(IFS=,; printf '%s' "${SELECTED_ENGINES[*]}")" "$SCRIPT_DIR/setup-secure-dev.sh"
1681
- elif [[ "$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_ENABLED_ENGINES")" != "$(IFS=,; printf '%s' "${SELECTED_ENGINES[*]}")" ]]; then
1716
+ BRIDGE_NETWORK_MODE="$NETWORK_MODE" BRIDGE_HOST_OVERRIDE="$BRIDGE_HOST" BRIDGE_ACTIVE_ENGINE="$ACTIVE_ENGINE" BRIDGE_ENABLED_ENGINES="$(selected_engines_csv)" "$SCRIPT_DIR/setup-secure-dev.sh"
1717
+ elif [[ "$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_ENABLED_ENGINES")" != "$(selected_engines_csv)" ]]; then
1682
1718
  section "Write secure config"
1683
- BRIDGE_NETWORK_MODE="$NETWORK_MODE" BRIDGE_HOST_OVERRIDE="$BRIDGE_HOST" BRIDGE_ACTIVE_ENGINE="$ACTIVE_ENGINE" BRIDGE_ENABLED_ENGINES="$(IFS=,; printf '%s' "${SELECTED_ENGINES[*]}")" "$SCRIPT_DIR/setup-secure-dev.sh"
1719
+ BRIDGE_NETWORK_MODE="$NETWORK_MODE" BRIDGE_HOST_OVERRIDE="$BRIDGE_HOST" BRIDGE_ACTIVE_ENGINE="$ACTIVE_ENGINE" BRIDGE_ENABLED_ENGINES="$(selected_engines_csv)" "$SCRIPT_DIR/setup-secure-dev.sh"
1684
1720
  fi
1685
1721
  fi
1686
1722
 
@@ -1711,7 +1747,7 @@ BRIDGE_PORT="${BRIDGE_PORT:-8787}"
1711
1747
  section "Summary"
1712
1748
  rail_echo "Bridge mode: $NETWORK_MODE"
1713
1749
  rail_echo "Bridge endpoint: http://$BRIDGE_HOST:$BRIDGE_PORT"
1714
- rail_echo "Harnesses: $(format_engine_list "${SELECTED_ENGINES[@]}")"
1750
+ rail_echo "Harnesses: $(format_selected_engines)"
1715
1751
  rail_echo "Secure env: $SECURE_ENV_FILE"
1716
1752
  if [[ "$FLOW" == "quickstart" ]]; then
1717
1753
  rail_echo "${DIM}Tip: re-run with Manual mode for full control at each step.${RESET}"
@@ -17,6 +17,10 @@ const {
17
17
 
18
18
  const DEFAULT_HEALTH_TIMEOUT_MS = 15000;
19
19
  const DEV_HEALTH_TIMEOUT_MS = 60000;
20
+ let qrcodeTerminal = null;
21
+ let qrcodeTerminalLoaded = false;
22
+ let qrcodeTerminalLoadError = null;
23
+ let pairingQrRenderError = null;
20
24
 
21
25
  function resolveRootDir() {
22
26
  let rootDir = process.env.INIT_CWD ? path.resolve(process.env.INIT_CWD) : path.resolve(__dirname, "..");
@@ -75,88 +79,144 @@ function buildBridgeUrl(host, port) {
75
79
  return `http://${formatHostForUrl(host)}:${port}`;
76
80
  }
77
81
 
78
- function shouldShowPairingQr(env) {
79
- const raw = readNonEmptyEnv(env, "BRIDGE_SHOW_PAIRING_QR");
80
- return raw ? raw.toLowerCase() !== "false" : true;
82
+ function isUnspecifiedBindHost(host) {
83
+ const normalized = String(host || "").trim().toLowerCase();
84
+ return normalized === "0.0.0.0" || normalized === "::" || normalized === "[::]";
81
85
  }
82
86
 
83
- function printBridgeAccessDetails(env, endpoint) {
84
- const bridgeUrl = buildBridgeUrl(endpoint.host, endpoint.port);
85
- console.log(`Bridge URL: ${bridgeUrl}`);
86
-
87
+ function buildPairingPayload(env, endpoint) {
87
88
  const token = readNonEmptyEnv(env, "BRIDGE_AUTH_TOKEN");
88
- if (token) {
89
- console.log(`Bridge token: ${token}`);
89
+ if (!token || isUnspecifiedBindHost(endpoint.host)) {
90
+ return null;
90
91
  }
91
92
 
92
- return bridgeUrl;
93
+ return JSON.stringify({
94
+ type: "clawdex-bridge-pair",
95
+ bridgeUrl: buildBridgeUrl(endpoint.host, endpoint.port),
96
+ bridgeToken: token,
97
+ });
93
98
  }
94
99
 
95
- function bridgePidFile(rootDir) {
96
- return path.join(rootDir, ".bridge.pid");
97
- }
100
+ function buildTokenOnlyPairingPayload(env) {
101
+ const token = readNonEmptyEnv(env, "BRIDGE_AUTH_TOKEN");
102
+ if (!token) {
103
+ return null;
104
+ }
98
105
 
99
- function bridgeLogFile(rootDir) {
100
- return path.join(rootDir, ".bridge.log");
106
+ return JSON.stringify({
107
+ type: "clawdex-bridge-token",
108
+ bridgeToken: token,
109
+ });
101
110
  }
102
111
 
103
- function extractLatestPairingQrBlock(logContents) {
104
- const lines = logContents.split(/\r?\n/);
105
- for (let index = lines.length - 1; index >= 0; index -= 1) {
106
- const line = lines[index];
107
- if (line.includes("Bridge pairing QR (scan from mobile onboarding):")) {
108
- const endIndex = lines.findIndex(
109
- (entry, offset) =>
110
- offset > index && entry.includes("QR contains bridge URL + token for one-tap onboarding.")
111
- );
112
- if (endIndex !== -1) {
113
- return lines.slice(index, endIndex + 1).join("\n").trimEnd();
114
- }
115
- }
112
+ function loadQrcodeTerminal() {
113
+ if (qrcodeTerminalLoaded) {
114
+ return qrcodeTerminal;
115
+ }
116
116
 
117
- if (line.includes("Bridge token QR fallback (scan from mobile onboarding):")) {
118
- const endIndex = lines.findIndex(
119
- (entry, offset) =>
120
- offset > index &&
121
- entry.includes("Full pairing QR unavailable because BRIDGE_HOST=")
122
- );
123
- if (endIndex !== -1) {
124
- return lines.slice(index, endIndex + 1).join("\n").trimEnd();
125
- }
126
- }
117
+ qrcodeTerminalLoaded = true;
118
+ try {
119
+ qrcodeTerminal = require("qrcode-terminal");
120
+ } catch (error) {
121
+ qrcodeTerminal = null;
122
+ qrcodeTerminalLoadError = error;
127
123
  }
128
124
 
129
- return null;
125
+ return qrcodeTerminal;
130
126
  }
131
127
 
132
- function printLatestPairingQr(logPath, startOffset = 0) {
128
+ function printPairingQr(env, endpoint) {
129
+ pairingQrRenderError = null;
130
+ const qr = loadQrcodeTerminal();
131
+ if (!qr) {
132
+ return false;
133
+ }
134
+
133
135
  try {
134
- const raw = fs.readFileSync(logPath, "utf8");
135
- const contents = startOffset > 0 ? raw.slice(startOffset) : raw;
136
- const qrBlock = extractLatestPairingQrBlock(contents);
137
- if (!qrBlock) {
136
+ const payload = buildPairingPayload(env, endpoint);
137
+ if (payload) {
138
+ console.log("");
139
+ console.log("Bridge pairing QR (scan from mobile onboarding):");
140
+ qr.generate(payload, { small: true });
141
+ console.log("QR contains bridge URL + token for one-tap onboarding.");
142
+ console.log("");
143
+ return true;
144
+ }
145
+
146
+ const tokenPayload = buildTokenOnlyPairingPayload(env);
147
+ if (!tokenPayload) {
138
148
  return false;
139
149
  }
150
+
140
151
  console.log("");
141
- console.log(qrBlock);
152
+ console.log("Bridge token QR fallback (scan from mobile onboarding):");
153
+ qr.generate(tokenPayload, { small: true });
154
+ console.log(
155
+ `Full pairing QR unavailable because BRIDGE_HOST=${endpoint.host} is a bind address. Enter URL manually in onboarding.`
156
+ );
142
157
  console.log("");
143
158
  return true;
144
- } catch {
159
+ } catch (error) {
160
+ pairingQrRenderError = error;
145
161
  return false;
146
162
  }
147
163
  }
148
164
 
149
- async function waitForLatestPairingQr(logPath, startOffset, timeoutMs = 4000) {
150
- const startedAt = Date.now();
165
+ function printPairingQrUnavailableMessage(env) {
166
+ const token = readNonEmptyEnv(env, "BRIDGE_AUTH_TOKEN");
167
+ if (!token) {
168
+ console.log(
169
+ "Pairing QR unavailable because BRIDGE_AUTH_TOKEN is not set. Bridge URL is above for manual onboarding."
170
+ );
171
+ return;
172
+ }
151
173
 
152
- while (Date.now() - startedAt < timeoutMs) {
153
- if (printLatestPairingQr(logPath, startOffset)) {
154
- return true;
155
- }
156
- await sleep(250);
174
+ if (pairingQrRenderError) {
175
+ console.log(
176
+ `Pairing QR unavailable because terminal rendering failed: ${pairingQrRenderError.message}. Bridge URL/token are above for manual onboarding.`
177
+ );
178
+ return;
179
+ }
180
+
181
+ if (qrcodeTerminalLoaded && !qrcodeTerminal) {
182
+ const detail =
183
+ qrcodeTerminalLoadError && qrcodeTerminalLoadError.message
184
+ ? ` (${qrcodeTerminalLoadError.message})`
185
+ : "";
186
+ console.log(
187
+ `Pairing QR unavailable because the terminal QR renderer could not be loaded${detail}. Bridge URL/token are above for manual onboarding.`
188
+ );
189
+ return;
190
+ }
191
+
192
+ console.log(
193
+ "Pairing QR unavailable due to an unexpected startup condition. Bridge URL/token are above for manual onboarding."
194
+ );
195
+ }
196
+
197
+ function shouldShowPairingQr(env) {
198
+ const raw = readNonEmptyEnv(env, "BRIDGE_SHOW_PAIRING_QR");
199
+ return raw ? raw.toLowerCase() !== "false" : true;
200
+ }
201
+
202
+ function printBridgeAccessDetails(env, endpoint) {
203
+ const bridgeUrl = buildBridgeUrl(endpoint.host, endpoint.port);
204
+ console.log(`Bridge URL: ${bridgeUrl}`);
205
+
206
+ const token = readNonEmptyEnv(env, "BRIDGE_AUTH_TOKEN");
207
+ if (token) {
208
+ console.log(`Bridge token: ${token}`);
157
209
  }
158
210
 
159
- return false;
211
+ return bridgeUrl;
212
+ }
213
+
214
+ function bridgePidFile(rootDir) {
215
+ return path.join(rootDir, ".bridge.pid");
216
+ }
217
+
218
+ function bridgeLogFile(rootDir) {
219
+ return path.join(rootDir, ".bridge.log");
160
220
  }
161
221
 
162
222
  function readPidFile(rootDir) {
@@ -344,9 +404,10 @@ async function spawnDetachedAndWait(command, args, options) {
344
404
  console.log(`Bridge already running (pid ${existingPid}).`);
345
405
  console.log(`Logs: ${logPath}`);
346
406
  console.log("Bridge is healthy.");
347
- printBridgeAccessDetails(env, { host, port });
348
- if (shouldShowPairingQr(env)) {
349
- printLatestPairingQr(logPath);
407
+ const endpoint = { host, port };
408
+ printBridgeAccessDetails(env, endpoint);
409
+ if (shouldShowPairingQr(env) && !printPairingQr(env, endpoint)) {
410
+ printPairingQrUnavailableMessage(env);
350
411
  }
351
412
  return;
352
413
  }
@@ -363,7 +424,6 @@ async function spawnDetachedAndWait(command, args, options) {
363
424
 
364
425
  const output = fs.openSync(logPath, "a");
365
426
  const error = fs.openSync(logPath, "a");
366
- const logStartOffset = fs.existsSync(logPath) ? fs.statSync(logPath).size : 0;
367
427
 
368
428
  const child = spawn(command, args, {
369
429
  cwd,
@@ -394,10 +454,8 @@ async function spawnDetachedAndWait(command, args, options) {
394
454
  console.log("Bridge is healthy.");
395
455
  printBridgeAccessDetails(env, endpoint);
396
456
 
397
- if (shouldShowPairingQr(env) && !(await waitForLatestPairingQr(logPath, logStartOffset, 8000))) {
398
- console.log(
399
- "Pairing QR not found in the new bridge startup log. Bridge URL/token are above. Open logs if you need to inspect startup output."
400
- );
457
+ if (shouldShowPairingQr(env) && !printPairingQr(env, endpoint)) {
458
+ printPairingQrUnavailableMessage(env);
401
459
  }
402
460
  } catch (error) {
403
461
  removePidFile(rootDir);
@@ -73,6 +73,23 @@ extract_env_value() {
73
73
  ' "$file"
74
74
  }
75
75
 
76
+ stop_launchctl_job() {
77
+ local label="$1"
78
+
79
+ if [[ -z "$label" ]] || ! command -v launchctl >/dev/null 2>&1; then
80
+ return 1
81
+ fi
82
+
83
+ local domain="gui/$(id -u)/$label"
84
+ if ! launchctl print "$domain" >/dev/null 2>&1; then
85
+ return 1
86
+ fi
87
+
88
+ echo "Stopping launchd job: $label"
89
+ launchctl bootout "$domain" >/dev/null 2>&1 || launchctl remove "$label" >/dev/null 2>&1 || true
90
+ return 0
91
+ }
92
+
76
93
  stop_process_group() {
77
94
  local label="$1"
78
95
  local pattern="$2"
@@ -111,7 +128,12 @@ stop_process_group() {
111
128
 
112
129
  echo "Stopping Clawdex services for project: $ROOT_DIR"
113
130
 
131
+ BRIDGE_PORT="${BRIDGE_PORT:-$(extract_env_value "$SECURE_ENV_FILE" "BRIDGE_PORT" || true)}"
132
+ BRIDGE_PORT="${BRIDGE_PORT:-8787}"
133
+
114
134
  stop_process_group "Expo" "$ROOT_DIR/.*/expo start|$ROOT_DIR/node_modules/.bin/expo start"
135
+ stop_launchctl_job "clawdex.bridge.$BRIDGE_PORT" || true
136
+ stop_process_group "Bridge launcher" "$ROOT_DIR/scripts/start-bridge-secure\\.js|node .*start-bridge-secure\\.js"
115
137
  stop_pid_file_process "Rust bridge" "$BRIDGE_PID_FILE" || true
116
138
  stop_process_group "Rust bridge" "$ROOT_DIR/services/rust-bridge|codex-rust-bridge|@codex/rust-bridge"
117
139
  stop_process_group "Legacy TS bridge" "$ROOT_DIR/services/mac-bridge|@codex/mac-bridge"
@@ -149,7 +149,7 @@ dependencies = [
149
149
 
150
150
  [[package]]
151
151
  name = "codex-rust-bridge"
152
- version = "5.0.7"
152
+ version = "5.0.8"
153
153
  dependencies = [
154
154
  "axum",
155
155
  "base64",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "codex-rust-bridge"
3
- version = "5.0.7"
3
+ version = "5.0.8"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]