CLI Agent Hook 可通过两个对象与执行环境交互:
- **`ctx.workspace`** —— 读写已挂载的 workspace 卷中的文件
- **`ctx.computer`** —— 在容器内执行命令并查看容器状态
配置了 `sandbox.yao` 时,Create Hook 和 Next Hook 中均可使用这两个对象。
---
## `ctx.workspace`
workspace 是容器与 Hook 之间共享的持久化卷。Claude Code 在执行期间在其中读写文件。你的 Hook 使用 `ctx.workspace` 在 Claude 运行前准备输入,在运行后收集产出。
### `ReadFile(path)`
从 workspace 读取文件,返回字符串格式的文件内容。
```typescript
const content = ctx.workspace.ReadFile("output.json");
const parsed = JSON.parse(content);
```
### `WriteFile(path, content)`
将字符串写入文件。文件不存在时创建,存在时覆盖。
```typescript
ctx.workspace.WriteFile("context.md", `
# 任务上下文
## 需求
${requirement}
## 约束
- 使用 TypeScript
- 不引入外部依赖
`);
```
### `ReadDir(path?)`
列出目录项,返回 `{ name, is_dir, size? }` 对象数组。省略 `path` 时默认列出 workspace 根目录。
```typescript
const entries = ctx.workspace.ReadDir("src/");
for (const entry of entries) {
if (!entry.is_dir && entry.name.endsWith(".ts")) {
ctx.Send(`已生成:${entry.name}(${entry.size} 字节)`);
}
}
```
### `MkdirAll(path)`
创建目录及所有中间层目录。
```typescript
ctx.workspace.MkdirAll("src/components");
```
### `Remove(path)`
删除单个文件。
```typescript
ctx.workspace.Remove("temp/scratch.txt");
```
### `RemoveAll(path)`
递归删除文件或目录及其所有内容。
```typescript
ctx.workspace.RemoveAll("temp/");
```
### `Rename(oldName, newName)`
重命名或移动文件/目录。
```typescript
ctx.workspace.Rename("output/draft.ts", "src/main.ts");
```
### `Copy(src, dst)`
在 workspace 内复制文件或目录。
```typescript
ctx.workspace.Copy("templates/base.ts", "src/base.ts");
```
### `Stat(path)`
获取文件元数据,返回 `{ name, size, is_dir, mode, mtime }`。
```typescript
const info = ctx.workspace.Stat("output.json");
if (!info.is_dir && info.size > 0) {
// 文件存在且有内容
}
```
### `Exists(path)`
检查文件或目录是否存在,返回 `true` 或 `false`。
```typescript
if (ctx.workspace.Exists("package.json")) {
ctx.Send("检测到 Node 项目");
}
```
---
## `ctx.computer`
`ctx.computer` 操作容器本身 —— 执行命令、访问 VNC、代理端口。它与 `ctx.workspace` 互补:workspace 处理文件,computer 处理进程和系统状态。
### `ctx.computer.id`
Computer ID(容器对应 Box ID,宿主机对应 Node ID)。
```typescript
ctx.Send(`运行于:${ctx.computer.id}`);
```
### `Exec(cmd)`
在容器内执行 shell 命令,返回 `{ stdout, stderr, exit_code }`。
```typescript
// 字符串形式 —— 按空白符分割
const result = ctx.computer.Exec("git log --oneline -5");
// 数组形式 —— 精确参数,路径含空格时安全
const result = ctx.computer.Exec(["cat", "/etc/os-release"]);
if (result.exit_code !== 0) {
ctx.Send(`失败:${result.stderr}`);
return { data: { status: "error" } };
}
ctx.Send(result.stdout);
```
### `VNC()`
返回 VNC WebSocket URL。若节点上 VNC 不可用则抛出异常 —— 仅在 `sandbox.yao` 中启用了 `computer.vnc` 时调用。
```typescript
const vncUrl = ctx.computer.VNC();
ctx.Send(`桌面地址:${vncUrl}`);
```
### `Proxy(port, path?)`
返回容器内指定端口的代理 URL。
```typescript
const previewUrl = ctx.computer.Proxy(3000);
const apiUrl = ctx.computer.Proxy(8080, "/api/status");
```
### `Info()`
返回 Computer 的元数据。
```typescript
const info = ctx.computer.Info();
/*
{
kind: "box",
node_id: "node-abc",
tai_id: "tai-xyz",
status: "running",
system: {
os: "linux",
arch: "amd64",
hostname: "container-123",
num_cpu: 4,
shell: "/bin/sh"
},
// 仅 box:
box_id: "box-456",
container_id: "sha256:...",
image: "yaoapp/tai-sandbox-claude:latest",
policy: "session"
}
*/
```
---
## 汇总
| API | 返回值 | 出错时抛出 |
|-----|--------|-----------|
| `ctx.workspace.ReadFile(path)` | `string` | 是 |
| `ctx.workspace.WriteFile(path, content)` | `void` | 是 |
| `ctx.workspace.ReadDir(path?)` | `{ name, is_dir, size? }[]` | 是 |
| `ctx.workspace.MkdirAll(path)` | `void` | 是 |
| `ctx.workspace.Remove(path)` | `void` | 是 |
| `ctx.workspace.RemoveAll(path)` | `void` | 是 |
| `ctx.workspace.Rename(old, new)` | `void` | 是 |
| `ctx.workspace.Copy(src, dst)` | `void` | 是 |
| `ctx.workspace.Stat(path)` | `{ name, size, is_dir, mode, mtime }` | 是 |
| `ctx.workspace.Exists(path)` | `boolean` | 否 |
| `ctx.computer.id` | `string` | — |
| `ctx.computer.Exec(cmd)` | `{ stdout, stderr, exit_code }` | 是 |
| `ctx.computer.VNC()` | `string` | 是 |
| `ctx.computer.Proxy(port, path?)` | `string` | 是 |
| `ctx.computer.Info()` | object | 是 |
---
## 下一步
API 已全部了解。现在看 Create Hook 和 Next Hook 如何配合使用。
→ **[Hook 配合 CLI](./hook-with-cli)**