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
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { spawn, spawnSync } = require("node:child_process");
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
const os = require("node:os");
|
|
7
|
+
const path = require("node:path");
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
builtBinaryPath,
|
|
11
|
+
ensureExecutable,
|
|
12
|
+
packagedBinaryPath,
|
|
13
|
+
resolveRuntimeTarget,
|
|
14
|
+
} = require("./bridge-binary");
|
|
15
|
+
|
|
16
|
+
function resolveRootDir() {
|
|
17
|
+
let rootDir = process.env.INIT_CWD ? path.resolve(process.env.INIT_CWD) : path.resolve(__dirname, "..");
|
|
18
|
+
if (!fs.existsSync(path.join(rootDir, "package.json"))) {
|
|
19
|
+
rootDir = path.resolve(__dirname, "..");
|
|
20
|
+
}
|
|
21
|
+
return rootDir;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readEnvFile(filePath) {
|
|
25
|
+
const contents = fs.readFileSync(filePath, "utf8");
|
|
26
|
+
const nextEnv = {};
|
|
27
|
+
|
|
28
|
+
for (const rawLine of contents.split(/\r?\n/)) {
|
|
29
|
+
const line = rawLine.trim();
|
|
30
|
+
if (!line || line.startsWith("#")) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const match = line.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
35
|
+
if (!match) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const [, key, rawValue] = match;
|
|
40
|
+
let value = rawValue;
|
|
41
|
+
if (
|
|
42
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
43
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
44
|
+
) {
|
|
45
|
+
value = value.slice(1, -1);
|
|
46
|
+
}
|
|
47
|
+
nextEnv[key] = value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return nextEnv;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function commandExists(command) {
|
|
54
|
+
const checker = process.platform === "win32" ? "where" : "which";
|
|
55
|
+
const result = spawnSync(checker, [command], { stdio: "ignore" });
|
|
56
|
+
return result.status === 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function walkFiles(directory) {
|
|
60
|
+
const entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
61
|
+
const files = [];
|
|
62
|
+
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
const entryPath = path.join(directory, entry.name);
|
|
65
|
+
if (entry.isDirectory()) {
|
|
66
|
+
files.push(...walkFiles(entryPath));
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (entry.isFile()) {
|
|
70
|
+
files.push(entryPath);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return files;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isBuiltBinaryFresh(rootDir, binaryPath) {
|
|
78
|
+
if (!fs.existsSync(binaryPath)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const binaryMtime = fs.statSync(binaryPath).mtimeMs;
|
|
83
|
+
const watchPaths = [
|
|
84
|
+
path.join(rootDir, "services", "rust-bridge", "Cargo.toml"),
|
|
85
|
+
path.join(rootDir, "services", "rust-bridge", "Cargo.lock"),
|
|
86
|
+
];
|
|
87
|
+
const sourceDir = path.join(rootDir, "services", "rust-bridge", "src");
|
|
88
|
+
|
|
89
|
+
if (fs.existsSync(sourceDir)) {
|
|
90
|
+
watchPaths.push(...walkFiles(sourceDir));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return watchPaths.every((watchPath) => {
|
|
94
|
+
if (!fs.existsSync(watchPath)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return fs.statSync(watchPath).mtimeMs <= binaryMtime;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function printMissingCompilerHint() {
|
|
102
|
+
if (process.platform === "win32") {
|
|
103
|
+
console.error("Install Visual Studio Build Tools (Desktop development with C++) and Rust, then retry.");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (commandExists("apt-get")) {
|
|
107
|
+
console.error("Install on Ubuntu/Debian: sudo apt-get update && sudo apt-get install -y build-essential");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (commandExists("dnf")) {
|
|
111
|
+
console.error("Install on Fedora/RHEL: sudo dnf install -y gcc gcc-c++ make");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (commandExists("yum")) {
|
|
115
|
+
console.error("Install on CentOS/RHEL: sudo yum install -y gcc gcc-c++ make");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (commandExists("apk")) {
|
|
119
|
+
console.error("Install on Alpine: sudo apk add build-base");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (commandExists("xcode-select")) {
|
|
123
|
+
console.error("Install on macOS: xcode-select --install");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function spawnAndRelay(command, args, options) {
|
|
128
|
+
const child = spawn(command, args, {
|
|
129
|
+
stdio: "inherit",
|
|
130
|
+
...options,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
child.on("error", (error) => {
|
|
134
|
+
console.error(`error: failed to start ${command}: ${error.message}`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
child.on("exit", (code, signal) => {
|
|
139
|
+
if (signal) {
|
|
140
|
+
process.kill(process.pid, signal);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
process.exit(code ?? 0);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function buildBridgeFromSource(rootDir, env) {
|
|
148
|
+
const cargoCmd = "cargo";
|
|
149
|
+
const args = ["build", "--release", "--locked"];
|
|
150
|
+
const result = spawnSync(cargoCmd, args, {
|
|
151
|
+
cwd: path.join(rootDir, "services", "rust-bridge"),
|
|
152
|
+
env,
|
|
153
|
+
stdio: "inherit",
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (result.error) {
|
|
157
|
+
console.error(`error: failed to run cargo build: ${result.error.message}`);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if ((result.status ?? 1) !== 0) {
|
|
162
|
+
process.exit(result.status ?? 1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function start() {
|
|
167
|
+
const rootDir = resolveRootDir();
|
|
168
|
+
const secureEnvFile = path.join(rootDir, ".env.secure");
|
|
169
|
+
if (!fs.existsSync(secureEnvFile)) {
|
|
170
|
+
console.error(`error: ${secureEnvFile} not found. Run: npm run secure:setup`);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const fileEnv = readEnvFile(secureEnvFile);
|
|
175
|
+
const env = { ...fileEnv, ...process.env };
|
|
176
|
+
const devMode = process.argv.includes("--dev") || env.BRIDGE_RUN_MODE === "dev";
|
|
177
|
+
const forceSourceBuild = env.CLAWDEX_BRIDGE_FORCE_SOURCE_BUILD === "true";
|
|
178
|
+
|
|
179
|
+
if (devMode) {
|
|
180
|
+
if (!commandExists("cargo")) {
|
|
181
|
+
console.error("error: missing Rust/Cargo toolchain for dev bridge mode.");
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
spawnAndRelay("cargo", ["run"], {
|
|
186
|
+
cwd: path.join(rootDir, "services", "rust-bridge"),
|
|
187
|
+
env,
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const overrideBinary = env.CLAWDEX_BRIDGE_BINARY ? path.resolve(env.CLAWDEX_BRIDGE_BINARY) : "";
|
|
193
|
+
if (overrideBinary) {
|
|
194
|
+
if (!fs.existsSync(overrideBinary)) {
|
|
195
|
+
console.error(`error: CLAWDEX_BRIDGE_BINARY not found at ${overrideBinary}`);
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
ensureExecutable(overrideBinary);
|
|
199
|
+
spawnAndRelay(overrideBinary, [], { cwd: rootDir, env });
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const packagedBinary = packagedBinaryPath(rootDir, resolveRuntimeTarget());
|
|
204
|
+
if (!forceSourceBuild && packagedBinary && fs.existsSync(packagedBinary)) {
|
|
205
|
+
ensureExecutable(packagedBinary);
|
|
206
|
+
spawnAndRelay(packagedBinary, [], { cwd: rootDir, env });
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const builtBinary = builtBinaryPath(rootDir, os.platform());
|
|
211
|
+
if (isBuiltBinaryFresh(rootDir, builtBinary)) {
|
|
212
|
+
ensureExecutable(builtBinary);
|
|
213
|
+
spawnAndRelay(builtBinary, [], { cwd: rootDir, env });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!commandExists("cargo")) {
|
|
218
|
+
console.error("error: no packaged bridge binary was found for this host, and cargo is not installed.");
|
|
219
|
+
console.error("Reinstall a published clawdex-mobile package with bundled bridge binaries, or install Rust and retry.");
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (process.platform !== "win32" && !commandExists("cc")) {
|
|
224
|
+
console.error("error: missing system C compiler/linker ('cc'). Rust bridge cannot compile without it.");
|
|
225
|
+
printMissingCompilerHint();
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
buildBridgeFromSource(rootDir, env);
|
|
230
|
+
|
|
231
|
+
if (!fs.existsSync(builtBinary)) {
|
|
232
|
+
console.error(`error: expected built bridge binary at ${builtBinary}, but it was not created.`);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
ensureExecutable(builtBinary);
|
|
237
|
+
spawnAndRelay(builtBinary, [], { cwd: rootDir, env });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
start();
|
|
@@ -2,43 +2,4 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -L)"
|
|
5
|
-
|
|
6
|
-
if [[ ! -f "$ROOT_DIR/package.json" ]]; then
|
|
7
|
-
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd -L)"
|
|
8
|
-
fi
|
|
9
|
-
SECURE_ENV_FILE="$ROOT_DIR/.env.secure"
|
|
10
|
-
|
|
11
|
-
if [[ ! -f "$SECURE_ENV_FILE" ]]; then
|
|
12
|
-
echo "error: $SECURE_ENV_FILE not found. Run: npm run secure:setup" >&2
|
|
13
|
-
exit 1
|
|
14
|
-
fi
|
|
15
|
-
|
|
16
|
-
if ! command -v cc >/dev/null 2>&1; then
|
|
17
|
-
echo "error: missing system C compiler/linker ('cc'). Rust bridge cannot compile without it." >&2
|
|
18
|
-
if command -v apt-get >/dev/null 2>&1; then
|
|
19
|
-
echo "Install on Ubuntu/Debian: sudo apt-get update && sudo apt-get install -y build-essential" >&2
|
|
20
|
-
elif command -v dnf >/dev/null 2>&1; then
|
|
21
|
-
echo "Install on Fedora/RHEL: sudo dnf install -y gcc gcc-c++ make" >&2
|
|
22
|
-
elif command -v yum >/dev/null 2>&1; then
|
|
23
|
-
echo "Install on CentOS/RHEL: sudo yum install -y gcc gcc-c++ make" >&2
|
|
24
|
-
elif command -v apk >/dev/null 2>&1; then
|
|
25
|
-
echo "Install on Alpine: sudo apk add build-base" >&2
|
|
26
|
-
elif command -v xcode-select >/dev/null 2>&1; then
|
|
27
|
-
echo "Install on macOS: xcode-select --install" >&2
|
|
28
|
-
fi
|
|
29
|
-
exit 1
|
|
30
|
-
fi
|
|
31
|
-
|
|
32
|
-
set -a
|
|
33
|
-
# shellcheck disable=SC1090
|
|
34
|
-
source "$SECURE_ENV_FILE"
|
|
35
|
-
set +a
|
|
36
|
-
|
|
37
|
-
BRIDGE_RUN_MODE="${BRIDGE_RUN_MODE:-release}"
|
|
38
|
-
|
|
39
|
-
cd "$ROOT_DIR"
|
|
40
|
-
if [[ "$BRIDGE_RUN_MODE" == "dev" ]]; then
|
|
41
|
-
exec npm run -w @codex/rust-bridge dev
|
|
42
|
-
fi
|
|
43
|
-
|
|
44
|
-
exec npm run -w @codex/rust-bridge start
|
|
5
|
+
exec node "$SCRIPT_DIR/start-bridge-secure.js" "$@"
|
|
@@ -1907,6 +1907,7 @@ struct WorkspaceListRequest {
|
|
|
1907
1907
|
struct WorkspaceSummary {
|
|
1908
1908
|
path: String,
|
|
1909
1909
|
chat_count: usize,
|
|
1910
|
+
updated_at: Option<u64>,
|
|
1910
1911
|
}
|
|
1911
1912
|
|
|
1912
1913
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
@@ -2806,7 +2807,16 @@ async fn list_workspace_roots(
|
|
|
2806
2807
|
|
|
2807
2808
|
let mut workspaces = workspaces_by_path
|
|
2808
2809
|
.into_iter()
|
|
2809
|
-
.map(|(path, (chat_count, updated_at))|
|
|
2810
|
+
.map(|(path, (chat_count, updated_at))| {
|
|
2811
|
+
(
|
|
2812
|
+
WorkspaceSummary {
|
|
2813
|
+
path,
|
|
2814
|
+
chat_count,
|
|
2815
|
+
updated_at: (updated_at > 0).then_some(updated_at),
|
|
2816
|
+
},
|
|
2817
|
+
updated_at,
|
|
2818
|
+
)
|
|
2819
|
+
})
|
|
2810
2820
|
.collect::<Vec<_>>();
|
|
2811
2821
|
|
|
2812
2822
|
workspaces.sort_by(|(left, left_updated_at), (right, right_updated_at)| {
|