Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
294 changes: 208 additions & 86 deletions CLAUDE.md

Large diffs are not rendered by default.

160 changes: 108 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,33 @@
# system-test-sandbox

macOS 桌面自动化沙箱 — 让 Agent CLI 工具模拟人类操作任意 macOS 应用和 CLI,并获取截图反馈
macOS 桌面自动化沙箱 — 支持多实例管理,通过 CLI 命令启动独立沙箱窗口,在其中运行任意 CLI macOS 应用,模拟人类操作并获取截图反馈

## 特性

- **多实例管理**:`sandbox-cli start --cli "claude"` 一键启动沙箱,返回唯一 ID
- **零侵入**:目标应用不需要任何适配,所有操作在 OS 层面完成
- **窗口级截图**:ScreenCaptureKit 只截取沙箱窗口,不需要窗口在前台
- **窗口级截图**:ScreenCaptureKit 按窗口 ID 截图,不需要窗口在前台
- **双协议**:MCP (Agent CLI 原生) + HTTP (通用调用)
- **可复用**:不限于特定项目,任何 macOS 应用/CLI 都能用

## 架构

```
Agent CLI (Claude Code / OpenCode)
│ MCP / HTTP
┌──────────────────────────┐
│ Sandbox Host (Tauri) │
│ │
│ ┌────────────────────┐ │
│ │ Sandbox Window │ │
│ │ ┌──────┐ ┌──────┐│ │
│ │ │ CLI │ │ App ││ │
│ │ │(PTY) │ │(嵌入)││ │
│ │ └──────┘ └──────┘│ │
│ └────────────────────┘ │
│ │
│ CGEvent · AXUIElement │
│ ScreenCaptureKit │
│ PTY · NSWorkspace │
└──────────────────────────┘
sandbox-cli start --cli "claude"
├─ 生成沙箱 ID, 分配端口
├─ 启动 Tauri 窗口进程 (含内嵌 HTTP API)
├─ 在 xterm.js 终端中运行 claude (PTY)
└─ 写入注册中心 ~/.sandbox/instances/<id>.json

sandbox-cli screenshot <id>
├─ 读取注册中心, 获取端口
└─ GET http://127.0.0.1:<port>/screenshot → PNG

sandbox-cli close <id>
├─ 读取注册中心
├─ POST http://127.0.0.1:<port>/shutdown
└─ 清理注册信息, 终止关联进程
```

## 快速开始
Expand All @@ -41,52 +39,76 @@ Agent CLI (Claude Code / OpenCode)
git clone https://github.com/your-org/system-test-sandbox.git
cd system-test-sandbox

# 构建
# 构建 Tauri 应用 + CLI
cd sandbox-web && pnpm install && pnpm build && cd ..
cargo build --release

# 前端依赖
cd sandbox-web && pnpm install && cd ..
```

### 启动沙箱

```bash
# CLI 模式
cargo run -p sandbox-cli -- serve --port 5801
# 启动沙箱,运行 Claude Code
sandbox-cli start --cli "claude"
# → 打开 "System Test Sandbox" 窗口
# → xterm.js 终端中运行 claude
# → 输出: Sandbox started: abc123

# 启动沙箱,运行 macOS 应用
sandbox-cli start --app "/Applications/cc-switch.app"
# → 打开沙箱窗口,启动 cc-switch
# → 输出: Sandbox started: def456

# 启动沙箱,运行带参数的 CLI
sandbox-cli start --cli "npm" --args "run" "test"
```

### 管理沙箱

# Tauri 桌面模式
cd sandbox-web && pnpm dev
cargo run -p system-test-sandbox
```bash
# 查看所有活跃沙箱
sandbox-cli list
# → ID TITLE KIND STATUS PORT CREATED
# → abc123 "claude" CLI Running 15801 2026-05-16 10:30
# → def456 "cc-switch" APP Running 15802 2026-05-16 10:31

# 截取沙箱截图
sandbox-cli screenshot abc123 -o sandbox.png

