From 4025647b7d71b98f6122969799d8ad54393cf9d5 Mon Sep 17 00:00:00 2001 From: ZN-ice Date: Sat, 16 May 2026 11:08:22 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(build):=20=E6=B7=BB=E5=8A=A0=20release?= =?UTF-8?q?.sh=20=E6=9E=84=E5=BB=BA=E8=84=9A=E6=9C=AC=E5=92=8C=20release/?= =?UTF-8?q?=20=E7=9B=AE=E5=BD=95=E5=88=B0=20gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加本地构建发布脚本,自动编译 CLI 二进制、Tauri .app 和 DMG, 打包到 release/ 目录并生成 README.md 使用说明。 Co-Authored-By: Claude Opus 4.7 --- .gitignore | 1 + release.sh | 313 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 314 insertions(+) create mode 100755 release.sh diff --git a/.gitignore b/.gitignore index 34dbcb8..9af5f33 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ target/ # Build output dist/ out/ +release/ # Rust **/*.rs.bk diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..f05651a --- /dev/null +++ b/release.sh @@ -0,0 +1,313 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================ +# system-test-sandbox — Release Build Script +# ============================================================ +# Builds both the CLI binary and the macOS app, then packages +# them into ./release/ for distribution. +# +# Prerequisites: +# - Rust >= 1.88 +# - Node.js + pnpm +# - macOS (Apple Silicon or Intel) +# ============================================================ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$SCRIPT_DIR" + +RELEASE_DIR="$SCRIPT_DIR/release" +VERSION="0.1.0" + +# --- helpers --- +info() { echo " ➜ $*"; } +ok() { echo " ✓ $*"; } +err() { echo " ✗ $*" >&2; exit 1; } +check() { + if command -v "$1" &>/dev/null; then + ok "$1 found: $(command -v "$1")" + else + err "$1 not found — please install $1" + fi +} + +echo "" +echo "==============================================" +echo " system-test-sandbox v${VERSION} — Release Build" +echo "==============================================" +echo "" + +# --- step 1: check prerequisites --- +info "Checking prerequisites..." +check rustc +check cargo +check node +check pnpm +ok "All prerequisites met" + +# --- step 2: build frontend --- +echo "" +info "Building frontend (sandbox-web)..." +cd "$SCRIPT_DIR/sandbox-web" + +if [ ! -d "node_modules" ]; then + pnpm install --frozen-lockfile +fi +pnpm build +ok "Frontend built -> sandbox-web/dist/" + +# --- step 3: build CLI binary --- +echo "" +info "Building CLI binary (release)..." +cd "$SCRIPT_DIR" +cargo build --release -p sandbox-cli +CLI_BIN="$SCRIPT_DIR/target/release/sandbox" +ok "CLI binary built: $(du -h "$CLI_BIN" | cut -f1)" + +# --- step 4: build Tauri app --- +echo "" +info "Building Tauri desktop app..." + +cd "$SCRIPT_DIR" + +# Try cargo-tauri if installed, otherwise install it +if ! cargo tauri --version &>/dev/null; then + info "Installing tauri-cli (one-time) ..." + cargo install tauri-cli --version "^2" +fi + +APP_NAME="System Test Sandbox" +cargo tauri build --target universal-apple-darwin 2>/dev/null || cargo tauri build + +TAURI_BUILD_DIR="$SCRIPT_DIR/target/release/bundle/macos" +APP_BUNDLE="$TAURI_BUILD_DIR/${APP_NAME}.app" +DMG_PATH="$SCRIPT_DIR/target/release/bundle/dmg" + +if [ -d "$APP_BUNDLE" ]; then + ok "Tauri app built: $APP_BUNDLE" +else + # Fallback: manually assemble .app from cargo binary + info "Manually assembling .app bundle..." + cargo build --release -p system-test-sandbox + APP_BUNDLE="$TAURI_BUILD_DIR/${APP_NAME}.app" + mkdir -p "$APP_BUNDLE/Contents/MacOS" + mkdir -p "$APP_BUNDLE/Contents/Resources" + cp "$SCRIPT_DIR/target/release/system-test-sandbox" "$APP_BUNDLE/Contents/MacOS/" + # Copy Info.plist if exists + if [ -f "$SCRIPT_DIR/src-tauri/Info.plist" ]; then + cp "$SCRIPT_DIR/src-tauri/Info.plist" "$APP_BUNDLE/Contents/" + fi + # Copy icons + if [ -f "$SCRIPT_DIR/src-tauri/icons/icon.icns" ]; then + cp "$SCRIPT_DIR/src-tauri/icons/icon.icns" "$APP_BUNDLE/Contents/Resources/" + fi + ok ".app bundle manually assembled" +fi + +# --- step 5: assemble release folder --- +echo "" +info "Assembling release artifacts -> $RELEASE_DIR" +rm -rf "$RELEASE_DIR" +mkdir -p "$RELEASE_DIR" + +# CLI binary +cp "$CLI_BIN" "$RELEASE_DIR/sandbox" +chmod +x "$RELEASE_DIR/sandbox" + +# Fix rpath for Swift Concurrency (required by ScreenCaptureKit) +install_name_tool -add_rpath /usr/lib/swift "$RELEASE_DIR/sandbox" 2>/dev/null || true +ok "sandbox CLI binary" + +# Tauri .app +if [ -d "$APP_BUNDLE" ]; then + cp -R "$APP_BUNDLE" "$RELEASE_DIR/" + # Fix rpath for the app binary too + APP_EXEC="$RELEASE_DIR/${APP_NAME}.app/Contents/MacOS/system-test-sandbox" + if [ -f "$APP_EXEC" ]; then + install_name_tool -add_rpath /usr/lib/swift "$APP_EXEC" 2>/dev/null || true + fi + ok "$APP_NAME.app" +fi + +# DMG installer +DMG_FILE=$(ls "$DMG_PATH"/*.dmg 2>/dev/null | head -1) +if [ -n "$DMG_FILE" ]; then + cp "$DMG_FILE" "$RELEASE_DIR/" + ok "$(basename "$DMG_FILE")" +fi + +# Generate README (inline, see step 6) +ok "Release artifacts ready" + +# --- step 6: generate README --- +echo "" +info "Generating README.md..." + +BUILD_DATE="$(date '+%Y-%m-%d %H:%M')" + +cat > "$RELEASE_DIR/README.md" << 'RELEASEREADME' +# System Test Sandbox — Release v0.1.0 + +macOS 桌面自动化沙箱。模拟鼠标/键盘操作、截图、读取 UI 元素树,通过 CLI 或 MCP 协议供 Agent 工具调用。 + +## 文件说明 + +``` +release/ +├── sandbox # CLI 工具(命令行) +├── System Test Sandbox.app # macOS 桌面应用(Tauri) +└── README.md # 本文件 +``` + +## 一、前置条件 + +| 依赖 | 版本要求 | +|------|---------| +| macOS | 14.0+ (Sonoma) | +| 芯片 | Apple Silicon (M1–M4),Intel 也支持 | + +### 必须授予的权限 + +> **没有这两个权限,sandbox 无法工作。** + +1. **辅助功能 (Accessibility)**:用于 CGEvent 输入模拟 + AXUIElement UI 读取 +2. **屏幕录制 (Screen Recording)**:用于 ScreenCaptureKit 截图 + +授予方式:`系统设置 → 隐私与安全性 → 辅助功能 / 屏幕录制`,将 `sandbox` 或 `System Test Sandbox.app` 添加进去并勾选。 + +## 二、CLI 使用方法 + +### 启动 HTTP + MCP 服务(最常用) + +```bash +./sandbox serve --port 5801 +``` + +启动后可用端点: + +``` +GET http://127.0.0.1:5801/health # 健康检查 +GET http://127.0.0.1:5801/screenshot # 截取沙箱窗口 (PNG) +POST http://127.0.0.1:5801/input/click # 鼠标点击 +POST http://127.0.0.1:5801/input/type # 键盘输入 +POST http://127.0.0.1:5801/input/key # 按键 +POST http://127.0.0.1:5801/cli/spawn # 启动 CLI 进程 +POST http://127.0.0.1:5801/app/spawn # 启动 macOS 应用 +GET http://127.0.0.1:5801/windows # 列出窗口 +GET http://127.0.0.1:5801/processes # 列出进程 +GET http://127.0.0.1:5801/ui/inspect/:window # 读取 UI 树 +``` + +### 启动 MCP 服务(供 Claude Code / Codex 调用) + +```bash +./sandbox mcp-serve +``` + +在 `.claude/settings.json` 中配置: + +```json +{ + "mcpServers": { + "sandbox": { + "command": "/absolute/path/to/release/sandbox", + "args": ["mcp-serve"] + } + } +} +``` + +### 一次性命令 + +```bash +# 截图 +./sandbox screenshot -o result.png + +# 列出所有窗口 +./sandbox windows + +# 模拟点击 +./sandbox click 500 300 + +# 模拟打字 +./sandbox type "Hello World" + +# 模拟按键 +./sandbox key Return +./sandbox key c --modifiers cmd + +# 启动 App +./sandbox spawn-app /Applications/Calculator.app + +# 启动 CLI +./sandbox spawn-cli ls -la + +# 终止进程 +./sandbox kill 12345 +``` + +### curl 调用示例 + +```bash +# 截图 +curl http://127.0.0.1:5801/screenshot -o screenshot.png + +# 点击 +curl -X POST http://127.0.0.1:5801/input/click \ + -H "Content-Type: application/json" \ + -d '{"x": 100, "y": 200, "button": "left"}' + +# 输入文字 +curl -X POST http://127.0.0.1:5801/input/type \ + -H "Content-Type: application/json" \ + -d '{"text": "hello"}' + +# 按回车 +curl -X POST http://127.0.0.1:5801/input/key \ + -H "Content-Type: application/json" \ + -d '{"key": "Return", "modifiers": []}' + +# 启动 CLI +curl -X POST http://127.0.0.1:5801/cli/spawn \ + -H "Content-Type: application/json" \ + -d '{"command": "ls", "args": ["-la"]}' +``` + +## 三、桌面应用使用方法 + +1. 双击 `System Test Sandbox.app` 启动 +2. 应用窗口内嵌 xterm.js 终端,可直接运行 CLI 命令 +3. 顶部状态栏提供截图按钮 + +## 四、常见问题 + +**Q: 截图全黑?** +A: 检查「屏幕录制」权限是否已授予。 + +**Q: 点击/输入无效?** +A: 检查「辅助功能」权限是否已授予。 + +**Q: `serve` 端口被占用?** +A: 使用 `./sandbox serve --port 5802` 更换端口。 + +**Q: MCP 连接失败?** +A: 确认 `settings.json` 中的 `command` 路径是绝对路径。 + +--- + +**版本**: v0.1.0 | **构建时间**: __BUILD_DATE__ +RELEASEREADME + +# Inject build date +sed -i '' "s/__BUILD_DATE__/${BUILD_DATE}/" "$RELEASE_DIR/README.md" + +ok "README.md generated" + +# --- done --- +echo "" +echo "==============================================" +echo " Release v${VERSION} built successfully!" +echo " Artifacts -> $RELEASE_DIR" +echo "==============================================" +ls -lh "$RELEASE_DIR" +echo "" From 93d16a7289aaed254cae2296f25ddf7846c77fad Mon Sep 17 00:00:00 2001 From: ZN-ice Date: Sat, 16 May 2026 11:18:59 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(ci):=20=E4=BF=AE=E5=A4=8D=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E6=9E=84=E5=BB=BA=E9=AA=8C=E8=AF=81=20pnpm=20tauri=20?= =?UTF-8?q?build=20=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 用 cargo tauri build 替代 pnpm tauri build,解决 @tauri-apps/cli 未安装问题 - 修正二进制文件名 sandbox-cli → sandbox(与 Cargo.toml [[bin]] name 一致) - 修复 release.yml 前端依赖安装缺少 working-directory Co-Authored-By: Claude Opus 4.7 --- .github/workflows/ci.yml | 9 ++++++--- .github/workflows/release.yml | 9 +++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7dc9fe3..ec61f93 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -608,13 +608,16 @@ jobs: run: cargo build -p sandbox-cli --release - name: 构建前端 + Tauri 桌面应用 - working-directory: sandbox-web - run: pnpm tauri build + run: | + if ! cargo tauri --version &>/dev/null; then + cargo install tauri-cli --version "^2" --locked + fi + cargo tauri build - name: 整理构建产物 run: | mkdir -p release - cp target/release/sandbox-cli release/ + cp target/release/sandbox release/ find target/release/bundle/macos -name "*.app" -maxdepth 1 -exec cp -R {} release/ \; 2>/dev/null || true find target/release/bundle/dmg -name "*.dmg" -maxdepth 1 -exec cp {} release/ \; 2>/dev/null || true echo "## 构建产物" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e64293e..baec934 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,18 +72,23 @@ jobs: ${{ runner.os }}-pnpm- - name: 安装前端依赖 + working-directory: sandbox-web run: pnpm install --frozen-lockfile - name: 构建 CLI 二进制 (release) run: cargo build -p sandbox-cli --release - name: 构建前端 + Tauri 桌面应用 - run: pnpm tauri build + run: | + if ! cargo tauri --version &>/dev/null; then + cargo install tauri-cli --version "^2" --locked + fi + cargo tauri build - name: 整理发布产物 run: | mkdir -p release - cp target/release/sandbox-cli release/ + cp target/release/sandbox release/ find target/release/bundle/dmg -name "*.dmg" -maxdepth 1 -exec cp {} release/ \; 2>/dev/null || true cd release find ../target/release/bundle/macos -name "*.app" -maxdepth 1 -exec zip -r "System Test Sandbox.app.zip" {} \; 2>/dev/null || true