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

# Run Commands Inside a Wrenn Capsule

> Execute shell commands inside capsules using capsule.commands. Supports foreground, background, and streaming execution with full stdout/stderr capture.

The `capsule.commands` interface gives you full control over process execution inside a capsule. You can run commands synchronously and wait for the result, launch long-running processes in the background, or stream output in real time over a WebSocket connection.

## Foreground execution

`capsule.commands.run()` executes a shell command and blocks until it exits. It returns a `CommandResult` with the full stdout, stderr, exit code, and wall-clock duration.

```python theme={null}
result = capsule.commands.run("python -c 'print(42)'")
print(result.stdout)       # "42\n"
print(result.stderr)       # ""
print(result.exit_code)    # 0
print(result.duration_ms)  # 35
```

### Options

| Parameter | Type                     | Default | Description                                                        |
| --------- | ------------------------ | ------- | ------------------------------------------------------------------ |
| `timeout` | `int \| None`            | `30`    | Seconds before the command is killed. `None` disables the timeout. |
| `envs`    | `dict[str, str] \| None` | `None`  | Additional environment variables for the process.                  |
| `cwd`     | `str \| None`            | `None`  | Working directory for the process.                                 |

```python theme={null}
result = capsule.commands.run(
    "python train.py",
    timeout=300,
    envs={"CUDA_VISIBLE_DEVICES": "0", "LOG_LEVEL": "debug"},
    cwd="/app",
)
```

## Background execution

Pass `background=True` to launch a process without waiting for it to finish. You get a `CommandHandle` immediately with the process PID and a tag.

```python theme={null}
handle = capsule.commands.run("python server.py", background=True)
print(handle.pid)  # 1234
print(handle.tag)  # "exec-abc123"
```

<Tip>
  Pass a `tag` string to label a background process with a human-readable name. You can use the same tag later to find it in `capsule.commands.list()`.
</Tip>

```python theme={null}
handle = capsule.commands.run(
    "uvicorn app:main --port 8000",
    background=True,
    tag="api-server",
)
```

## Streaming execution

`capsule.commands.stream()` opens a WebSocket connection and yields events as the process runs. Use this when you want to display output in real time instead of waiting for the process to finish.

```python theme={null}
import sys

for event in capsule.commands.stream("python", args=["-u", "train.py"]):
    match event.type:
        case "start":
            print(f"process started with PID {event.pid}")
        case "stdout":
            print(event.data, end="")
        case "stderr":
            print(event.data, end="", file=sys.stderr)
        case "exit":
            print(f"\nexited with code {event.exit_code}")
```

When you pass `args`, the command is executed directly. When you omit `args`, the command string is passed to `/bin/sh -c`.

### Stream event types

| Event type | Fields      | Description                    |
| ---------- | ----------- | ------------------------------ |
| `start`    | `pid`       | Process has started.           |
| `stdout`   | `data`      | A chunk of standard output.    |
| `stderr`   | `data`      | A chunk of standard error.     |
| `exit`     | `exit_code` | Process has exited.            |
| `error`    | `data`      | A stream-level error occurred. |

## Connecting to a running background process

`capsule.commands.connect()` attaches to an already-running background process by PID and yields its output events, exactly like `stream()`.

```python theme={null}
# Launch a background process
handle = capsule.commands.run("python long_job.py", background=True)

# Attach to it later and stream its output
for event in capsule.commands.connect(handle.pid):
    if event.type == "stdout":
        print(event.data, end="")
    elif event.type == "exit":
        print(f"exited: {event.exit_code}")
```

## Listing running processes

`capsule.commands.list()` returns all background processes currently running in the capsule as a list of `ProcessInfo` objects.

```python theme={null}
for proc in capsule.commands.list():
    print(proc.pid, proc.tag, proc.cmd)
```

Each `ProcessInfo` has:

| Field  | Type                | Description                          |
| ------ | ------------------- | ------------------------------------ |
| `pid`  | `int`               | Process PID.                         |
| `tag`  | `str \| None`       | User-assigned or auto-generated tag. |
| `cmd`  | `str \| None`       | Executable path.                     |
| `args` | `list[str] \| None` | Arguments passed to the executable.  |

## Killing a process

Send `SIGKILL` to a background process by PID:

```python theme={null}
capsule.commands.kill(pid=1234)
```

## Complete example

```python theme={null}
from wrenn import Capsule
import sys

with Capsule(template="minimal", wait=True) as capsule:
    # Write a script to run
    capsule.files.write("/app/task.py", """
import time
for i in range(5):
    print(f"step {i}")
    time.sleep(0.5)
""")

    # Stream it
    for event in capsule.commands.stream("python", args=["-u", "/app/task.py"]):
        match event.type:
            case "stdout":
                sys.stdout.write(event.data)
            case "exit":
                print(f"done (exit {event.exit_code})")

    # Launch a background server
    handle = capsule.commands.run(
        "python -m http.server 8080",
        background=True,
        cwd="/app",
        tag="http-server",
    )

    # Confirm it is running
    procs = capsule.commands.list()
    print(any(p.tag == "http-server" for p in procs))  # True

    # Kill it
    capsule.commands.kill(pid=handle.pid)
```