# 在沙箱内模拟操作
sandbox-cli click abc123 100 200 # 鼠标点击
sandbox-cli type abc123 "帮我写一个函数" # 输入文本
sandbox-cli key abc123 Return --modifiers cmd # 按键

# 关闭沙箱
sandbox-cli close abc123
```

### Agent 调用示例

**通过 HTTP API:**
**通过 HTTP API(每个沙箱独立端口):**

```bash
# 截图
curl http://127.0.0.1:5801/screenshot -o sandbox.png
# 获取沙箱信息
curl http://127.0.0.1:15801/health

# 启动 CLI
curl -X POST http://127.0.0.1:5801/cli/spawn \
-H "Content-Type: application/json" \
-d '{"command": "hiboss", "args": ["start"]}'
# 截图
curl http://127.0.0.1:15801/screenshot -o sandbox.png

# 鼠标点击
curl -X POST http://127.0.0.1:5801/input/click \
curl -X POST http://127.0.0.1:15801/input/click \
-H "Content-Type: application/json" \
-d '{"x": 100, "y": 200}'

# 键盘输入
curl -X POST http://127.0.0.1:5801/input/type \
curl -X POST http://127.0.0.1:15801/input/type \
-H "Content-Type: application/json" \
-d '{"text": "Hello World"}'

# 列出窗口
curl http://127.0.0.1:5801/windows | jq
curl http://127.0.0.1:15801/windows | jq

