# Commands and files

> Run shell commands in a sandbox and move files in and out of it

Both need a sandbox that is `starting` or `running`. Calls made while it starts wait for it; a stopped one answers `409 sandbox_not_running`, so resume it first.

## Run a command

A command runs in a shell, so pipes and `&&` work. It runs in `/home/user` unless you pass `cwd`, relative to it.

```typescript
const { result } = await zavu.sandboxes.commands.run(sandboxId, {
  command: "pip install requests && python3 main.py",
  cwd: "app",
  timeoutSeconds: 90,
});

if (result?.timedOut) console.log("killed after 90 s");
console.log(result?.exitCode, result?.stdout, result?.stderr);
```

```bash
curl -X POST https://api.zavu.dev/v1/sandboxes/$SANDBOX_ID/commands \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command": "pip install requests && python3 main.py", "cwd": "app", "timeoutSeconds": 90}'
```

The call waits up to `timeoutSeconds` (default 30, max 120). Past it the command is killed, `timedOut` is `true` and `exitCode` is `null`. Output longer than the response holds is cut, and `stdoutTruncated` or `stderrTruncated` says so.

## Run a long command in the background

For anything longer than two minutes (a build, a test suite, a server), pass `background: true`. The answer is `202` with a command `id`, at once.

```typescript
const started = await zavu.sandboxes.commands.run(sandboxId, {
  command: "npm ci && npm test",
  background: true,
});
const commandId = started.command!.id;

// Later: its status and the last 1 MiB of each stream.
const { command } = await zavu.sandboxes.commands.retrieve(commandId, { sandboxId });
console.log(command.status, command.exitCode); // "running" null, then "finished" 0
```

```bash
curl -X POST https://api.zavu.dev/v1/sandboxes/$SANDBOX_ID/commands \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command": "npm ci && npm test", "background": true}'

curl https://api.zavu.dev/v1/sandboxes/$SANDBOX_ID/commands/$COMMAND_ID \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

`status` is `running`, `finished`, or `unknown` when the machine's process supervisor restarted and lost track of it; the output shown then is what was written to disk.

> **Warning:**
> A command is never retried for you. If a call fails mid-way, the command may still be running inside the machine.

## Files

Read, write, list and delete files under `/home/user` or `/tmp`, up to 5 MiB each. A relative path is relative to `/home/user`.

```typescript
await zavu.sandboxes.files.write(sandboxId, {
  path: "app/config.json",
  content: JSON.stringify({ debug: true }),
});

const { file } = await zavu.sandboxes.files.read(sandboxId, { path: "app/config.json" });
console.log(file.content);

// Binary content goes as base64, both ways.
const { file: image } = await zavu.sandboxes.files.read(sandboxId, {
  path: "out/chart.png",
  encoding: "base64",
});

const { files, truncated } = await zavu.sandboxes.files.list(sandboxId, { path: "app" });

await zavu.sandboxes.files.delete(sandboxId, { path: "out", recursive: true });
```

```bash
curl -X PUT https://api.zavu.dev/v1/sandboxes/$SANDBOX_ID/files \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"path": "app/config.json", "content": "{\"debug\": true}"}'

curl "https://api.zavu.dev/v1/sandboxes/$SANDBOX_ID/files?path=app/config.json" \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"

curl "https://api.zavu.dev/v1/sandboxes/$SANDBOX_ID/files/list?path=app" \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"

curl -X DELETE "https://api.zavu.dev/v1/sandboxes/$SANDBOX_ID/files?path=out&recursive=true" \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY"
```

A listing is one level, sorted by name, and `truncated` is `true` past 10,000 entries. Deleting a directory needs `recursive=true`. For anything bigger than 5 MiB, fetch it from inside the machine with a command (`curl`, `git clone`) instead of through the API.

## Run a script at start

`setupScript` on create runs with bash once the machine is ready, as its user, with passwordless sudo. It runs in the background: it does not delay the start, and its output is not returned, so write to a file if you need it.

```typescript
await zavu.sandboxes.create({
  setupScript: "#!/bin/bash\ngit clone https://github.com/acme/app && cd app && npm ci > /tmp/setup.log 2>&1",
});
```

```bash
curl -X POST https://api.zavu.dev/v1/sandboxes \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"setupScript": "#!/bin/bash\ngit clone https://github.com/acme/app && cd app && npm ci > /tmp/setup.log 2>&1"}'
```

To start ready every time instead of setting up on each start, save the result as a [snapshot](https://www.zavu.dev/docs/guides/sandboxes/snapshots-and-environments).
