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.
- package/.github/workflows/ci.yml +4 -3
- package/.github/workflows/npm-release.yml +66 -2
- package/.github/workflows/pages.yml +1 -1
- package/README.md +16 -3
- package/apps/mobile/app.json +1 -1
- package/apps/mobile/package.json +2 -1
- package/apps/mobile/src/api/__tests__/client.test.ts +13 -5
- package/apps/mobile/src/api/client.ts +25 -0
- package/apps/mobile/src/api/types.ts +1 -0
- package/apps/mobile/src/components/WorkspacePickerModal.tsx +555 -315
- package/apps/mobile/src/screens/MainScreen.tsx +0 -5
- package/apps/mobile/src/screens/OnboardingScreen.tsx +924 -312
- package/bin/clawdex.js +7 -6
- package/codex-rust-bridge +0 -0
- package/codex-rust-bridge.exe +0 -0
- package/docs/setup-and-operations.md +19 -12
- package/docs/troubleshooting.md +16 -19
- package/package.json +4 -3
- package/scripts/bridge-binary.js +200 -0
- package/scripts/setup-wizard.sh +17 -240
- package/scripts/start-bridge-secure.js +240 -0
- package/scripts/start-bridge-secure.sh +1 -40
- package/services/rust-bridge/Cargo.lock +1 -1
- package/services/rust-bridge/Cargo.toml +1 -1
- package/services/rust-bridge/package.json +1 -1
- package/services/rust-bridge/src/main.rs +11 -1
package/.github/workflows/ci.yml
CHANGED
|
@@ -29,14 +29,15 @@ jobs:
|
|
|
29
29
|
- "clawdex-mobile"
|
|
30
30
|
steps:
|
|
31
31
|
- name: Checkout
|
|
32
|
-
uses: actions/checkout@
|
|
32
|
+
uses: actions/checkout@v5
|
|
33
33
|
|
|
34
34
|
- name: Setup Node.js
|
|
35
|
-
uses: actions/setup-node@
|
|
35
|
+
uses: actions/setup-node@v5
|
|
36
36
|
with:
|
|
37
37
|
node-version: "20"
|
|
38
38
|
cache: npm
|
|
39
39
|
cache-dependency-path: package-lock.json
|
|
40
|
+
package-manager-cache: false
|
|
40
41
|
|
|
41
42
|
- name: Install dependencies
|
|
42
43
|
run: npm ci
|
|
@@ -56,7 +57,7 @@ jobs:
|
|
|
56
57
|
timeout-minutes: 20
|
|
57
58
|
steps:
|
|
58
59
|
- name: Checkout
|
|
59
|
-
uses: actions/checkout@
|
|
60
|
+
uses: actions/checkout@v5
|
|
60
61
|
|
|
61
62
|
- name: Setup Rust toolchain
|
|
62
63
|
uses: dtolnay/rust-toolchain@stable
|
|
@@ -16,23 +16,80 @@ permissions:
|
|
|
16
16
|
contents: write
|
|
17
17
|
|
|
18
18
|
jobs:
|
|
19
|
+
build_bridge_binaries:
|
|
20
|
+
name: Build bridge binary (${{ matrix.package_target }})
|
|
21
|
+
runs-on: ${{ matrix.runner }}
|
|
22
|
+
timeout-minutes: 30
|
|
23
|
+
strategy:
|
|
24
|
+
fail-fast: false
|
|
25
|
+
matrix:
|
|
26
|
+
include:
|
|
27
|
+
- runner: macos-26
|
|
28
|
+
package_target: darwin-arm64
|
|
29
|
+
rust_target: aarch64-apple-darwin
|
|
30
|
+
binary_path: services/rust-bridge/target/aarch64-apple-darwin/release/codex-rust-bridge
|
|
31
|
+
- runner: macos-26-intel
|
|
32
|
+
package_target: darwin-x64
|
|
33
|
+
rust_target: x86_64-apple-darwin
|
|
34
|
+
binary_path: services/rust-bridge/target/x86_64-apple-darwin/release/codex-rust-bridge
|
|
35
|
+
- runner: ubuntu-latest
|
|
36
|
+
package_target: linux-x64
|
|
37
|
+
rust_target: x86_64-unknown-linux-gnu
|
|
38
|
+
binary_path: services/rust-bridge/target/x86_64-unknown-linux-gnu/release/codex-rust-bridge
|
|
39
|
+
- runner: windows-latest
|
|
40
|
+
package_target: win32-x64
|
|
41
|
+
rust_target: x86_64-pc-windows-msvc
|
|
42
|
+
binary_path: services/rust-bridge/target/x86_64-pc-windows-msvc/release/codex-rust-bridge.exe
|
|
43
|
+
steps:
|
|
44
|
+
- name: Checkout
|
|
45
|
+
uses: actions/checkout@v5
|
|
46
|
+
|
|
47
|
+
- name: Setup Node.js
|
|
48
|
+
uses: actions/setup-node@v5
|
|
49
|
+
with:
|
|
50
|
+
node-version: "20"
|
|
51
|
+
package-manager-cache: false
|
|
52
|
+
|
|
53
|
+
- name: Setup Rust toolchain
|
|
54
|
+
uses: dtolnay/rust-toolchain@stable
|
|
55
|
+
with:
|
|
56
|
+
targets: ${{ matrix.rust_target }}
|
|
57
|
+
|
|
58
|
+
- name: Cache Rust dependencies
|
|
59
|
+
uses: Swatinem/rust-cache@v2
|
|
60
|
+
|
|
61
|
+
- name: Build bridge binary
|
|
62
|
+
working-directory: services/rust-bridge
|
|
63
|
+
run: cargo build --release --locked --target ${{ matrix.rust_target }}
|
|
64
|
+
|
|
65
|
+
- name: Stage packaged bridge binary
|
|
66
|
+
run: node scripts/bridge-binary.js stage --target ${{ matrix.package_target }} --from ${{ matrix.binary_path }}
|
|
67
|
+
|
|
68
|
+
- name: Upload bridge artifact
|
|
69
|
+
uses: actions/upload-artifact@v6
|
|
70
|
+
with:
|
|
71
|
+
name: bridge-binaries-${{ matrix.package_target }}
|
|
72
|
+
path: vendor/bridge-binaries/${{ matrix.package_target }}/
|
|
73
|
+
|
|
19
74
|
publish:
|
|
20
75
|
name: Publish package to npm
|
|
76
|
+
needs: build_bridge_binaries
|
|
21
77
|
runs-on: ubuntu-latest
|
|
22
78
|
timeout-minutes: 30
|
|
23
79
|
steps:
|
|
24
80
|
- name: Checkout
|
|
25
|
-
uses: actions/checkout@
|
|
81
|
+
uses: actions/checkout@v5
|
|
26
82
|
with:
|
|
27
83
|
fetch-depth: 0
|
|
28
84
|
|
|
29
85
|
- name: Setup Node.js
|
|
30
|
-
uses: actions/setup-node@
|
|
86
|
+
uses: actions/setup-node@v5
|
|
31
87
|
with:
|
|
32
88
|
node-version: "20"
|
|
33
89
|
registry-url: "https://registry.npmjs.org"
|
|
34
90
|
cache: npm
|
|
35
91
|
cache-dependency-path: package-lock.json
|
|
92
|
+
package-manager-cache: false
|
|
36
93
|
|
|
37
94
|
- name: Setup Rust toolchain
|
|
38
95
|
uses: dtolnay/rust-toolchain@stable
|
|
@@ -43,6 +100,13 @@ jobs:
|
|
|
43
100
|
- name: Install dependencies
|
|
44
101
|
run: npm ci
|
|
45
102
|
|
|
103
|
+
- name: Download packaged bridge binaries
|
|
104
|
+
uses: actions/download-artifact@v7
|
|
105
|
+
with:
|
|
106
|
+
pattern: bridge-binaries-*
|
|
107
|
+
path: .
|
|
108
|
+
merge-multiple: true
|
|
109
|
+
|
|
46
110
|
- name: Verify Rust bridge compiles
|
|
47
111
|
working-directory: services/rust-bridge
|
|
48
112
|
run: cargo check --locked
|
package/README.md
CHANGED
|
@@ -41,6 +41,10 @@ clawdex init
|
|
|
41
41
|
|
|
42
42
|
This is the primary starting point.
|
|
43
43
|
|
|
44
|
+
Published npm releases bundle prebuilt Rust bridge binaries for `darwin-arm64`, `darwin-x64`, `linux-x64`, and `win32-x64`, so supported hosts do not need to compile the bridge during normal startup. The interactive shell-based setup helpers are still macOS/Linux-oriented today.
|
|
45
|
+
|
|
46
|
+
`clawdex init` does not run a project-local `npm install`. The global package install is the only npm install needed for the published bridge flow.
|
|
47
|
+
|
|
44
48
|
`clawdex init` guides you through:
|
|
45
49
|
|
|
46
50
|
1. bridge mode selection: `Local (LAN)` or `Tailscale`
|
|
@@ -48,6 +52,8 @@ This is the primary starting point.
|
|
|
48
52
|
3. phone readiness checks for selected mode
|
|
49
53
|
4. optional bridge launch in the foreground (release build)
|
|
50
54
|
|
|
55
|
+
This flow is bridge-only. The mobile app is already shipped, so no Expo dev server is required to pair your phone.
|
|
56
|
+
|
|
51
57
|
When the bridge starts, it prints a pairing QR:
|
|
52
58
|
|
|
53
59
|
- preferred: QR contains both `bridgeUrl + bridgeToken` (one-scan onboarding)
|
|
@@ -103,7 +109,7 @@ Optional for simulators/emulators:
|
|
|
103
109
|
From repo root:
|
|
104
110
|
|
|
105
111
|
- `npm run setup:wizard` — guided secure setup + optional bridge start
|
|
106
|
-
- `npm run stop:services` — stop running bridge (and Expo if running)
|
|
112
|
+
- `npm run stop:services` — stop running bridge (and local Expo if running)
|
|
107
113
|
- `npm run secure:setup` — generate/update secure env
|
|
108
114
|
- `npm run secure:bridge` — start rust bridge from `.env.secure` (release profile)
|
|
109
115
|
- `npm run secure:bridge:dev` — start rust bridge in dev profile
|
|
@@ -140,7 +146,14 @@ npm run secure:bridge
|
|
|
140
146
|
|
|
141
147
|
Keep `npm run secure:bridge` running in foreground. It prints pairing QR and bridge logs.
|
|
142
148
|
|
|
143
|
-
|
|
149
|
+
On supported published installs, this uses the bundled bridge binary directly instead of compiling Rust on startup. Source checkouts still build from Cargo when a packaged binary is unavailable.
|
|
150
|
+
|
|
151
|
+
Then open the installed mobile app and pair it:
|
|
152
|
+
|
|
153
|
+
- preferred: scan the bridge QR to autofill URL + token
|
|
154
|
+
- fallback: enter the bridge URL and token manually
|
|
155
|
+
|
|
156
|
+
For local mobile development only, start Expo from the repo:
|
|
144
157
|
|
|
145
158
|
```bash
|
|
146
159
|
npm run mobile
|
|
@@ -150,7 +163,7 @@ npm run mobile
|
|
|
150
163
|
|
|
151
164
|
On first launch (or after reset):
|
|
152
165
|
|
|
153
|
-
1.
|
|
166
|
+
1. start the bridge on your server
|
|
154
167
|
2. scan bridge QR to autofill URL + token
|
|
155
168
|
3. use `Test Connection` (health + authenticated RPC check)
|
|
156
169
|
4. tap `Continue`
|
package/apps/mobile/app.json
CHANGED
package/apps/mobile/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawdex-mobile",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"expo-audio": "~55.0.9",
|
|
21
21
|
"expo-blur": "~55.0.10",
|
|
22
22
|
"expo-camera": "~55.0.10",
|
|
23
|
+
"expo-clipboard": "~55.0.9",
|
|
23
24
|
"expo-document-picker": "~55.0.9",
|
|
24
25
|
"expo-file-system": "~55.0.9",
|
|
25
26
|
"expo-image-picker": "~55.0.13",
|
|
@@ -383,9 +383,9 @@ describe('HostBridgeApiClient', () => {
|
|
|
383
383
|
bridgeRoot: '/Users/mohit/work',
|
|
384
384
|
allowOutsideRootCwd: true,
|
|
385
385
|
workspaces: [
|
|
386
|
-
{ path: '/Users/mohit/work/app', chatCount: 3 },
|
|
387
|
-
{ path: '/Users/mohit/work/docs', chatCount: '1' },
|
|
388
|
-
{ path: '', chatCount: 99 },
|
|
386
|
+
{ path: '/Users/mohit/work/app', chatCount: 3, updatedAt: 1700000000 },
|
|
387
|
+
{ path: '/Users/mohit/work/docs', chatCount: '1', updatedAt: '1700001000' },
|
|
388
|
+
{ path: '', chatCount: 99, updatedAt: 1700002000 },
|
|
389
389
|
],
|
|
390
390
|
});
|
|
391
391
|
|
|
@@ -397,8 +397,16 @@ describe('HostBridgeApiClient', () => {
|
|
|
397
397
|
bridgeRoot: '/Users/mohit/work',
|
|
398
398
|
allowOutsideRootCwd: true,
|
|
399
399
|
workspaces: [
|
|
400
|
-
{
|
|
401
|
-
|
|
400
|
+
{
|
|
401
|
+
path: '/Users/mohit/work/app',
|
|
402
|
+
chatCount: 3,
|
|
403
|
+
updatedAt: new Date(1700000000 * 1000).toISOString(),
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
path: '/Users/mohit/work/docs',
|
|
407
|
+
chatCount: 1,
|
|
408
|
+
updatedAt: new Date(1700001000 * 1000).toISOString(),
|
|
409
|
+
},
|
|
402
410
|
],
|
|
403
411
|
});
|
|
404
412
|
});
|
|
@@ -955,6 +955,29 @@ function normalizeCwd(cwd: string | null | undefined): string | null {
|
|
|
955
955
|
return trimmed.length > 0 ? trimmed : null;
|
|
956
956
|
}
|
|
957
957
|
|
|
958
|
+
function readTimestampIso(value: unknown): string | null {
|
|
959
|
+
if (typeof value === 'string') {
|
|
960
|
+
const trimmed = value.trim();
|
|
961
|
+
if (!trimmed) {
|
|
962
|
+
return null;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
const numeric = Number(trimmed);
|
|
966
|
+
if (Number.isFinite(numeric) && numeric > 0) {
|
|
967
|
+
return new Date((numeric > 1_000_000_000_000 ? numeric : numeric * 1000)).toISOString();
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
const parsedMs = Date.parse(trimmed);
|
|
971
|
+
return Number.isFinite(parsedMs) ? new Date(parsedMs).toISOString() : null;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
|
975
|
+
return new Date((value > 1_000_000_000_000 ? value : value * 1000)).toISOString();
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
return null;
|
|
979
|
+
}
|
|
980
|
+
|
|
958
981
|
function readWorkspaceListResponse(value: unknown): WorkspaceListResponse {
|
|
959
982
|
const record = toRecord(value) ?? {};
|
|
960
983
|
const workspacesRaw = Array.isArray(record.workspaces) ? record.workspaces : [];
|
|
@@ -981,10 +1004,12 @@ function readWorkspaceListResponse(value: unknown): WorkspaceListResponse {
|
|
|
981
1004
|
: typeof rawChatCount === 'string'
|
|
982
1005
|
? Math.max(0, Number.parseInt(rawChatCount, 10) || 0)
|
|
983
1006
|
: 0;
|
|
1007
|
+
const updatedAt = readTimestampIso(workspace.updatedAt);
|
|
984
1008
|
|
|
985
1009
|
return {
|
|
986
1010
|
path,
|
|
987
1011
|
chatCount,
|
|
1012
|
+
...(updatedAt ? { updatedAt } : {}),
|
|
988
1013
|
};
|
|
989
1014
|
})
|
|
990
1015
|
.filter((entry): entry is WorkspaceListResponse['workspaces'][number] => entry !== null),
|