# 读取 UI 树
curl http://127.0.0.1:5801/ui/inspect/12345 | jq
curl http://127.0.0.1:15801/ui/inspect/12345 | jq
```

**通过 MCP(Claude Code):**
Expand All @@ -97,8 +119,8 @@ curl http://127.0.0.1:5801/ui/inspect/12345 | jq
{
"mcpServers": {
"mac-sandbox": {
"command": "sandbox",
"args": ["serve", "--mcp"]
"command": "sandbox-cli",
"args": ["mcp-serve"]
}
}
}
Expand All @@ -107,10 +129,11 @@ curl http://127.0.0.1:5801/ui/inspect/12345 | jq
然后 Agent 可以直接调用:

```
screenshot() → 获取沙箱截图
click(100, 200) → 点击指定坐标
type_text("hello") → 输入文本
spawn_cli("hiboss start") → 启动 CLI
start_sandbox(cli="claude") → 启动沙箱,返回 ID
screenshot(sandbox_id="abc123") → 获取沙箱截图
click(100, 200, sandbox_id="abc123") → 点击指定坐标
type_text("hello", sandbox_id="abc123") → 输入文本
close_sandbox("abc123") → 关闭沙箱
```

## macOS 权限
Expand All @@ -122,12 +145,45 @@ spawn_cli("hiboss start") → 启动 CLI

## 技术栈

- Rust + Tauri 2 (桌面框架)
- React 18 + TypeScript + Vite + TailwindCSS (前端)
- xterm.js (终端模拟)
- CoreGraphics (CGEvent 输入模拟)
- ApplicationServices (AXUIElement UI 检查)
- ScreenCaptureKit (窗口级截图)
| 项目属性 | 规范值 |
|---------|--------|
| 核心库 | Rust (Edition 2021, >=1.88), `sandbox-core` library crate |
| CLI | Rust, `sandbox-cli` binary crate |
| 桌面框架 | Tauri 2.x |
| 桌面前端 | React 18 + TS + Vite + TailwindCSS + xterm.js |
| 异步运行时 | tokio |
| macOS API | CoreGraphics (CGEvent), ApplicationServices (AXUIElement), ScreenCaptureKit |
| 包管理 | Cargo Workspace + pnpm |
| 测试 | cargo test (Rust) + vitest (TS) |
| 目标平台 | macOS (Apple Silicon 优先) |
| License | Apache 2.0 |

## 项目结构

```
system-test-sandbox/
├── Cargo.toml # Workspace 根
├── crates/
│ ├── sandbox-core/ # 自动化核心 (library)
│ │ └── src/
│ │ ├── automation/ # CGEvent + AXUIElement
│ │ ├── capture/ # ScreenCaptureKit 截图
│ │ ├── process/ # PTY + NSWorkspace 进程管理
│ │ ├── sandbox/ # 沙箱窗口管理 (多实例)
│ │ ├── instance/ # 实例注册中心
│ │ └── server/ # HTTP API 服务器
│ └── sandbox-cli/ # CLI 工具
│ └── src/
│ ├── main.rs # start/list/close + 子命令
│ ├── client.rs # HTTP 客户端
│ └── mcp_server.rs # MCP 服务器
├── sandbox-web/ # 沙箱窗口前端
│ └── src/
│ ├── main.tsx, api.ts
│ └── components/
├── src-tauri/ # macOS 宿主应用 (Tauri)
└── docs/ # 设计文档 + 任务管理
```

## License

Expand Down
3 changes: 3 additions & 0 deletions docs/task/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,6 @@
| Phase 2 | [phase-2-server.md](./phase-2-server.md) | HTTP API + MCP Server |
| Phase 3 | [phase-3-ui-inspect.md](./phase-3-ui-inspect.md) | AXUIElement UI 检查 |
| Phase 4 | [phase-4-advanced.md](./phase-4-advanced.md) | 高级特性:多窗口、录制回放、测试框架 |
| Phase 5 | [phase-5-multi-instance.md](./phase-5-multi-instance.md) | 沙箱多实例管理:start/list/close + 注册中心 |
| Phase 6 | [phase-6-gui-support.md](./phase-6-gui-support.md) | GUI 应用支持 + 前端 API 集成 |
| Phase 7 | [phase-7-integration.md](./phase-7-integration.md) | 集成测试 + MCP 更新 + 文档 |
45 changes: 45 additions & 0 deletions docs/task/phase-5-multi-instance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Phase 5: 沙箱多实例管理

> 目标:实现多实例沙箱管理系统,支持 `start --cli/--app`、`list`、`close` 命令,每个沙箱拥有唯一 ID 和独立 HTTP API。

## 任务清单

| 任务 ID | 描述 | 层 |
|---------|------|----|
| P5-01 | 实例注册中心:`InstanceRegistry` + `SandboxInstance` + ID 生成 (`sandbox-core/src/instance.rs`) | Rust |
| P5-02 | 增强 Sandbox struct:添加 id/port/kind/start_time 字段,支持多实例 | Rust |
| P5-03 | 将 HTTP 服务器从 sandbox-cli 迁移到 sandbox-core:库化 server.rs,添加 PTY 端点 | Rust |
| P5-04 | HTTP 客户端模块:`SandboxClient` 封装 reqwest 调用 (`sandbox-cli/src/client.rs`) | Rust |
| P5-05 | 新增 CLI 命令:`start --cli/--app`、`list`、`close <id>` | Rust |
| P5-06 | 实例作用域操作:screenshot/click/type/key 支持 `--id` 参数 | Rust |
| P5-07 | Tauri 多实例支持:CLI 参数解析 + 内嵌 HTTP 服务器 + 关闭清理 | Rust |
| P5-08 | workspace Cargo.toml:添加 reqwest、uuid 依赖 | Rust |

## 架构决策

每个沙箱实例 = 一个独立的 Tauri 窗口进程,拥有:
- 唯一 ID (8 字符 hex)
- 内嵌 axum HTTP 服务器(随机端口)
- 文件系统注册:`~/.sandbox/instances/<id>.json`

CLI 通过注册中心发现实例,通过 HTTP 通信。

```
sandbox-cli start --cli "claude"
├─ 1. 生成 sandbox ID (generate_instance_id)
├─ 2. 分配可用端口 (bind 127.0.0.1:0)
├─ 3. 启动 Tauri: open -n -a "System Test Sandbox" --args --sandbox-id=<id> --port=<port> --mode=cli --cmd=claude
├─ 4. 轮询 http://127.0.0.1:<port>/health 等待就绪
├─ 5. 写入 ~/.sandbox/instances/<id>.json
└─ 6. 打印 Sandbox ID
```

## 验收标准

- `sandbox-cli start --cli "echo hello"` 打开沙箱窗口并返回 ID
- `sandbox-cli list` 列出所有活跃沙箱及其状态
- `sandbox-cli screenshot <id> -o test.png` 截取指定沙箱截图
- `sandbox-cli click <id> 100 200` 在指定沙箱内模拟点击
- `sandbox-cli close <id>` 关闭沙箱,清理注册信息
- 所有现有测试仍通过
- 多个沙箱可同时运行,互不干扰
33 changes: 33 additions & 0 deletions docs/task/phase-6-gui-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Phase 6: GUI 应用支持与前端集成

> 目标:完善沙箱对 macOS GUI 应用的支持,并将前端所有 stub handler 替换为真实 API 调用。

## 任务清单

| 任务 ID | 描述 | 层 |
|---------|------|----|
| P6-01 | Tauri --cli 模式:启动时在 PTY 中运行 CLI,输出流式传输到前端 | Rust + TS |
| P6-02 | Tauri --app 模式:启动 macOS 应用,发现窗口,关联到沙箱 | Rust |
| P6-03 | 前端 API 客户端层:fetch 封装所有沙箱操作 (`sandbox-web/src/api.ts`) | TS |
| P6-04 | 连接 main.tsx:将所有 stub handler 替换为真实 API 调用 | TS |
| P6-05 | 连接 Terminal 组件:PTY 读写通过 API 轮询 | TS |
| P6-06 | 更新 StatusBar 组件:显示沙箱 ID、端口、进程信息 | TS |

## GUI 应用支持说明

真实的窗口嵌入(将外部 app 的 NSWindow 嵌入沙箱窗口)需要私有 macOS API,不可行。替代方案:

- 通过 NSWorkspace 启动应用
- 通过 ScreenCaptureKit 发现应用窗口
- 将应用窗口定位在沙箱窗口附近
- 通过 CGEvent + AXUIElement 提供完整的自动化交互
- 沙箱关闭时自动终止关联应用

## 验收标准

- `sandbox-cli start --cli "claude"` 在 xterm.js 中显示 Claude Code 交互界面
- `sandbox-cli start --app "/Applications/TextEdit.app"` 启动应用并关联
- 前端可实时查看沙箱状态(ID、运行进程、截图)
- 前端 Terminal 支持 PTY 输入输出
- ControlPanel 所有按钮产生真实的沙箱操作
- RecordControls 录制/回放功能可用
23 changes: 23 additions & 0 deletions docs/task/phase-7-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Phase 7: 集成测试与发布

> 目标:完善测试覆盖,更新 MCP 服务器,完成端到端验证和文档更新。

## 任务清单

| 任务 ID | 描述 | 层 |
|---------|------|----|
| P7-01 | 实例注册中心单元测试:CRUD、并发访问、过期清理 | Rust |
| P7-02 | CLI 集成测试:SandboxClient + mock HTTP server | Rust |
| P7-03 | MCP 服务器更新:添加 list_sandboxes、start_sandbox、close_sandbox 工具 | Rust |
| P7-04 | 端到端冒烟测试:start --cli echo → screenshot → close | Manual + CI |
| P7-05 | 更新文档:CLAUDE.md、README.md、docs/task/* | Docs |

## 验收标准

- `cargo test --all` 全部通过(含新增测试)
- `cargo fmt --all -- --check` + `cargo clippy --all-targets` 无警告
- `cargo check --all-targets` 通过
- `pnpm typecheck` + `pnpm format:check` + `pnpm test:unit` 通过
- MCP 工具 `list_sandboxes`、`start_sandbox`、`close_sandbox` 可用
- 端到端流程:start → list → screenshot → close 全程正常
- CLAUDE.md、README.md、docs/task/* 文档已更新
Loading
Loading