Skip to content

Commit dc5f287

Browse files
authored
Merge pull request #1 from ZN-Ice/feat/release-script-and-ci-fix
feat(build): 添加 release.sh 构建脚本 + 修复 CI
2 parents 8031f18 + 93d16a7 commit dc5f287

4 files changed

Lines changed: 327 additions & 5 deletions

File tree

.github/workflows/ci.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -608,13 +608,16 @@ jobs:
608608
run: cargo build -p sandbox-cli --release
609609

610610
- name: 构建前端 + Tauri 桌面应用
611-
working-directory: sandbox-web
612-
run: pnpm tauri build
611+
run: |
612+
if ! cargo tauri --version &>/dev/null; then
613+
cargo install tauri-cli --version "^2" --locked
614+
fi
615+
cargo tauri build
613616
614617
- name: 整理构建产物
615618
run: |
616619
mkdir -p release
617-
cp target/release/sandbox-cli release/
620+
cp target/release/sandbox release/
618621
find target/release/bundle/macos -name "*.app" -maxdepth 1 -exec cp -R {} release/ \; 2>/dev/null || true
619622
find target/release/bundle/dmg -name "*.dmg" -maxdepth 1 -exec cp {} release/ \; 2>/dev/null || true
620623
echo "## 构建产物" >> "$GITHUB_STEP_SUMMARY"

.github/workflows/release.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,18 +72,23 @@ jobs:
7272
${{ runner.os }}-pnpm-
7373
7474
- name: 安装前端依赖
75+
working-directory: sandbox-web
7576
run: pnpm install --frozen-lockfile
7677

7778
- name: 构建 CLI 二进制 (release)
7879
run: cargo build -p sandbox-cli --release
7980

8081
- name: 构建前端 + Tauri 桌面应用
81-
run: pnpm tauri build
82+
run: |
83+
if ! cargo tauri --version &>/dev/null; then
84+
cargo install tauri-cli --version "^2" --locked
85+
fi
86+
cargo tauri build
8287
8388
- name: 整理发布产物
8489
run: |
8590
mkdir -p release
86-
cp target/release/sandbox-cli release/
91+
cp target/release/sandbox release/
8792
find target/release/bundle/dmg -name "*.dmg" -maxdepth 1 -exec cp {} release/ \; 2>/dev/null || true
8893
cd release
8994
find ../target/release/bundle/macos -name "*.app" -maxdepth 1 -exec zip -r "System Test Sandbox.app.zip" {} \; 2>/dev/null || true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ target/
66
# Build output
77
dist/
88
out/
9+
release/
910

