clawdex-mobile 5.1.3-internal.1 → 5.1.3-internal.2

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 CHANGED
@@ -70,7 +70,7 @@ Notes:
70
70
  - Browser preview uses a second forwarded port (`8788` by default), so both ports need public visibility.
71
71
  - GitHub resets public forwarded ports back to private when a codespace restarts. Restarting the bridge reruns the visibility step.
72
72
  - If automatic visibility setup fails, run `gh codespace ports visibility 8787:public 8788:public`.
73
- - If the mobile app is built with `EXPO_PUBLIC_GITHUB_CLIENT_ID`, users can now tap `Use GitHub Codespaces` in onboarding/settings, sign in with GitHub, pick a Codespace, and connect without manually copying the bridge token.
73
+ - If the mobile app is built with `EXPO_PUBLIC_GITHUB_APP_CLIENT_ID` and `EXPO_PUBLIC_GITHUB_APP_SLUG`, users can now tap `Use GitHub Codespaces` in onboarding/settings, sign in with GitHub, approve the Claudex GitHub App for only the repositories they want, pick a Codespace, and connect without manually copying the bridge token.
74
74
  - The app can also create a new repo-backed Codespace directly. It prefers `<signed-in-user>/<EXPO_PUBLIC_GITHUB_CODESPACES_REPO_NAME>` first. If that repo does not exist, it automatically forks `EXPO_PUBLIC_GITHUB_CODESPACES_SOURCE_OWNER/<EXPO_PUBLIC_GITHUB_CODESPACES_REPO_NAME>` into the signed-in user account, then creates the Codespace there.
75
75
 
76
76
  This repo now also includes a Codespaces bootstrap flow. On Codespace start/resume, `.devcontainer/devcontainer.json` runs:
@@ -89,7 +89,7 @@ That pre-installs Codex and prebuilds the Rust bridge binary so the later startu
89
89
 
90
90
  The published npm package now includes that bootstrap script too, so a minimal Codespaces template repo can install `clawdex-mobile@latest` in `postCreateCommand` and call the packaged bootstrap without vendoring bridge source into the template itself.
91
91
 
92
- In Codespaces mode, the bootstrap also enables bridge-side GitHub bearer auth for the current `CODESPACE_NAME`, so the mobile app can authenticate with the same GitHub OAuth token it used to discover and start the Codespace.
92
+ In Codespaces mode, the bootstrap also enables bridge-side GitHub bearer auth for the current `CODESPACE_NAME`, so the mobile app can authenticate with the same GitHub App user token it used to discover and start the Codespace.
93
93
 
94
94
  ## OpenCode Setup
95
95
 
@@ -63,10 +63,10 @@ Important constraints:
63
63
  - Browser preview uses the preview port (`8788` by default), so that forwarded port must also be public
64
64
  - GitHub resets public forwarded ports back to private whenever the codespace restarts
65
65
  - Keep bridge auth enabled and use Codespaces only for repos you trust, because public forwarded ports are internet-reachable
66
- - If the mobile app build sets `EXPO_PUBLIC_GITHUB_CLIENT_ID`, onboarding/settings can now sign in with GitHub, start the Codespace, and connect directly with the same OAuth token instead of copying `BRIDGE_AUTH_TOKEN`
67
- - That same in-app GitHub sign-in now also bootstraps GitHub git auth inside the Codespace so `git clone`, `git push`, GitHub HTTPS remotes, and common `git@github.com:...` SSH-style remotes can reuse the app login without extra account setup
66
+ - If the mobile app build sets `EXPO_PUBLIC_GITHUB_APP_CLIENT_ID` and `EXPO_PUBLIC_GITHUB_APP_SLUG`, onboarding/settings can now sign in with GitHub, approve the Claudex GitHub App for only the repositories they want, start the Codespace, and connect directly with the same GitHub App user token instead of copying `BRIDGE_AUTH_TOKEN`
67
+ - That same in-app GitHub sign-in also bootstraps GitHub git auth inside the Codespace so `git clone`, `git push`, GitHub HTTPS remotes, and common `git@github.com:...` SSH-style remotes can reuse the app login without extra account setup
68
68
  - The same in-app GitHub flow can create a new Codespace. It prefers `<signed-in-user>/<EXPO_PUBLIC_GITHUB_CODESPACES_REPO_NAME>`. If that repo does not exist yet, Clawdex automatically forks `EXPO_PUBLIC_GITHUB_CODESPACES_SOURCE_OWNER/<EXPO_PUBLIC_GITHUB_CODESPACES_REPO_NAME>` into the signed-in user account and creates the Codespace from that fork
