CLI Agent Hooks have access to two objects for interacting with the execution environment:
- **`ctx.workspace`** — read and write files in the mounted workspace volume
- **`ctx.computer`** — execute commands and inspect the container
Both are available in Create and Next Hooks when `sandbox.yao` is configured.
---
## `ctx.workspace`
The workspace is a persistent volume shared between the container and your Hooks. Claude Code reads and writes files inside it during execution. Your Hooks use `ctx.workspace` to prepare inputs before Claude runs and collect outputs after.
### `ReadFile(path)`
Read a file from the workspace. Returns the file content as a string.
```typescript
const content = ctx.workspace.ReadFile("output.json");
const parsed = JSON.parse(content);
```
### `WriteFile(path, content)`
Write a string to a file. Creates the file if it does not exist, overwrites if it does.
```typescript
ctx.workspace.WriteFile("context.md", `
# Task Context
## Requirement
${requirement}
## Constraints
- Use TypeScript
- No external dependencies
`);
```
### `ReadDir(path?)`
List directory entries. Returns an array of `{ name, is_dir, size? }` objects. Defaults to the workspace root if `path` is omitted.
```typescript
const entries = ctx.workspace.ReadDir("src/");
for (const entry of entries) {
if (!entry.is_dir && entry.name.endsWith(".ts")) {
ctx.Send(`Generated: ${entry.name} (${entry.size} bytes)`);
}
}
```
### `MkdirAll(path)`
Create a directory and all intermediate directories.
```typescript
ctx.workspace.MkdirAll("src/components");
```
### `Remove(path)`
Remove a single file.
```typescript
ctx.workspace.Remove("temp/scratch.txt");
```
### `RemoveAll(path)`
Remove a file or directory and all its contents recursively.
```typescript
ctx.workspace.RemoveAll("temp/");
```
### `Rename(oldName, newName)`
Rename or move a file or directory.
```typescript
ctx.workspace.Rename("output/draft.ts", "src/main.ts");
```
### `Copy(src, dst)`
Copy a file or directory within the workspace.
```typescript
ctx.workspace.Copy("templates/base.ts", "src/base.ts");
```
### `Stat(path)`
Get file metadata. Returns `{ name, size, is_dir, mode, mtime }`.
```typescript
const info = ctx.workspace.Stat("output.json");
if (!info.is_dir && info.size > 0) {
// file exists and has content
}
```
### `Exists(path)`
Check whether a file or directory exists. Returns `true` or `false`.
```typescript
if (ctx.workspace.Exists("package.json")) {
ctx.Send("Node project detected");
}
```
---
## `ctx.computer`
`ctx.computer` operates on the container itself — running commands, accessing VNC, and proxying ports. It complements `ctx.workspace`: workspace deals with files, computer deals with processes and system state.
### `ctx.computer.id`
The Computer ID (Box ID for containers, Node ID for hosts).
```typescript
ctx.Send(`Running on: ${ctx.computer.id}`);
```
### `Exec(cmd)`
Execute a shell command inside the container. Returns `{ stdout, stderr, exit_code }`.
```typescript
// String form — split on whitespace
const result = ctx.computer.Exec("git log --oneline -5");
// Array form — exact arguments, safe for paths with spaces
const result = ctx.computer.Exec(["cat", "/etc/os-release"]);
if (result.exit_code !== 0) {
ctx.Send(`Failed: ${result.stderr}`);
return { data: { status: "error" } };
}
ctx.Send(result.stdout);
```
### `VNC()`
Returns the VNC WebSocket URL. Throws if VNC is not available on the node — only call this when `computer.vnc` is enabled in `sandbox.yao`.
```typescript
const vncUrl = ctx.computer.VNC();
ctx.Send(`Desktop: ${vncUrl}`);
```
### `Proxy(port, path?)`
Returns a proxied URL to a port inside the container.
```typescript
const previewUrl = ctx.computer.Proxy(3000);
const apiUrl = ctx.computer.Proxy(8080, "/api/status");
```
### `Info()`
Returns metadata about the 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 only:
box_id: "box-456",
container_id: "sha256:...",
image: "yaoapp/tai-sandbox-claude:latest",
policy: "session"
}
*/
```
---
## Summary
| API | Returns | Throws on error |
|-----|---------|-----------------|
| `ctx.workspace.ReadFile(path)` | `string` | Yes |
| `ctx.workspace.WriteFile(path, content)` | `void` | Yes |
| `ctx.workspace.ReadDir(path?)` | `{ name, is_dir, size? }[]` | Yes |
| `ctx.workspace.MkdirAll(path)` | `void` | Yes |
| `ctx.workspace.Remove(path)` | `void` | Yes |
| `ctx.workspace.RemoveAll(path)` | `void` | Yes |
| `ctx.workspace.Rename(old, new)` | `void` | Yes |
| `ctx.workspace.Copy(src, dst)` | `void` | Yes |
| `ctx.workspace.Stat(path)` | `{ name, size, is_dir, mode, mtime }` | Yes |
| `ctx.workspace.Exists(path)` | `boolean` | No |
| `ctx.computer.id` | `string` | — |
| `ctx.computer.Exec(cmd)` | `{ stdout, stderr, exit_code }` | Yes |
| `ctx.computer.VNC()` | `string` | Yes |
| `ctx.computer.Proxy(port, path?)` | `string` | Yes |
| `ctx.computer.Info()` | object | Yes |
---
## What's Next
You know the APIs. Now see how Create and Next Hooks use them together.
→ **[Hook with CLI](./hook-with-cli)**