clawdex-mobile 5.0.6 → 5.0.7
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/README.md +1 -1
- package/docs/setup-and-operations.md +1 -1
- package/package.json +1 -1
- package/scripts/start-bridge-secure.js +38 -5
- package/services/rust-bridge/Cargo.lock +1 -1
- package/services/rust-bridge/Cargo.toml +1 -1
- package/services/rust-bridge/src/main.rs +76 -1
- 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/README.md
CHANGED
|
@@ -38,7 +38,7 @@ npm install -g clawdex-mobile@latest
|
|
|
38
38
|
clawdex init
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
Then open the mobile app and connect using the printed bridge URL/token.
|
|
41
|
+
Then open the mobile app and connect using the printed bridge URL/token or pairing QR.
|
|
42
42
|
`clawdex init` now writes config, starts the bridge in the background, and returns you to the shell. Bridge logs go to `.bridge.log`.
|
|
43
43
|
|
|
44
44
|
The npm package is bridge-only. It does not install Expo or the mobile source tree. On supported macOS, Linux, and Windows hosts it uses bundled bridge binaries, so normal startup does not compile Rust.
|
|
@@ -28,7 +28,7 @@ After `clawdex init`, expected sequence:
|
|
|
28
28
|
|
|
29
29
|
1. Secure config is written or reused
|
|
30
30
|
2. The bridge starts in the background
|
|
31
|
-
3. The wizard prints the bridge URL
|
|
31
|
+
3. The wizard prints the bridge URL, token, and pairing QR for mobile onboarding
|
|
32
32
|
4. Bridge logs are written to `.bridge.log`
|
|
33
33
|
|
|
34
34
|
Published npm releases bundle prebuilt bridge binaries for `darwin-arm64`, `darwin-x64`, `linux-x64`, `linux-arm64`, `linux-armv7l`, and `win32-x64`. On those hosts, normal bridge startup does not require a Rust compile.
|
package/package.json
CHANGED
|
@@ -59,6 +59,11 @@ function sleep(ms) {
|
|
|
59
59
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
function readNonEmptyEnv(env, key) {
|
|
63
|
+
const value = env[key];
|
|
64
|
+
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
65
|
+
}
|
|
66
|
+
|
|
62
67
|
function formatHostForUrl(host) {
|
|
63
68
|
if (host.includes(":") && !host.startsWith("[")) {
|
|
64
69
|
return `[${host}]`;
|
|
@@ -66,6 +71,27 @@ function formatHostForUrl(host) {
|
|
|
66
71
|
return host;
|
|
67
72
|
}
|
|
68
73
|
|
|
74
|
+
function buildBridgeUrl(host, port) {
|
|
75
|
+
return `http://${formatHostForUrl(host)}:${port}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function shouldShowPairingQr(env) {
|
|
79
|
+
const raw = readNonEmptyEnv(env, "BRIDGE_SHOW_PAIRING_QR");
|
|
80
|
+
return raw ? raw.toLowerCase() !== "false" : true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function printBridgeAccessDetails(env, endpoint) {
|
|
84
|
+
const bridgeUrl = buildBridgeUrl(endpoint.host, endpoint.port);
|
|
85
|
+
console.log(`Bridge URL: ${bridgeUrl}`);
|
|
86
|
+
|
|
87
|
+
const token = readNonEmptyEnv(env, "BRIDGE_AUTH_TOKEN");
|
|
88
|
+
if (token) {
|
|
89
|
+
console.log(`Bridge token: ${token}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return bridgeUrl;
|
|
93
|
+
}
|
|
94
|
+
|
|
69
95
|
function bridgePidFile(rootDir) {
|
|
70
96
|
return path.join(rootDir, ".bridge.pid");
|
|
71
97
|
}
|
|
@@ -317,8 +343,11 @@ async function spawnDetachedAndWait(command, args, options) {
|
|
|
317
343
|
if (await probeHealth(healthUrl)) {
|
|
318
344
|
console.log(`Bridge already running (pid ${existingPid}).`);
|
|
319
345
|
console.log(`Logs: ${logPath}`);
|
|
320
|
-
console.log(
|
|
321
|
-
|
|
346
|
+
console.log("Bridge is healthy.");
|
|
347
|
+
printBridgeAccessDetails(env, { host, port });
|
|
348
|
+
if (shouldShowPairingQr(env)) {
|
|
349
|
+
printLatestPairingQr(logPath);
|
|
350
|
+
}
|
|
322
351
|
return;
|
|
323
352
|
}
|
|
324
353
|
} else if (existingPid) {
|
|
@@ -362,9 +391,13 @@ async function spawnDetachedAndWait(command, args, options) {
|
|
|
362
391
|
|
|
363
392
|
try {
|
|
364
393
|
const endpoint = await waitForHealth(env, child.pid, healthTimeoutMs);
|
|
365
|
-
console.log(
|
|
366
|
-
|
|
367
|
-
|
|
394
|
+
console.log("Bridge is healthy.");
|
|
395
|
+
printBridgeAccessDetails(env, endpoint);
|
|
396
|
+
|
|
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
|
+
);
|
|
368
401
|
}
|
|
369
402
|
} catch (error) {
|
|
370
403
|
removePidFile(rootDir);
|
|
@@ -2,7 +2,7 @@ use std::{
|
|
|
2
2
|
collections::{HashMap, HashSet, VecDeque},
|
|
3
3
|
env,
|
|
4
4
|
hash::{Hash, Hasher},
|
|
5
|
-
io::SeekFrom,
|
|
5
|
+
io::{SeekFrom, Write},
|
|
6
6
|
path::{Component, Path, PathBuf},
|
|
7
7
|
process::Stdio,
|
|
8
8
|
sync::{
|
|
@@ -5404,6 +5404,11 @@ fn build_token_only_pairing_payload(config: &BridgeConfig) -> Option<String> {
|
|
|
5404
5404
|
)
|
|
5405
5405
|
}
|
|
5406
5406
|
|
|
5407
|
+
fn flush_pairing_output() {
|
|
5408
|
+
let _ = std::io::stdout().flush();
|
|
5409
|
+
let _ = std::io::stderr().flush();
|
|
5410
|
+
}
|
|
5411
|
+
|
|
5407
5412
|
fn maybe_print_pairing_qr(config: &BridgeConfig) {
|
|
5408
5413
|
if !config.show_pairing_qr {
|
|
5409
5414
|
return;
|
|
@@ -5414,15 +5419,18 @@ fn maybe_print_pairing_qr(config: &BridgeConfig) {
|
|
|
5414
5419
|
println!("Bridge pairing QR (scan from mobile onboarding):");
|
|
5415
5420
|
if let Err(error) = qr2term::print_qr(payload.as_bytes()) {
|
|
5416
5421
|
eprintln!("failed to render pairing QR: {error}");
|
|
5422
|
+
flush_pairing_output();
|
|
5417
5423
|
return;
|
|
5418
5424
|
}
|
|
5419
5425
|
println!("QR contains bridge URL + token for one-tap onboarding.");
|
|
5420
5426
|
println!();
|
|
5427
|
+
flush_pairing_output();
|
|
5421
5428
|
return;
|
|
5422
5429
|
}
|
|
5423
5430
|
|
|
5424
5431
|
let Some(payload) = build_token_only_pairing_payload(config) else {
|
|
5425
5432
|
eprintln!("bridge token QR skipped because BRIDGE_AUTH_TOKEN is not set");
|
|
5433
|
+
flush_pairing_output();
|
|
5426
5434
|
return;
|
|
5427
5435
|
};
|
|
5428
5436
|
|
|
@@ -5430,6 +5438,7 @@ fn maybe_print_pairing_qr(config: &BridgeConfig) {
|
|
|
5430
5438
|
println!("Bridge token QR fallback (scan from mobile onboarding):");
|
|
5431
5439
|
if let Err(error) = qr2term::print_qr(payload.as_bytes()) {
|
|
5432
5440
|
eprintln!("failed to render pairing QR: {error}");
|
|
5441
|
+
flush_pairing_output();
|
|
5433
5442
|
return;
|
|
5434
5443
|
}
|
|
5435
5444
|
println!(
|
|
@@ -5437,6 +5446,7 @@ fn maybe_print_pairing_qr(config: &BridgeConfig) {
|
|
|
5437
5446
|
config.host
|
|
5438
5447
|
);
|
|
5439
5448
|
println!();
|
|
5449
|
+
flush_pairing_output();
|
|
5440
5450
|
}
|
|
5441
5451
|
|
|
5442
5452
|
fn parse_bool_env(name: &str) -> bool {
|
|
@@ -9009,6 +9019,71 @@ mod tests {
|
|
|
9009
9019
|
assert!(!constant_time_eq("secret-token", "secret-token-extra"));
|
|
9010
9020
|
}
|
|
9011
9021
|
|
|
9022
|
+
#[test]
|
|
9023
|
+
fn build_pairing_payload_includes_url_and_token_for_connectable_host() {
|
|
9024
|
+
let config = BridgeConfig {
|
|
9025
|
+
host: "127.0.0.1".to_string(),
|
|
9026
|
+
port: 8787,
|
|
9027
|
+
workdir: PathBuf::from("/tmp/workdir"),
|
|
9028
|
+
cli_bin: "codex".to_string(),
|
|
9029
|
+
opencode_cli_bin: "opencode".to_string(),
|
|
9030
|
+
active_engine: BridgeRuntimeEngine::Codex,
|
|
9031
|
+
enabled_engines: vec![BridgeRuntimeEngine::Codex],
|
|
9032
|
+
opencode_host: "127.0.0.1".to_string(),
|
|
9033
|
+
opencode_port: 4090,
|
|
9034
|
+
opencode_server_username: "opencode".to_string(),
|
|
9035
|
+
opencode_server_password: Some("secret-token".to_string()),
|
|
9036
|
+
auth_token: Some("secret-token".to_string()),
|
|
9037
|
+
auth_enabled: true,
|
|
9038
|
+
allow_insecure_no_auth: false,
|
|
9039
|
+
allow_query_token_auth: false,
|
|
9040
|
+
allow_outside_root_cwd: false,
|
|
9041
|
+
disable_terminal_exec: false,
|
|
9042
|
+
terminal_allowed_commands: HashSet::new(),
|
|
9043
|
+
show_pairing_qr: true,
|
|
9044
|
+
};
|
|
9045
|
+
|
|
9046
|
+
let payload = build_pairing_payload(&config).expect("pairing payload");
|
|
9047
|
+
let parsed: Value = serde_json::from_str(&payload).expect("valid json");
|
|
9048
|
+
|
|
9049
|
+
assert_eq!(parsed["type"], "clawdex-bridge-pair");
|
|
9050
|
+
assert_eq!(parsed["bridgeUrl"], "http://127.0.0.1:8787");
|
|
9051
|
+
assert_eq!(parsed["bridgeToken"], "secret-token");
|
|
9052
|
+
}
|
|
9053
|
+
|
|
9054
|
+
#[test]
|
|
9055
|
+
fn build_pairing_payload_uses_token_only_fallback_for_unspecified_bind_host() {
|
|
9056
|
+
let config = BridgeConfig {
|
|
9057
|
+
host: "0.0.0.0".to_string(),
|
|
9058
|
+
port: 8787,
|
|
9059
|
+
workdir: PathBuf::from("/tmp/workdir"),
|
|
9060
|
+
cli_bin: "codex".to_string(),
|
|
9061
|
+
opencode_cli_bin: "opencode".to_string(),
|
|
9062
|
+
active_engine: BridgeRuntimeEngine::Codex,
|
|
9063
|
+
enabled_engines: vec![BridgeRuntimeEngine::Codex],
|
|
9064
|
+
opencode_host: "127.0.0.1".to_string(),
|
|
9065
|
+
opencode_port: 4090,
|
|
9066
|
+
opencode_server_username: "opencode".to_string(),
|
|
9067
|
+
opencode_server_password: Some("secret-token".to_string()),
|
|
9068
|
+
auth_token: Some("secret-token".to_string()),
|
|
9069
|
+
auth_enabled: true,
|
|
9070
|
+
allow_insecure_no_auth: false,
|
|
9071
|
+
allow_query_token_auth: false,
|
|
9072
|
+
allow_outside_root_cwd: false,
|
|
9073
|
+
disable_terminal_exec: false,
|
|
9074
|
+
terminal_allowed_commands: HashSet::new(),
|
|
9075
|
+
show_pairing_qr: true,
|
|
9076
|
+
};
|
|
9077
|
+
|
|
9078
|
+
assert!(build_pairing_payload(&config).is_none());
|
|
9079
|
+
|
|
9080
|
+
let fallback = build_token_only_pairing_payload(&config).expect("token-only payload");
|
|
9081
|
+
let parsed: Value = serde_json::from_str(&fallback).expect("valid json");
|
|
9082
|
+
|
|
9083
|
+
assert_eq!(parsed["type"], "clawdex-bridge-token");
|
|
9084
|
+
assert_eq!(parsed["bridgeToken"], "secret-token");
|
|
9085
|
+
}
|
|
9086
|
+
|
|
9012
9087
|
#[test]
|
|
9013
9088
|
fn bridge_config_authorization_validates_header_and_query_token_paths() {
|
|
9014
9089
|
let base = BridgeConfig {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|