clawdex-mobile 3.0.0 → 4.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 +62 -2
- package/.github/workflows/pages.yml +1 -1
- package/README.md +14 -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 +17 -12
- package/docs/troubleshooting.md +15 -19
- package/package.json +4 -3
- package/scripts/bridge-binary.js +194 -0
- package/scripts/setup-wizard.sh +17 -186
- 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,76 @@ 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-14
|
|
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: ubuntu-latest
|
|
32
|
+
package_target: linux-x64
|
|
33
|
+
rust_target: x86_64-unknown-linux-gnu
|
|
34
|
+
binary_path: services/rust-bridge/target/x86_64-unknown-linux-gnu/release/codex-rust-bridge
|
|
35
|
+
- runner: windows-latest
|
|
36
|
+
package_target: win32-x64
|
|
37
|
+
rust_target: x86_64-pc-windows-msvc
|
|
38
|
+
binary_path: services/rust-bridge/target/x86_64-pc-windows-msvc/release/codex-rust-bridge.exe
|
|
39
|
+
steps:
|
|
40
|
+
- name: Checkout
|
|
41
|
+
uses: actions/checkout@v5
|
|
42
|
+
|
|
43
|
+
- name: Setup Node.js
|
|
44
|
+
uses: actions/setup-node@v5
|
|
45
|
+
with:
|
|
46
|
+
node-version: "20"
|
|
47
|
+
package-manager-cache: false
|
|
48
|
+
|
|
49
|
+
- name: Setup Rust toolchain
|
|
50
|
+
uses: dtolnay/rust-toolchain@stable
|
|
51
|
+
with:
|
|
52
|
+
targets: ${{ matrix.rust_target }}
|
|
53
|
+
|
|
54
|
+
- name: Cache Rust dependencies
|
|
55
|
+
uses: Swatinem/rust-cache@v2
|
|
56
|
+
|
|
57
|
+
- name: Build bridge binary
|
|
58
|
+
working-directory: services/rust-bridge
|
|
59
|
+
run: cargo build --release --locked --target ${{ matrix.rust_target }}
|
|
60
|
+
|
|
61
|
+
- name: Stage packaged bridge binary
|
|
62
|
+
run: node scripts/bridge-binary.js stage --target ${{ matrix.package_target }} --from ${{ matrix.binary_path }}
|
|
63
|
+
|
|
64
|
+
- name: Upload bridge artifact
|
|
65
|
+
uses: actions/upload-artifact@v6
|
|
66
|
+
with:
|
|
67
|
+
name: bridge-binaries-${{ matrix.package_target }}
|
|
68
|
+
path: vendor/bridge-binaries/${{ matrix.package_target }}/
|
|
69
|
+
|
|
19
70
|
publish:
|
|
20
71
|
name: Publish package to npm
|
|
72
|
+
needs: build_bridge_binaries
|
|
21
73
|
runs-on: ubuntu-latest
|
|
22
74
|
timeout-minutes: 30
|
|
23
75
|
steps:
|
|
24
76
|
- name: Checkout
|
|
25
|
-
uses: actions/checkout@
|
|
77
|
+
uses: actions/checkout@v5
|
|
26
78
|
with:
|
|
27
79
|
fetch-depth: 0
|
|
28
80
|
|
|
29
81
|
- name: Setup Node.js
|
|
30
|
-
uses: actions/setup-node@
|
|
82
|
+
uses: actions/setup-node@v5
|
|
31
83
|
with:
|
|
32
84
|
node-version: "20"
|
|
33
85
|
registry-url: "https://registry.npmjs.org"
|
|
34
86
|
cache: npm
|
|
35
87
|
cache-dependency-path: package-lock.json
|
|
88
|
+
package-manager-cache: false
|
|
36
89
|
|
|
37
90
|
- name: Setup Rust toolchain
|
|
38
91
|
uses: dtolnay/rust-toolchain@stable
|
|
@@ -43,6 +96,13 @@ jobs:
|
|
|
43
96
|
- name: Install dependencies
|
|
44
97
|
run: npm ci
|
|
45
98
|
|
|
99
|
+
- name: Download packaged bridge binaries
|
|
100
|
+
uses: actions/download-artifact@v7
|
|
101
|
+
with:
|
|
102
|
+
pattern: bridge-binaries-*
|
|
103
|
+
path: .
|
|
104
|
+
merge-multiple: true
|
|
105
|
+
|
|
46
106
|
- name: Verify Rust bridge compiles
|
|
47
107
|
working-directory: services/rust-bridge
|
|
48
108
|
run: cargo check --locked
|
package/README.md
CHANGED
|
@@ -41,6 +41,8 @@ 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`, `linux-x64`, and `win32-x64`, so supported hosts do not need to compile the bridge during normal startup. Intel Macs are not packaged in the npm release and fall back to a local Rust build if needed. The interactive shell-based setup helpers are still macOS/Linux-oriented today.
|
|
45
|
+
|
|
44
46
|
`clawdex init` guides you through:
|
|
45
47
|
|
|
46
48
|
1. bridge mode selection: `Local (LAN)` or `Tailscale`
|
|
@@ -48,6 +50,8 @@ This is the primary starting point.
|
|
|
48
50
|
3. phone readiness checks for selected mode
|
|
49
51
|
4. optional bridge launch in the foreground (release build)
|
|
50
52
|
|
|
53
|
+
This flow is bridge-only. The mobile app is already shipped, so no Expo dev server is required to pair your phone.
|
|
54
|
+
|
|
51
55
|
When the bridge starts, it prints a pairing QR:
|
|
52
56
|
|
|
53
57
|
- preferred: QR contains both `bridgeUrl + bridgeToken` (one-scan onboarding)
|
|
@@ -103,7 +107,7 @@ Optional for simulators/emulators:
|
|
|
103
107
|
From repo root:
|
|
104
108
|
|
|
105
109
|
- `npm run setup:wizard` — guided secure setup + optional bridge start
|
|
106
|
-
- `npm run stop:services` — stop running bridge (and Expo if running)
|
|
110
|
+
- `npm run stop:services` — stop running bridge (and local Expo if running)
|
|
107
111
|
- `npm run secure:setup` — generate/update secure env
|
|
108
112
|
- `npm run secure:bridge` — start rust bridge from `.env.secure` (release profile)
|
|
109
113
|
- `npm run secure:bridge:dev` — start rust bridge in dev profile
|
|
@@ -140,7 +144,14 @@ npm run secure:bridge
|
|
|
140
144
|
|
|
141
145
|
Keep `npm run secure:bridge` running in foreground. It prints pairing QR and bridge logs.
|
|
142
146
|
|
|
143
|
-
|
|
147
|
+
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.
|
|
148
|
+
|
|
149
|
+
Then open the installed mobile app and pair it:
|
|
150
|
+
|
|
151
|
+
- preferred: scan the bridge QR to autofill URL + token
|
|
152
|
+
- fallback: enter the bridge URL and token manually
|
|
153
|
+
|
|
154
|
+
For local mobile development only, start Expo from the repo:
|
|
144
155
|
|
|
145
156
|
```bash
|
|
146
157
|
npm run mobile
|
|
@@ -150,7 +161,7 @@ npm run mobile
|
|
|
150
161
|
|
|
151
162
|
On first launch (or after reset):
|
|
152
163
|
|
|
153
|
-
1.
|
|
164
|
+
1. start the bridge on your server
|
|
154
165
|
2. scan bridge QR to autofill URL + token
|
|
155
166
|
3. use `Test Connection` (health + authenticated RPC check)
|
|
156
167
|
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": "4.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),
|