> ## Documentation Index
> Fetch the complete documentation index at: https://docs.declaw.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Filesystem

> Read, write, and watch files inside Declaw sandboxes. Each sandbox has an isolated ext4 rootfs.

Every sandbox has an independent ext4 rootfs — a copy of the base image. Writes in one sandbox never affect another. The filesystem API lets you read and write files, list directories, watch for changes, and upload or download data.

## Write a file

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    # Write text
    sbx.files.write("/workspace/hello.py", b"print('hello')")

    # Write from a string
    sbx.files.write("/workspace/config.json", b'{"key": "value"}')
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    await sbx.files.write('/workspace/hello.py', "print('hello')");
    ```
  </Tab>
</Tabs>

### Write multiple files at once

`write_files()` uploads multiple files in a single request.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from declaw import WriteEntry

    sbx.files.write_files([
        WriteEntry(path="/workspace/main.py", data=b"print('main')"),
        WriteEntry(path="/workspace/utils.py", data=b"def helper(): pass"),
        WriteEntry(path="/workspace/config.yaml", data=b"debug: true"),
    ])
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    await sbx.files.writeFiles([
      { path: '/workspace/main.py', data: "print('main')" },
      { path: '/workspace/utils.py', data: 'def helper(): pass' },
    ]);
    ```
  </Tab>
</Tabs>

### Binary files

`sbx.files.write()` accepts both strings and raw bytes. When you pass `bytes`
/ `Uint8Array`, the SDK routes the payload to the binary-safe
`PUT /files/raw` endpoint automatically (500 MiB cap) — PNGs, compiled
artifacts, and base64-decoded payloads round-trip byte-identically without
any manual encoding.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import base64, os

    # Raw bytes
    sbx.files.write("/tmp/blob.bin", os.urandom(4096))

    # Real binary format (PNG)
    with open("image.png", "rb") as f:
        sbx.files.write("/home/user/image.png", f.read())

    # Read it back byte-identical
    got = sbx.files.read("/tmp/blob.bin", format="bytes")
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    import { randomBytes } from "node:crypto";
    import { readFileSync } from "node:fs";

    await sbx.files.write("/tmp/blob.bin", new Uint8Array(randomBytes(4096)));

    await sbx.files.write(
      "/home/user/image.png",
      new Uint8Array(readFileSync("image.png"))
    );
    ```
  </Tab>
</Tabs>

For uploads larger than 500 MiB, use the streaming
[`upload_url()` / `download_url()`](/sdks/python/sandbox#upload_url) helpers.
See the [Binary File Operations cookbook](/cookbook/filesystem/binary-file-operations)
for a full walkthrough including batch writes with mixed `str` + `bytes`
entries.

### WriteEntry model

| Field  | Type           | Description                                                                                                         |
| ------ | -------------- | ------------------------------------------------------------------------------------------------------------------- |
| `path` | `str`          | Absolute path inside the sandbox                                                                                    |
| `data` | `bytes \| str` | File content. `bytes` entries are dispatched individually to `/files/raw`; `str` entries go through the JSON batch. |
| `user` | `str \| None`  | User to write as (default: `root`)                                                                                  |

### WriteInfo model

`write()` returns a `WriteInfo` with metadata about the written file.

| Field  | Type  | Description                       |
| ------ | ----- | --------------------------------- |
| `path` | `str` | Absolute path of the written file |
| `size` | `int` | Bytes written                     |

## Read a file

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    # Read as bytes (default)
    content = sbx.files.read("/workspace/output.txt")
    print(content)  # b'...'

    # Read as text
    text = sbx.files.read("/workspace/output.txt", format="text")
    print(text)  # '...'
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const content = await sbx.files.read('/workspace/output.txt');
    console.log(content);
    ```
  </Tab>
</Tabs>

## List a directory

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    entries = sbx.files.list("/workspace")
    for entry in entries:
        print(entry.name, entry.type, entry.size)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const entries = await sbx.files.list('/workspace');
    for (const entry of entries) {
      console.log(entry.name, entry.type, entry.size);
    }
    ```
  </Tab>
</Tabs>

### EntryInfo model

| Field      | Type       | Description                       |
| ---------- | ---------- | --------------------------------- |
| `name`     | `str`      | Filename or directory name        |
| `type`     | `FileType` | `file` or `dir`                   |
| `size`     | `int`      | Size in bytes (0 for directories) |
| `path`     | `str`      | Full absolute path                |
| `modified` | `datetime` | Last modification time            |

## Check if a path exists

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    if sbx.files.exists("/workspace/output.csv"):
        data = sbx.files.read("/workspace/output.csv")
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    if (await sbx.files.exists('/workspace/output.csv')) {
      const data = await sbx.files.read('/workspace/output.csv');
    }
    ```
  </Tab>
</Tabs>