1011
# Rust
1112
**/*.rs.bk

release.sh

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# ============================================================
5+
# system-test-sandbox — Release Build Script
6+
# ============================================================
7+
# Builds both the CLI binary and the macOS app, then packages
8+
# them into ./release/ for distribution.
9+
#
10+
# Prerequisites:
11+
# - Rust >= 1.88
12+
# - Node.js + pnpm
13+
# - macOS (Apple Silicon or Intel)
14+
# ============================================================
15+
16+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
17+
cd "$SCRIPT_DIR"
18+
19+
RELEASE_DIR="$SCRIPT_DIR/release"
20+
VERSION="0.1.0"
21+
22+
# --- helpers ---
23+
info() { echo "$*"; }
24+
ok() { echo "$*"; }
25+
err() { echo "$*" >&2; exit 1; }
26+
check() {
27+
if command -v "$1" &>/dev/null; then
28+
ok "$1 found: $(command -v "$1")"
29+
else
30+
err "$1 not found — please install $1"
31+
fi
32+
}
33+
34+
echo ""
35+
echo "=============================================="
36+
echo " system-test-sandbox v${VERSION} — Release Build"
37+
echo "=============================================="
38+
echo ""
39+
40+
# --- step 1: check prerequisites ---
41+
info "Checking prerequisites..."
42+
check rustc
43+
check cargo
44+
check node
45+
check pnpm
46+
ok "All prerequisites met"
47+
48+
# --- step 2: build frontend ---
49+
echo ""
50+
info "Building frontend (sandbox-web)..."
51+
cd "$SCRIPT_DIR/sandbox-web"
52+
53+
if [ ! -d "node_modules" ]; then
54+
pnpm install --frozen-lockfile
55+
fi
56+
pnpm build
57+
ok "Frontend built -> sandbox-web/dist/"
58+
59+
# --- step 3: build CLI binary ---
60+
echo ""
61+
info "Building CLI binary (release)..."
62+
cd "$SCRIPT_DIR"
63+
cargo build --release -p sandbox-cli
64+
CLI_BIN="$SCRIPT_DIR/target/release/sandbox"
65+
ok "CLI binary built: $(du -h "$CLI_BIN" | cut -f1)"
66+
67+
# --- step 4: build Tauri app ---
68+
echo ""
69+
info "Building Tauri desktop app..."
70+
71+
cd "$SCRIPT_DIR"
72+
73+
# Try cargo-tauri if installed, otherwise install it
74+
if ! cargo tauri --version &>/dev/null; then
75+
info "Installing tauri-cli (one-time) ..."
76+
cargo install tauri-cli --version "^2"
77+
fi
78+
79+
APP_NAME="System Test Sandbox"
80+
cargo tauri build --target universal-apple-darwin 2>/dev/null || cargo tauri build
81+
82+
TAURI_BUILD_DIR="$SCRIPT_DIR/target/release/bundle/macos"
83+
APP_BUNDLE="$TAURI_BUILD_DIR/${APP_NAME}.app"
84+
DMG_PATH="$SCRIPT_DIR/target/release/bundle/dmg"
85+
86+
if [ -d "$APP_BUNDLE" ]; then
87+
ok "Tauri app built: $APP_BUNDLE"
88+
else
89+
# Fallback: manually assemble .app from cargo binary
90+
info "Manually assembling .app bundle..."
91+
cargo build --release -p system-test-sandbox
92+
APP_BUNDLE="$TAURI_BUILD_DIR/${APP_NAME}.app"
93+
mkdir -p "$APP_BUNDLE/Contents/MacOS"
94+
mkdir -p "$APP_BUNDLE/Contents/Resources"
95+
cp "$SCRIPT_DIR/target/release/system-test-sandbox" "$APP_BUNDLE/Contents/MacOS/"
96+
# Copy Info.plist if exists
97+
if [ -f "$SCRIPT_DIR/src-tauri/Info.plist" ]; then
98+
cp "$SCRIPT_DIR/src-tauri/Info.plist" "$APP_BUNDLE/Contents/"
99+
fi
100+
# Copy icons
101+
if [ -f "$SCRIPT_DIR/src-tauri/icons/icon.icns" ]; then
102+
cp "$SCRIPT_DIR/src-tauri/icons/icon.icns" "$APP_BUNDLE/Contents/Resources/"
103+
fi
104+
ok ".app bundle manually assembled"
105+
fi
106+
107+
# --- step 5: assemble release folder ---
108+
echo ""
109+
info "Assembling release artifacts -> $RELEASE_DIR"
110+
rm -rf "$RELEASE_DIR"
111+
mkdir -p "$RELEASE_DIR"
112+
113+
# CLI binary
114+
cp "$CLI_BIN" "$RELEASE_DIR/sandbox"
115+
chmod +x "$RELEASE_DIR/sandbox"
116+
117+
# Fix rpath for Swift Concurrency (required by ScreenCaptureKit)
118+
install_name_tool -add_rpath /usr/lib/swift "$RELEASE_DIR/sandbox" 2>/dev/null || true
119+
ok "sandbox CLI binary"
120+
121+
# Tauri .app
122+
if [ -d "$APP_BUNDLE" ]; then
123+
cp -R "$APP_BUNDLE" "$RELEASE_DIR/"
124+
# Fix rpath for the app binary too
125+
APP_EXEC="$RELEASE_DIR/${APP_NAME}.app/Contents/MacOS/system-test-sandbox"
126+
if [ -f "$APP_EXEC" ]; then
127+
install_name_tool -add_rpath /usr/lib/swift "$APP_EXEC" 2>/dev/null || true
128+
fi
129+
ok "$APP_NAME.app"
130+
fi
131+
132+
# DMG installer
133+
DMG_FILE=$(ls "$DMG_PATH"/*.dmg 2>/dev/null | head -1)
134+
if [ -n "$DMG_FILE" ]; then
135+
cp "$DMG_FILE" "$RELEASE_DIR/"
136+
ok "$(basename "$DMG_FILE")"
137+
fi
138+
139+
# Generate README (inline, see step 6)
140+
ok "Release artifacts ready"
141+
142+
# --- step 6: generate README ---
143+
echo ""
144+
info "Generating README.md..."
145+
146+
BUILD_DATE="$(date '+%Y-%m-%d %H:%M')"
147+
148+
cat > "$RELEASE_DIR/README.md" << 'RELEASEREADME'
149+
# System Test Sandbox — Release v0.1.0
150+
151+
macOS 桌面自动化沙箱。模拟鼠标/键盘操作、截图、读取 UI 元素树,通过 CLI 或 MCP 协议供 Agent 工具调用。
152+
153+
## 文件说明
154+
155+
```
156+
release/
157+
├── sandbox # CLI 工具(命令行)
158+
├── System Test Sandbox.app # macOS 桌面应用(Tauri)
159+
└── README.md # 本文件
160+
```
161+
162+
## 一、前置条件
163+
164+
| 依赖 | 版本要求 |
165+
|------|---------|
166+
| macOS | 14.0+ (Sonoma) |
167+
| 芯片 | Apple Silicon (M1–M4),Intel 也支持 |
168+
169+
### 必须授予的权限
170+
171+
> **没有这两个权限,sandbox 无法工作。**
172+
173+
1. **辅助功能 (Accessibility)**:用于 CGEvent 输入模拟 + AXUIElement UI 读取
174+
2. **屏幕录制 (Screen Recording)**:用于 ScreenCaptureKit 截图
175+
176+
授予方式:`系统设置 → 隐私与安全性 → 辅助功能 / 屏幕录制`,将 `sandbox` 或 `System Test Sandbox.app` 添加进去并勾选。
177+
178+
## 二、CLI 使用方法
179+
180+
### 启动 HTTP + MCP 服务(最常用)
181+
182+
```bash
183+
./sandbox serve --port 5801
184+
```
185+
186+
启动后可用端点:
187+
188+
```
189+
GET http://127.0.0.1:5801/health # 健康检查
190+
GET http://127.0.0.1:5801/screenshot # 截取沙箱窗口 (PNG)
191+
POST http://127.0.0.1:5801/input/click # 鼠标点击
192+
POST http://127.0.0.1:5801/input/type # 键盘输入
193+
POST http://127.0.0.1:5801/input/key # 按键
194+
POST http://127.0.0.1:5801/cli/spawn # 启动 CLI 进程
195+
POST http://127.0.0.1:5801/app/spawn # 启动 macOS 应用
196+
GET http://127.0.0.1:5801/windows # 列出窗口
197+
GET http://127.0.0.1:5801/processes # 列出进程
198+
GET http://127.0.0.1:5801/ui/inspect/:window # 读取 UI 树
199+
```
200+
201+
### 启动 MCP 服务(供 Claude Code / Codex 调用)
202+
203+
```bash
204+
./sandbox mcp-serve
205+
```
206+
207+
在 `.claude/settings.json` 中配置:
208+
209+
```json
210+
{
211+
"mcpServers": {
212+
"sandbox": {
213+
"command": "/absolute/path/to/release/sandbox",
214+
"args": ["mcp-serve"]
215+
}
216+
}
217+
}
218+
```
219+
220+
### 一次性命令
221+
222+
```bash
223+
# 截图
224+
./sandbox screenshot -o result.png
225+
226+
# 列出所有窗口
227+
./sandbox windows
228+
229+
# 模拟点击
230+
./sandbox click 500 300
231+
232+
# 模拟打字
233+
./sandbox type "Hello World"
234+
235+
# 模拟按键
236+
./sandbox key Return
237+
./sandbox key c --modifiers cmd
238+
239+
# 启动 App
240+
./sandbox spawn-app /Applications/Calculator.app
241+
242+
# 启动 CLI
243+
./sandbox spawn-cli ls -la
244+
245+
# 终止进程
246+
./sandbox kill 12345
247+
```
248+
249+
### curl 调用示例
250+
251+
```bash
252+
# 截图
253+
curl http://127.0.0.1:5801/screenshot -o screenshot.png
254+
255+
# 点击
256+
curl -X POST http://127.0.0.1:5801/input/click \
257+
-H "Content-Type: application/json" \
258+
-d '{"x": 100, "y": 200, "button": "left"}'
259+
260+
# 输入文字
261+
curl -X POST http://127.0.0.1:5801/input/type \
262+
-H "Content-Type: application/json" \
263+
-d '{"text": "hello"}'
264+
265+
# 按回车
266+
curl -X POST http://127.0.0.1:5801/input/key \
267+
-H "Content-Type: application/json" \
268+
-d '{"key": "Return", "modifiers": []}'
269+
270+
# 启动 CLI
271+
curl -X POST http://127.0.0.1:5801/cli/spawn \
272+
-H "Content-Type: application/json" \
273+
-d '{"command": "ls", "args": ["-la"]}'
274+
```
275+
276+
## 三、桌面应用使用方法
277+
278+
1. 双击 `System Test Sandbox.app` 启动
279+
2. 应用窗口内嵌 xterm.js 终端,可直接运行 CLI 命令
280+
3. 顶部状态栏提供截图按钮
281+
282+
## 四、常见问题
283+
284+
**Q: 截图全黑?**
285+
A: 检查「屏幕录制」权限是否已授予。
286+
287+
**Q: 点击/输入无效?**
288+
A: 检查「辅助功能」权限是否已授予。
289+
290+
**Q: `serve` 端口被占用?**
291+
A: 使用 `./sandbox serve --port 5802` 更换端口。
292+
293+
**Q: MCP 连接失败?**
294+
A: 确认 `settings.json` 中的 `command` 路径是绝对路径。
295+
296+
---
297+
298+
**版本**: v0.1.0 | **构建时间**: __BUILD_DATE__
299+
RELEASEREADME
300+
301+
# Inject build date
302+
sed -i '' "s/__BUILD_DATE__/${BUILD_DATE}/" "$RELEASE_DIR/README.md"
303+
304+
ok "README.md generated"
305+
306+
# --- done ---
307+
echo ""
308+
echo "=============================================="
309+
echo " Release v${VERSION} built successfully!"
310+
echo " Artifacts -> $RELEASE_DIR"
311+
echo "=============================================="
312+
ls -lh "$RELEASE_DIR"
313+
echo ""

0 commit comments

Comments
 (0)