69
- - Older saved GitHub Codespaces sessions may need one fresh sign-in from the app so the stored GitHub token includes repository access
69
+ - Older saved GitHub Codespaces sessions may need one fresh sign-in from the app so the stored GitHub App token and refresh token are updated
70
70
 
71
71
  Manual recovery if port visibility does not update automatically:
72
72
 
@@ -271,7 +271,8 @@ npm run teardown -- --yes
271
271
  | Variable | Purpose |
272
272
  |---|---|
273
273
  | `EXPO_PUBLIC_HOST_BRIDGE_TOKEN` | token used by local mobile dev builds |
274
- | `EXPO_PUBLIC_GITHUB_CLIENT_ID` | GitHub OAuth app client ID for in-app Codespaces sign-in |
274
+ | `EXPO_PUBLIC_GITHUB_APP_CLIENT_ID` | GitHub App client ID for in-app Codespaces sign-in |
275
+ | `EXPO_PUBLIC_GITHUB_APP_SLUG` | GitHub App slug used to open install/manage-access pages for repository selection |
275
276
  | `EXPO_PUBLIC_GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN` | forwarded port domain used to derive Codespaces bridge URLs (`app.github.dev` by default) |
276
277
  | `EXPO_PUBLIC_GITHUB_CODESPACES_REPO_NAME` | repository name to sort matching Codespaces first in the in-app picker |
277
278
  | `EXPO_PUBLIC_GITHUB_CODESPACES_SOURCE_OWNER` | template/source repository owner used for automatic forking when the signed-in user does not have a same-name repo |
@@ -34,7 +34,7 @@ npm run stop:services
34
34
  ## Bridge auth errors (`401`, invalid token)
35
35
 
36
36
  - For the shipped mobile app, rescan the bridge QR or update the stored token in Settings.
37
- - For GitHub-auth Codespaces profiles, reopen `GitHub Codespaces` in the app and sign in with GitHub again if the OAuth token was revoked or expired.
37
+ - For GitHub-auth Codespaces profiles, reopen `GitHub Codespaces` in the app and sign in with GitHub again if the GitHub App token or refresh token was revoked or expired.
38
38
  - For a local dev build, also ensure `BRIDGE_AUTH_TOKEN` in `.env.secure` matches `EXPO_PUBLIC_HOST_BRIDGE_TOKEN` in `apps/mobile/.env`.
39
39
  - Restart the bridge after token changes.
40
40
  - On secure-launcher installs, `Settings > Bridge Maintenance > Restart bridge safely` can do that from the phone.
@@ -53,7 +53,8 @@ gh codespace ports visibility 8787:public 8788:public
53
53
 
54
54
  - If `gh` is unavailable in the codespace, use the Codespaces `Ports` panel and change both forwarded ports to `Public`.
55
55
  - Keep bridge auth enabled. Public forwarded ports without bridge auth are not a safe setup.
56
- - If GitHub direct sign-in is not showing in the app, confirm the build includes `EXPO_PUBLIC_GITHUB_CLIENT_ID`.
56
+ - If GitHub direct sign-in is not showing in the app, confirm the build includes `EXPO_PUBLIC_GITHUB_APP_CLIENT_ID` and `EXPO_PUBLIC_GITHUB_APP_SLUG`.
57
+ - If the app signs in but still cannot create a Codespace or clone/push inside it, reopen the GitHub App access step in the app and make sure the template repo and any target repos are selected for the installation.
57
58
  - If in-app Codespace creation forks or targets the wrong repo, check `EXPO_PUBLIC_GITHUB_CODESPACES_REPO_NAME`, `EXPO_PUBLIC_GITHUB_CODESPACES_SOURCE_OWNER`, and `EXPO_PUBLIC_GITHUB_CODESPACES_REPO_REF` in the mobile build env.
