clawdex-mobile 5.1.2 → 5.1.3-internal.1

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
@@ -4,9 +4,9 @@
4
4
  <img src="https://raw.githubusercontent.com/Mohit-Patil/clawdex-mobile/main/screenshots/social/clawdex-social-poster-1200x675.png" alt="Clawdex social banner" width="100%" />
5
5
  </p>
6
6
 
7
- Run Codex or OpenCode from your phone. `clawdex-mobile` ships the bridge CLI plus bundled Rust bridge binaries for supported hosts, and the mobile app pairs to that bridge over Tailscale or local LAN.
7
+ Run Codex or OpenCode from your phone. `clawdex-mobile` ships the bridge CLI plus bundled Rust bridge binaries for supported hosts, and the mobile app pairs to that bridge over Tailscale, local LAN, or GitHub Codespaces forwarded HTTPS URLs.
8
8
 
9
- This project is for trusted/private networking only. Do not expose the bridge publicly.
9
+ This project is for trusted/private networking by default. GitHub Codespaces is supported as an internet-reachable exception: keep bridge auth enabled, use only repos you trust, and remember forwarded public ports reset to private when a codespace restarts.
10
10
 
11
11
  ## What You Get
12
12
 
@@ -52,6 +52,45 @@ clawdex init
52
52
  clawdex stop
53
53
  ```
54
54
 
55
+ ## GitHub Codespaces
56
+
57
+ You can run the bridge inside a GitHub Codespace instead of keeping a laptop or server online.
58
+
59
+ From a repo checkout inside Codespaces:
60
+
61
+ ```bash
62
+ npm run setup:wizard
63
+ ```
64
+
65
+ Pick `GitHub Codespaces` as the network mode. The setup flow writes forwarded HTTPS bridge URLs into `.env.secure`, and bridge startup will try to mark both bridge ports public automatically.
66
+
67
+ Notes:
68
+
69
+ - The mobile app should pair to the printed `https://<codespace>-8787.app.github.dev` URL, not `127.0.0.1`.
70
+ - Browser preview uses a second forwarded port (`8788` by default), so both ports need public visibility.
71
+ - GitHub resets public forwarded ports back to private when a codespace restarts. Restarting the bridge reruns the visibility step.
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.
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
+
76
+ This repo now also includes a Codespaces bootstrap flow. On Codespace start/resume, `.devcontainer/devcontainer.json` runs:
77
+
78
+ ```bash
79
+ npm run codespaces:bootstrap
80
+ ```
81
+
82
+ During initial Codespace creation, the devcontainer also runs:
83
+
84
+ ```bash
85
+ npm run codespaces:bootstrap -- --prepare-only
86
+ ```
87
+
88
+ That pre-installs Codex and prebuilds the Rust bridge binary so the later startup path is faster. The normal bootstrap command rewrites `.env.secure` for Codespaces with `codex` as the only enabled engine and starts the bridge in the background. You can rerun either command manually any time.
89
+
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
+
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.
93
+
55
94
  ## OpenCode Setup
56
95
 
57
96
  OpenCode is supported directly from the CLI now.
package/bin/clawdex.js CHANGED
@@ -34,9 +34,14 @@ Commands:
34
34
  }
35
35
 