## Get file info

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    info = sbx.files.get_info("/workspace/model.pkl")
    print(info.size)      # 4194304 (bytes)
    print(info.modified)  # 2024-01-15T10:30:00Z
    print(info.type)      # FileType.file
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const info = await sbx.files.getInfo('/workspace/model.pkl');
    console.log(info.size);
    console.log(info.type);
    ```
  </Tab>
</Tabs>

## Create a directory

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    sbx.files.make_dir("/workspace/results")
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    await sbx.files.makeDir('/workspace/results');
    ```
  </Tab>
</Tabs>

## Rename or move a file

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    sbx.files.rename("/workspace/temp.csv", "/workspace/final.csv")
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    await sbx.files.rename('/workspace/temp.csv', '/workspace/final.csv');
    ```
  </Tab>
</Tabs>

## Remove a file or directory

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    sbx.files.remove("/workspace/temp.txt")

    # Remove directory (recursive)
    sbx.files.remove("/workspace/old-results")
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    await sbx.files.remove('/workspace/temp.txt');
    ```
  </Tab>
</Tabs>

## Watch a directory for changes

`watch_dir()` / `watchDir()` registers a watcher on the directory and returns
a `WatchHandle`. The handle buffers `FilesystemEvent` objects internally —
drain them with `get_new_events()` / `getNewEvents()`, and call `stop()` when
you're done.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import time
    from declaw import FilesystemEventType

    handle = sbx.files.watch_dir("/workspace")
    sbx.commands.run("touch /workspace/output.txt")

    # Poll the buffered events
    time.sleep(0.5)
    for event in handle.get_new_events():
        if event.type == FilesystemEventType.create:
            print(f"Created: {event.path}")

    handle.stop()
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const handle = await sbx.files.watchDir('/workspace');
    await sbx.commands.run('touch /workspace/output.txt');

    // Poll the buffered events
    await new Promise((r) => setTimeout(r, 500));
    for (const event of handle.getNewEvents()) {
      console.log(event.type, event.path);
    }

    handle.stop();
    ```
  </Tab>
</Tabs>

<Note>
  Full SSE streaming into the `WatchHandle` buffer is still landing.
  The current release registers the watcher server-side and exposes the poll
  API; events may not populate until the streaming change ships.
</Note>

### FilesystemEvent model

| Field       | Type                  | Description                       |
| ----------- | --------------------- | --------------------------------- |
| `type`      | `FilesystemEventType` | `create`, `modify`, or `delete`   |
| `path`      | `str`                 | Absolute path of the changed file |
| `timestamp` | `datetime`            | When the event occurred           |

## Upload and download patterns

### Upload a local file to the sandbox

```python theme={null}
with open("local_dataset.csv", "rb") as f:
    sbx.files.write("/workspace/dataset.csv", f.read())

result = sbx.commands.run("python3 analyze.py /workspace/dataset.csv")
```

### Download a file from the sandbox

```python theme={null}
# Run a job that produces output
sbx.commands.run("python3 -c \"import json; json.dump({'result': 42}, open('/workspace/out.json','w'))\"")

# Read results back to the host
content = sbx.files.read("/workspace/out.json")
import json
data = json.loads(content)
print(data)  # {'result': 42}
```

### Upload multiple files efficiently

```python theme={null}
import os

files = []
for fname in os.listdir("./scripts"):
    with open(f"./scripts/{fname}", "rb") as f:
        files.append(WriteEntry(
            path=f"/workspace/scripts/{fname}",
            data=f.read(),
        ))

sbx.files.write_files(files)
```

<Info>
  The `write_files()` batch call is more efficient than calling `write()` in a loop. It sends all files in a single HTTP request.
</Info>

## Streaming upload and download

`sbx.files.read()` and `sbx.files.write()` buffer the whole payload in memory, which is fine up to \~10 MiB. For larger files — model weights, datasets, snapshot archives — use the raw streaming endpoints. `sbx.upload_url()` and `sbx.download_url()` return path-based URLs under `api.declaw.ai` that accept binary bodies **up to 500 MiB**, streamed end-to-end.

Requests must include your `X-API-Key` header. The URLs are safe to use from your own processes (CI jobs, local scripts, agents), but **should not be shared to third parties** because the API key is still required separately.

<CodeGroup>
  ```python Python theme={null}
  # PUT a large binary to the sandbox
  upload_url = sbx.upload_url("/workspace/model.bin")
  # Send the bytes with curl, requests, or any HTTP client.

  # GET the file back
  download_url = sbx.download_url("/workspace/output.zip")
  ```

  ```ts TypeScript theme={null}
  const uploadUrl = sbx.uploadUrl('/workspace/model.bin');
  const downloadUrl = sbx.downloadUrl('/workspace/output.zip');
  ```
</CodeGroup>

Example binary upload with curl:

```bash theme={null}
curl -X PUT "$UPLOAD_URL" \
  -H "X-API-Key: $DECLAW_API_KEY" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @model.bin
```

Example download:

```bash theme={null}
curl "$DOWNLOAD_URL" \
  -H "X-API-Key: $DECLAW_API_KEY" \
  -o output.zip
```