58
59
 
59
60
  ## GitHub Codespaces bootstrap did not start the bridge
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawdex-mobile",
3
- "version": "5.1.3-internal.1",
3
+ "version": "5.1.3-internal.2",
4
4
  "description": "Private-network mobile bridge and CLI for Codex and OpenCode",
5
5
  "keywords": [
6
6
  "codex",
@@ -149,7 +149,7 @@ dependencies = [
149
149
 
150
150
  [[package]]
151
151
  name = "codex-rust-bridge"
152
- version = "5.1.3-internal.1"
152
+ version = "5.1.3-internal.2"
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.1.3-internal.1"
3
+ version = "5.1.3-internal.2"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -392,10 +392,10 @@ async fn install_github_git_auth(
392
392
  access_token: &str,
393
393
  ) -> Result<GitHubAuthInstallResponse, BridgeError> {
394
394
  let viewer = fetch_github_viewer(state, access_token).await?;
395
- if !github_scopes_allow_repo_access(&viewer.scopes) {
395
+ if !github_token_can_be_used_for_git_auth(&viewer.scopes) {
396
396
  return Err(BridgeError::forbidden(
397
397
  "github_repo_scope_required",
398
- "GitHub repository access is required. Sign in again from the app and approve repository access.",
398
+ "GitHub repository access is required. Sign in again from the app and approve the required repository access.",
399
399
  ));
400
400
  }
401
401
 
@@ -485,6 +485,10 @@ fn github_scopes_allow_repo_access(scopes: &[String]) -> bool {
485
485
  .any(|scope| scope == "repo" || scope == "public_repo")
486
486
  }
487
487
 
488
+ fn github_token_can_be_used_for_git_auth(scopes: &[String]) -> bool {
489
+ scopes.is_empty() || github_scopes_allow_repo_access(scopes)
490
+ }
491
+
488
492
  fn resolve_github_credentials_file_path() -> Result<PathBuf, BridgeError> {
489
493
  let home = read_non_empty_env("HOME")
490
494
  .ok_or_else(|| BridgeError::server("HOME is not set; cannot install GitHub auth"))?;
@@ -7326,7 +7330,27 @@ fn bridge_chatgpt_auth_cache() -> &'static StdRwLock<Option<BridgeChatGptAuthBun
7326
7330
  CACHE.get_or_init(|| StdRwLock::new(None))
7327
7331
  }
7328
7332
 
7333
+ #[cfg(test)]
7334
+ fn bridge_chatgpt_auth_cache_path_override() -> &'static StdRwLock<Option<PathBuf>> {
7335
+ static OVERRIDE: OnceLock<StdRwLock<Option<PathBuf>>> = OnceLock::new();
7336
+ OVERRIDE.get_or_init(|| StdRwLock::new(None))
7337
+ }
7338
+
7339
+ #[cfg(test)]
7340
+ fn set_bridge_chatgpt_auth_cache_path_override(path: Option<PathBuf>) {
7341
+ if let Ok(mut guard) = bridge_chatgpt_auth_cache_path_override().write() {
7342
+ *guard = path;
7343
+ }
7344
+ }
7345
+
7329
7346
  fn resolve_bridge_chatgpt_auth_cache_path() -> Option<PathBuf> {
7347
+ #[cfg(test)]
7348
+ if let Ok(guard) = bridge_chatgpt_auth_cache_path_override().read() {
7349
+ if let Some(path) = guard.clone() {
7350
+ return Some(path);
7351
+ }
7352
+ }
7353
+
7330
7354
  let workdir = read_non_empty_env("BRIDGE_WORKDIR").map(PathBuf::from)?;
7331
7355
  Some(workdir.join(BRIDGE_CHATGPT_AUTH_CACHE_FILE_NAME))
7332
7356
  }
@@ -11859,6 +11883,51 @@ fn normalize_path(path: &Path) -> PathBuf {
11859
11883
  mod tests {
11860
11884
  use super::*;
11861
11885
 
11886
+ fn bridge_chatgpt_auth_test_lock() -> &'static std::sync::Mutex<()> {
11887
+ static LOCK: OnceLock<std::sync::Mutex<()>> = OnceLock::new();
11888
+ LOCK.get_or_init(|| std::sync::Mutex::new(()))
11889
+ }
11890
+
11891
+ struct TestBridgeChatGptAuthCacheScope {
11892
+ _guard: std::sync::MutexGuard<'static, ()>,
11893
+ temp_dir: PathBuf,
11894
+ }
11895
+
11896
+ impl TestBridgeChatGptAuthCacheScope {
11897
+ fn new() -> Self {
11898
+ let guard = bridge_chatgpt_auth_test_lock()
11899
+ .lock()
11900
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
11901
+ clear_cached_bridge_chatgpt_auth();
11902
+
11903
+ let nonce = SystemTime::now()
11904
+ .duration_since(SystemTime::UNIX_EPOCH)
11905
+ .expect("valid time")
11906
+ .as_nanos();
11907
+ let temp_dir = env::temp_dir().join(format!(
11908
+ "clawdex-bridge-chatgpt-auth-test-{}-{nonce}",
11909
+ std::process::id()
11910
+ ));
11911
+ std::fs::create_dir_all(&temp_dir).expect("create auth cache test dir");
11912
+ set_bridge_chatgpt_auth_cache_path_override(Some(
11913
+ temp_dir.join(BRIDGE_CHATGPT_AUTH_CACHE_FILE_NAME),
11914
+ ));
11915
+
11916
+ Self {
11917
+ _guard: guard,
11918
+ temp_dir,
11919
+ }
11920
+ }
11921
+ }
11922
+
11923
+ impl Drop for TestBridgeChatGptAuthCacheScope {
11924
+ fn drop(&mut self) {
11925
+ clear_cached_bridge_chatgpt_auth();
11926
+ set_bridge_chatgpt_auth_cache_path_override(None);
11927
+ let _ = std::fs::remove_dir_all(&self.temp_dir);
11928
+ }
11929
+ }
11930
+
11862
11931
  async fn build_test_bridge(hub: Arc<ClientHub>) -> Arc<AppServerBridge> {
11863
11932
  let mut child = Command::new("cat")
11864
11933
  .stdin(Stdio::piped())
@@ -13859,6 +13928,7 @@ mod tests {
13859
13928
 
13860
13929
  #[tokio::test]
13861
13930
  async fn successful_chatgpt_auth_token_login_populates_bridge_auth_cache() {
13931
+ let _auth_cache_scope = TestBridgeChatGptAuthCacheScope::new();
13862
13932
  clear_cached_bridge_chatgpt_auth();
13863
13933
 
13864
13934
  let hub = Arc::new(ClientHub::new());
@@ -13906,6 +13976,7 @@ mod tests {
13906
13976
 
13907
13977
  #[tokio::test]
13908
13978
  async fn successful_account_logout_clears_cached_bridge_chatgpt_auth() {
13979
+ let _auth_cache_scope = TestBridgeChatGptAuthCacheScope::new();
13909
13980
  clear_cached_bridge_chatgpt_auth();
13910
13981
  cache_bridge_chatgpt_auth(BridgeChatGptAuthBundle {
13911
13982
  access_token: "cached-before-logout".to_string(),
@@ -14370,4 +14441,14 @@ mod tests {
14370
14441
  "read:user".to_string()
14371
14442
  ]));
14372
14443
  }
14444
+
14445
+ #[test]
14446
+ fn github_git_auth_accepts_github_app_user_tokens_without_scope_headers() {
14447
+ assert!(github_token_can_be_used_for_git_auth(&[]));
14448
+ assert!(github_token_can_be_used_for_git_auth(&["repo".to_string()]));
14449
+ assert!(!github_token_can_be_used_for_git_auth(&[
14450
+ "codespace".to_string(),
14451
+ "read:user".to_string()
14452
+ ]));
14453
+ }
14373
14454
  }