36
36
  function runCommand(command, args = [], options = {}) {
37
+ const workspaceRoot = process.env.CLAWDEX_WORKSPACE_ROOT || process.cwd();
37
38
  const child = spawnSync(command, args, {
38
39
  stdio: "inherit",
39
- env: process.env,
40
+ env: {
41
+ ...process.env,
42
+ CLAWDEX_WORKSPACE_ROOT: workspaceRoot,
43
+ INIT_CWD: process.env.INIT_CWD || workspaceRoot,
44
+ },
40
45
  cwd: process.cwd(),
41
46
  ...options,
42
47
  });
@@ -37,6 +37,77 @@ Published npm releases bundle prebuilt bridge binaries for `darwin-arm64`, `darw
37
37
 
38
38
  Published CLI installs are bridge-only. They do not include the Expo workspace or mobile app source files.
39
39
 
40
+ ## GitHub Codespaces Setup
41
+
42
+ Codespaces can replace a user-managed always-on machine for development and lightweight remote use.
43
+
44
+ From a repo checkout inside an active codespace:
45
+
46
+ ```bash
47
+ npm run setup:wizard
48
+ ```
49
+
50
+ Choose `GitHub Codespaces` for the bridge network mode.
51
+
52
+ What that does:
53
+
54
+ - binds the bridge locally inside the codespace
55
+ - writes `BRIDGE_CONNECT_URL` and `BRIDGE_PREVIEW_CONNECT_URL` using the codespace forwarded HTTPS domain
56
+ - enables bridge-side GitHub bearer auth for the current codespace
57
+ - starts the bridge normally
58
+ - attempts to mark the bridge port and browser-preview port public on each startup
59
+
60
+ Important constraints:
61
+
62
+ - Pair the mobile app to the printed `https://<codespace>-8787.app.github.dev` URL, not `127.0.0.1`
63
+ - Browser preview uses the preview port (`8788` by default), so that forwarded port must also be public
64
+ - GitHub resets public forwarded ports back to private whenever the codespace restarts
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
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
70
+
71
+ Manual recovery if port visibility does not update automatically:
72
+
73
+ ```bash
74
+ gh codespace ports visibility 8787:public 8788:public
75
+ ```
76
+
77
+ ### Codespaces Bootstrap
78
+
79
+ The repo devcontainer now includes:
80
+
81
+ - `postCreateCommand`: `npm install --include=dev && npm run codespaces:bootstrap -- --prepare-only`
82
+ - `postStartCommand`: `npm run codespaces:bootstrap`
83
+
84
+ `npm run codespaces:bootstrap` does the following:
85
+
86
+ - installs the Codex CLI via `npm install -g @openai/codex` if it is missing
87
+ - in `--prepare-only` mode, prebuilds the Rust bridge binary without starting it
88
+ - rewrites `.env.secure` for `BRIDGE_NETWORK_MODE=codespaces` with `BRIDGE_ACTIVE_ENGINE=codex`, `BRIDGE_ENABLED_ENGINES=codex`, and `BRIDGE_GITHUB_CODESPACES_AUTH=true`
89
+ - starts the bridge in the background unless you set `CLAWDEX_CODESPACES_SKIP_START=true` or pass `--no-start`
90
+
91
+ That means the first Codespace create now front-loads the expensive bridge compile during `postCreateCommand`, so the later `postStartCommand` can usually start the bridge much faster.
92
+
93
+ The same bootstrap script is included in the published `clawdex-mobile` npm package. That lets the `clawdex-codespace` template stay minimal: it can install `clawdex-mobile@latest` globally in the devcontainer and invoke the packaged bootstrap against the current workspace instead of copying `scripts/*` and `services/rust-bridge/*` into the template repo.
94
+
95
+ Manual examples:
96
+
97
+ ```bash
98
+ npm run codespaces:bootstrap -- --prepare-only
99
+ npm run codespaces:bootstrap
100
+ npm run codespaces:bootstrap -- --no-start
101
+ ```
102
+
103
+ Minimal template equivalent:
104
+
105
+ ```bash
106
+ npm install -g clawdex-mobile@latest @openai/codex
107
+ CLAWDEX_WORKSPACE_ROOT="$PWD" node "$(npm root -g)/clawdex-mobile/scripts/codespaces-bootstrap.js" --prepare-only
108
+ CLAWDEX_WORKSPACE_ROOT="$PWD" node "$(npm root -g)/clawdex-mobile/scripts/codespaces-bootstrap.js"
109
+ ```
110
+
40
111
  ## Manual Secure Setup (No Wizard)
41
112
 
42
113
  ### 1) Install dependencies
@@ -78,7 +149,7 @@ When both CLIs are selected, the bridge starts both backends and merges chat lis
78
149
 
79
150
  ### 4) Pair from the mobile app
80
151
 
81
- Open the installed mobile app on your phone, then scan the bridge QR. If needed, enter the bridge URL manually (for example `http://100.x.y.z:8787` or `http://192.168.x.y:8787`). The chosen bridge URL is stored on-device and can be changed later in Settings.
152
+ Open the installed mobile app on your phone, then scan the bridge QR. If needed, enter the bridge URL manually (for example `http://100.x.y.z:8787`, `http://192.168.x.y:8787`, or `https://<codespace>-8787.app.github.dev`). The chosen bridge URL is stored on-device and can be changed later in Settings.
82
153
 
83
154
  ### In-app Bridge Maintenance
84
155
 
@@ -173,11 +244,17 @@ npm run teardown -- --yes
173
244
 
174
245
  | Variable | Purpose |
175
246
  |---|---|
247
+ | `BRIDGE_NETWORK_MODE` | bridge connectivity mode (`tailscale`, `local`, or `codespaces`) |
176
248
  | `BRIDGE_HOST` | bind host for rust bridge |
177
249
  | `BRIDGE_PORT` | bridge port (default `8787`) |
178
250
  | `BRIDGE_PREVIEW_PORT` | browser preview port for proxied localhost web apps (default `BRIDGE_PORT + 1`) |
251
+ | `BRIDGE_CONNECT_URL` | externally reachable bridge base URL used for pairing/QR output |
252
+ | `BRIDGE_PREVIEW_CONNECT_URL` | externally reachable browser preview base URL |
179
253
  | `BRIDGE_AUTH_TOKEN` | required auth token |
180
254
  | `BRIDGE_ALLOW_QUERY_TOKEN_AUTH` | query-token auth fallback |
255
+ | `BRIDGE_GITHUB_CODESPACES_AUTH` | accept GitHub bearer tokens for the current codespace |
256
+ | `BRIDGE_GITHUB_CODESPACE_NAME` | codespace name used when validating GitHub bearer tokens |
257
+ | `BRIDGE_GITHUB_API_URL` | GitHub REST API base URL for Codespaces auth checks |
181
258
  | `CODEX_CLI_BIN` | codex executable |
182
259
  | `BRIDGE_ACTIVE_ENGINE` | internal preferred routing backend used when multiple harnesses are enabled |
183
260
  | `BRIDGE_ENABLED_ENGINES` | selected harnesses to expose (`codex`, `opencode`, or both) |
@@ -194,6 +271,11 @@ npm run teardown -- --yes
194
271
  | Variable | Purpose |
195
272
  |---|---|
196
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 |
275
+ | `EXPO_PUBLIC_GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN` | forwarded port domain used to derive Codespaces bridge URLs (`app.github.dev` by default) |
276
+ | `EXPO_PUBLIC_GITHUB_CODESPACES_REPO_NAME` | repository name to sort matching Codespaces first in the in-app picker |
277
+ | `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 |
278
+ | `EXPO_PUBLIC_GITHUB_CODESPACES_REPO_REF` | optional git ref/branch used when creating a new Codespace |
197
279
  | `EXPO_PUBLIC_ALLOW_QUERY_TOKEN_AUTH` | query-token behavior for WebSocket auth fallback |
198
280
  | `EXPO_PUBLIC_ALLOW_INSECURE_REMOTE_BRIDGE` | suppress insecure-HTTP warning |
199
281
  | `EXPO_PUBLIC_PRIVACY_POLICY_URL` | in-app Privacy link |
@@ -213,8 +295,9 @@ If you enable the optional tip jar:
213
295
 
214
296
  ## Production Readiness Checklist
215
297
 
216
- - Keep bridge network-private only (Tailscale/private LAN/VPN + host firewall)
217
- - Require `BRIDGE_AUTH_TOKEN`
298
+ - Keep bridge network-private only by default (Tailscale/private LAN/VPN + host firewall)
299
+ - If using GitHub Codespaces, remember the bridge is internet-reachable whenever its forwarded ports are public
300
+ - Require bridge auth of some kind (`BRIDGE_AUTH_TOKEN` or GitHub Codespaces auth)
218
301
  - Keep `BRIDGE_ALLOW_QUERY_TOKEN_AUTH=true` only on private networks (required for Android WS auth fallback)
219
302
  - Do not set `BRIDGE_ALLOW_INSECURE_NO_AUTH=true` outside local debugging
220
303
  - Scope `BRIDGE_WORKDIR` to minimal required root
@@ -34,11 +34,84 @@ 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
38
  - For a local dev build, also ensure `BRIDGE_AUTH_TOKEN` in `.env.secure` matches `EXPO_PUBLIC_HOST_BRIDGE_TOKEN` in `apps/mobile/.env`.
38
39
  - Restart the bridge after token changes.
39
40
  - On secure-launcher installs, `Settings > Bridge Maintenance > Restart bridge safely` can do that from the phone.
40
41
  - If an in-app bridge update fails, inspect `.bridge-updater.log` and `.bridge-update-status.json` in the bridge install root.
41
42
 
43
+ ## GitHub Codespaces bridge URL does not connect
44
+
45
+ - Pair to the printed forwarded HTTPS URL such as `https://<codespace>-8787.app.github.dev`, not `127.0.0.1`.
46
+ - Public forwarded ports reset back to private when the codespace restarts.
47
+ - Restart the bridge or rerun `npm run codespaces:bootstrap` to rerun the automatic visibility step.
48
+ - If needed, set both ports public manually:
49
+
50
+ ```bash
51
+ gh codespace ports visibility 8787:public 8788:public
52
+ ```
53
+
54
+ - If `gh` is unavailable in the codespace, use the Codespaces `Ports` panel and change both forwarded ports to `Public`.
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`.
57
+ - 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
+ ## GitHub Codespaces bootstrap did not start the bridge
60
+
61
+ - Check the post-start command output in the Codespace terminal or rerun it manually:
62
+
63
+ ```bash
64
+ npm run codespaces:bootstrap -- --prepare-only
65
+ npm run codespaces:bootstrap
66
+ ```
67
+
68
+ - On the minimal `clawdex-codespace` template, rerun the packaged bootstrap instead:
69
+
70
+ ```bash
71
+ CLAWDEX_WORKSPACE_ROOT="$PWD" node "$(npm root -g)/clawdex-mobile/scripts/codespaces-bootstrap.js" --prepare-only
72
+ CLAWDEX_WORKSPACE_ROOT="$PWD" node "$(npm root -g)/clawdex-mobile/scripts/codespaces-bootstrap.js"
73
+ ```
74
+
75
+ - The Codespaces bootstrap only prepares the `codex` engine. It will try to install Codex automatically with `npm install -g @openai/codex`.
76
+ - `--prepare-only` installs Codex if needed and prebuilds the Rust bridge binary without starting the bridge.
77
+ - The bootstrap also enables `BRIDGE_GITHUB_CODESPACES_AUTH=true` so GitHub bearer tokens can connect directly to the bridge.
78
+ - If that install fails, fix npm/global package permissions in the codespace and rerun the bootstrap.
79
+ - Bridge startup logs and runtime state live in the Codespace repo root:
80
+
81
+ ```bash
82
+ tail -n 200 .bridge.log
83
+ ls -la .bridge.pid .bridge.log .env.secure
84
+ ```
85
+
86
+ - To only rewrite `.env.secure` without starting the bridge:
87
+
88
+ ```bash
89
+ npm run codespaces:bootstrap -- --no-start
90
+ ```
91
+
92
+ ## Git push in a Codespace fails with `403` or permission denied
93
+
94
+ - The app now bootstraps GitHub HTTPS git credentials inside the Codespace after GitHub sign-in.
95
+ - If you signed in before this behavior shipped, reopen `GitHub Codespaces` in the app and sign in with GitHub once more so the saved token includes repository access.
96
+ - The bootstrap also rewrites common `git@github.com:...` and `ssh://git@github.com/...` remotes to HTTPS so they can use the same credential.
97
+ - After reconnecting, retry the clone/push from the app or from the Codespace shell.
98
+
99
+ ## Voice transcription says no credentials were found
100
+
101
+ - The bridge can transcribe with either `OPENAI_API_KEY`, `BRIDGE_CHATGPT_ACCESS_TOKEN`, or the same ChatGPT auth tokens already used for Codex login.
102
+ - In GitHub Codespaces, finish the ChatGPT/Codex login step from the app first. The bridge will persist those tokens to `BRIDGE_WORKDIR/.clawdex-chatgpt-auth.json` and reuse them for voice transcription.
103
+ - If you still see the error after logging in, restart the bridge once so it reloads the persisted auth cache:
104
+
105
+ ```bash
106
+ npm run secure:bridge
107
+ ```
108
+
109
+ - You can inspect whether the bridge captured the token bundle:
110
+
111
+ ```bash
112
+ ls -la .clawdex-chatgpt-auth.json
113
+ ```
114
+
42
115
  ## Local browser preview does not open
43
116
 
44
117
  - The in-app browser only supports loopback targets from the bridge host: `localhost`, `127.0.0.1`, or `::1`.
@@ -49,6 +122,7 @@ npm run stop:services
49
122
  - By default the preview server binds to `BRIDGE_PORT + 1`.
50
123
  - Restart the bridge after changing `BRIDGE_PREVIEW_PORT`.
51
124
  - If the page shell loads but live reload does not, verify the target dev server is still serving its WebSocket/HMR endpoint locally.
125
+ - In GitHub Codespaces, the preview port (`8788` by default) must also be public or the Browser screen will fail even if the main bridge port works.
52
126
 
53
127
  ## Tailscale issues
54
128
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawdex-mobile",
3
- "version": "5.1.2",
3
+ "version": "5.1.3-internal.1",
4
4
  "description": "Private-network mobile bridge and CLI for Codex and OpenCode",
5
5
  "keywords": [
6
6
  "codex",
@@ -34,6 +34,7 @@
34
34
  "docs/troubleshooting.md",
35
35
  "scripts/bridge-binary.js",
36
36
  "scripts/bridge-self-update.js",
37
+ "scripts/codespaces-bootstrap.js",
37
38
  "scripts/setup-secure-dev.sh",
38
39
  "scripts/setup-wizard.sh",
39
40
  "scripts/start-bridge-secure.js",
@@ -58,6 +59,7 @@
58
59
  "secure:setup": "./scripts/setup-secure-dev.sh",
59
60
  "secure:bridge": "node ./scripts/start-bridge-secure.js",
60
61
  "secure:bridge:dev": "node ./scripts/start-bridge-secure.js --dev",
62
+ "codespaces:bootstrap": "node ./scripts/codespaces-bootstrap.js",
61
63
  "bridge:ts": "BRIDGE_WORKDIR=$(pwd) npm run -w @codex/mac-bridge dev",
62
64
  "version:sync": "node scripts/sync-versions.js",
63
65
  "build": "npm run --workspaces build",
@@ -81,9 +81,9 @@ function packagedBinaryPath(rootDir = repoRoot(), target = resolveRuntimeTarget(
81
81
  return path.join(rootDir, "vendor", "bridge-binaries", target, binaryNameForTarget(target));
82
82
  }
83
83
 
84
- function builtBinaryPath(rootDir = repoRoot(), platform = os.platform()) {
84
+ function builtBinaryPath(rootDir = repoRoot(), platform = os.platform(), profile = "release") {
85
85
  const binaryName = platform === "win32" ? "codex-rust-bridge.exe" : "codex-rust-bridge";
86
- return path.join(rootDir, "services", "rust-bridge", "target", "release", binaryName);
86
+ return path.join(rootDir, "services", "rust-bridge", "target", profile, binaryName);
87
87
  }
88
88
 
89
89
  function ensureExecutable(filePath) {
@@ -168,13 +168,13 @@ function main() {
168
168
  return;
169
169
  }
170
170
  case "current-built-path": {
171
- console.log(builtBinaryPath(rootDir));
171
+ console.log(builtBinaryPath(rootDir, os.platform(), flags.profile || "release"));
172
172
  return;
173
173
  }
174
174
  case "stage-current": {
175
175
  const destination = stageBinary({
176
176
  rootDir,
177
- from: builtBinaryPath(rootDir),
177
+ from: builtBinaryPath(rootDir, os.platform(), flags.profile || "release"),
178
178
  target: flags.target || resolveRuntimeTarget(),
179
179
  });
180
180
  console.log(destination);
@@ -194,8 +194,8 @@ function main() {
194
194
  console.error(" node scripts/bridge-binary.js current-target");
195
195
  console.error(" node scripts/bridge-binary.js has-current-packaged");
196
196
  console.error(" node scripts/bridge-binary.js current-packaged-path [--target <target>]");
197
- console.error(" node scripts/bridge-binary.js current-built-path");
198
- console.error(" node scripts/bridge-binary.js stage-current [--target <target>]");
197
+ console.error(" node scripts/bridge-binary.js current-built-path [--profile <debug|release>]");
198
+ console.error(" node scripts/bridge-binary.js stage-current [--target <target>] [--profile <debug|release>]");
199
199
  console.error(" node scripts/bridge-binary.js stage --from <binary> [--target <target>]");
200
200
  process.exit(1);
201
201
  }
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { spawnSync } = require("node:child_process");
5
+ const fs = require("node:fs");
6
+ const path = require("node:path");
7
+
8
+ function resolveRootDir() {
9
+ return path.resolve(__dirname, "..");
10
+ }
11
+
12
+ function resolveWorkspaceDir() {
13
+ const candidates = [
14
+ process.env.CLAWDEX_WORKSPACE_ROOT,
15
+ process.env.INIT_CWD,
16
+ process.cwd(),
17
+ ];
18
+
19
+ for (const candidate of candidates) {
20
+ if (typeof candidate !== "string" || !candidate.trim()) {
21
+ continue;
22
+ }
23
+ return path.resolve(candidate);
24
+ }
25
+
26
+ return resolveRootDir();
27
+ }
28
+
29
+ function readEnvFile(filePath) {
30
+ const contents = fs.readFileSync(filePath, "utf8");
31
+ const nextEnv = {};
32
+
33
+ for (const rawLine of contents.split(/\r?\n/)) {
34
+ const line = rawLine.trim();
35
+ if (!line || line.startsWith("#")) {
36
+ continue;
37
+ }
38
+
39
+ const match = line.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
40
+ if (!match) {
41
+ continue;
42
+ }
43
+
44
+ const [, key, rawValue] = match;
45
+ let value = rawValue;
46
+ if (
47
+ (value.startsWith('"') && value.endsWith('"')) ||
48
+ (value.startsWith("'") && value.endsWith("'"))
49
+ ) {
50
+ value = value.slice(1, -1);
51
+ }
52
+ nextEnv[key] = value;
53
+ }
54
+
55
+ return nextEnv;
56
+ }
57
+
58
+ function readNonEmptyEnv(env, key) {
59
+ const value = env[key];
60
+ return typeof value === "string" && value.trim() ? value.trim() : "";
61
+ }
62
+
63
+ function runCommand(command, args, options = {}) {
64
+ return spawnSync(command, args, {
65
+ stdio: "inherit",
66
+ ...options,
67
+ });
68
+ }
69
+
70
+ function commandExists(command) {
71
+ if (typeof command === "string" && command.includes(path.sep)) {
72
+ try {
73
+ fs.accessSync(command, fs.constants.X_OK);
74
+ return true;
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
79
+
80
+ const checker = process.platform === "win32" ? "where" : "which";
81
+ const result = spawnSync(checker, [command], { stdio: "ignore" });
82
+ return result.status === 0;
83
+ }
84
+
85
+ function parseArgs(argv) {
86
+ return {
87
+ noStart: argv.includes("--no-start"),
88
+ prepareOnly: argv.includes("--prepare-only"),
89
+ force: argv.includes("--force"),
90
+ };
91
+ }
92
+
93
+ function ensureCodespacesContext({ force }) {
94
+ if (force) {
95
+ return;
96
+ }
97
+
98
+ if (String(process.env.CODESPACES || "").trim().toLowerCase() === "true") {
99
+ return;
100
+ }
101
+
102
+ console.log("Codespaces bootstrap skipped because this shell is not running inside GitHub Codespaces.");
103
+ process.exit(0);
104
+ }
105
+
106
+ function runSetup(rootDir, workspaceDir) {
107
+ const setupEnv = {
108
+ ...process.env,
109
+ };
110
+
111
+ delete setupEnv.BRIDGE_NETWORK_MODE;
112
+ delete setupEnv.BRIDGE_HOST_OVERRIDE;
113
+ delete setupEnv.BRIDGE_ACTIVE_ENGINE;
114
+ delete setupEnv.BRIDGE_ENABLED_ENGINES;
115
+
116
+ setupEnv.INIT_CWD = rootDir;
117
+ setupEnv.CLAWDEX_WORKSPACE_ROOT = workspaceDir;
118
+ setupEnv.BRIDGE_NETWORK_MODE = "codespaces";
119
+ setupEnv.BRIDGE_HOST_OVERRIDE = "127.0.0.1";
120
+ setupEnv.BRIDGE_ACTIVE_ENGINE = "codex";
121
+ setupEnv.BRIDGE_ENABLED_ENGINES = "codex";
122
+
123
+ console.log("Preparing .env.secure for GitHub Codespaces...");
124
+ const result = runCommand(path.join(rootDir, "scripts", "setup-secure-dev.sh"), [], {
125
+ cwd: workspaceDir,
126
+ env: setupEnv,
127
+ shell: false,
128
+ });
129
+
130
+ if ((result.status ?? 1) !== 0) {
131
+ process.exit(result.status ?? 1);
132
+ }
133
+ }
134
+
135
+ function ensureCodexCliInstalled(secureEnv) {
136
+ const codexBinary = readNonEmptyEnv(secureEnv, "CODEX_CLI_BIN") || "codex";
137
+ if (commandExists(codexBinary)) {
138
+ return;
139
+ }
140
+
141
+ console.log("Codex CLI not found in this codespace. Installing via npm...");
142
+ const installResult = runCommand("npm", ["install", "-g", "@openai/codex"], {
143
+ cwd: resolveWorkspaceDir(),
144
+ env: process.env,
145
+ });
146
+ if ((installResult.status ?? 1) !== 0) {
147
+ console.error("error: failed to install Codex CLI automatically.");
148
+ process.exit(installResult.status ?? 1);
149
+ }
150
+
151
+ if (!commandExists(codexBinary)) {
152
+ console.error(`error: Codex CLI still not found after install attempt: ${codexBinary}`);
153
+ process.exit(1);
154
+ }
155
+
156
+ console.log("Codex CLI installed.");
157
+ }
158
+
159
+ function prepareBridgeBinary(rootDir, workspaceDir, secureEnv) {
160
+ console.log("Prebuilding bridge binary for faster Codespaces startup...");
161
+ const prepareEnv = {
162
+ ...process.env,
163
+ ...secureEnv,
164
+ CLAWDEX_WORKSPACE_ROOT: workspaceDir,
165
+ INIT_CWD: process.env.INIT_CWD || workspaceDir,
166
+ };
167
+ const result = runCommand(
168
+ process.execPath,
169
+ [path.join(rootDir, "scripts", "start-bridge-secure.js"), "--prepare-only"],
170
+ {
171
+ cwd: workspaceDir,
172
+ env: prepareEnv,
173
+ }
174
+ );
175
+
176
+ if ((result.status ?? 1) !== 0) {
177
+ process.exit(result.status ?? 1);
178
+ }
179
+ }
180
+
181
+ function startBridge(rootDir, workspaceDir) {
182
+ console.log("Starting bridge in background for this codespace...");
183
+ const result = runCommand(
184
+ process.execPath,
185
+ [path.join(rootDir, "scripts", "start-bridge-secure.js"), "--background"],
186
+ {
187
+ cwd: workspaceDir,
188
+ env: {
189
+ ...process.env,
190
+ CLAWDEX_WORKSPACE_ROOT: workspaceDir,
191
+ INIT_CWD: process.env.INIT_CWD || workspaceDir,
192
+ },
193
+ }
194
+ );
195
+
196
+ if ((result.status ?? 1) !== 0) {
197
+ process.exit(result.status ?? 1);
198
+ }
199
+ }
200
+
201
+ function main() {
202
+ const options = parseArgs(process.argv.slice(2));
203
+ ensureCodespacesContext(options);
204
+
205
+ const rootDir = resolveRootDir();
206
+ const workspaceDir = resolveWorkspaceDir();
207
+ const secureEnvPath = path.join(workspaceDir, ".env.secure");
208
+
209
+ runSetup(rootDir, workspaceDir);
210
+
211
+ if (!fs.existsSync(secureEnvPath)) {
212
+ console.error(`error: expected secure env at ${secureEnvPath}`);
213
+ process.exit(1);
214
+ }
215
+
216
+ const secureEnv = readEnvFile(secureEnvPath);
217
+ if (options.noStart || String(process.env.CLAWDEX_CODESPACES_SKIP_START || "").trim().toLowerCase() === "true") {
218
+ console.log("Codespaces bootstrap configured bridge env only. Bridge auto-start skipped.");
219
+ return;
220
+ }
221
+
222
+ ensureCodexCliInstalled(secureEnv);
223
+ if (options.prepareOnly) {
224
+ prepareBridgeBinary(rootDir, workspaceDir, secureEnv);
225
+ console.log("Codespaces bootstrap prepared Codex and the bridge binary without starting the bridge.");
226
+ return;
227
+ }
228
+ startBridge(rootDir, workspaceDir);
229
+ }
230
+
231
+ main();