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

# Streaming Command Output

> Stream stdout and stderr from long-running commands.

## Stream stdout and stderr

<CodeGroup>
  ```python Python SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  def on_stdout(data: str) -> None:
      print(data, end="")

  def on_stderr(data: str) -> None:
      print(data, end="")

  result = machine.commands.run_streaming(
      "for i in 1 2 3; do echo tick-$i; sleep 1; done",
      shell=True,
      on_stdout=on_stdout,
      on_stderr=on_stderr,
  )
  print(result.exit_code)
  ```

  ```typescript TypeScript SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  const result = await machine.commands.run(
    "for i in 1 2 3; do echo tick-$i; sleep 1; done",
    {
      shell: true,
      onStdout: (chunk) => process.stdout.write(chunk),
      onStderr: (chunk) => process.stderr.write(chunk),
    },
  );
  console.log(result.exitCode);
  ```

  ```bash CLI theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  nullspace machine process stream mch_123 \
    --shell "for i in 1 2 3; do echo tick-$i; sleep 1; done"
  # add --json to emit the final exit_code, stdout, stderr, and pid
  ```
</CodeGroup>

Streaming command output is not a plain REST call: SDK callers stream over
`GET /v1/machines/{id}/ws?channel=exec` (sending `exec_start` and reading
`exec_stdout`/`exec_stderr`/`exec_exit`), so there is no `curl` equivalent. The
non-streaming `POST /v1/machines/{id}/exec` endpoint returns only the final
result.

Passing callbacks to `machine.commands.run(...)` without `background=True` uses
the same streaming path:

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
result = machine.commands.run(
    "python3 -u script.py",
    shell=True,
    on_stdout=on_stdout,
    on_stderr=on_stderr,
)
```

## Connect to a running process

<CodeGroup>
  ```python Python SDK theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  process = machine.commands.connect(
      pid=1234,
      on_stdout=on_stdout,
      on_stderr=on_stderr,
  )
  result = process.wait()
  print(result.exit_code)
  ```

  ```bash CLI theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
  nullspace machine process attach mch_123 1234
  ```
</CodeGroup>

Attaching to a running process is not a plain REST call: it reconnects over
`GET /v1/machines/{id}/ws?channel=exec` with an `exec_connect` message, so
there is no `curl` equivalent.

Use `connect(pid)` for background processes that are still running. It attaches
to the exec WebSocket and waits for the process exit event when possible.

## Live background streaming

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
worker = machine.commands.run(
    "python3 -u worker.py",
    shell=True,
    background=True,
    on_stdout=on_stdout,
    on_stderr=on_stderr,
)

worker.disconnect()
```

Combining `background=True` with callbacks starts the command through the exec
WebSocket and returns a `BackgroundCommand` after an `exec_started` message. The
CLI equivalent is `nullspace machine process stream` with `--cwd`, `--env`, and
`--timeout` flags.

## Behavior

Streaming uses `GET /v1/machines/{id}/ws?channel=exec`. SDK callers send
`exec_start` for new commands or `exec_connect` for existing PIDs and then read
`exec_started`, `exec_stdout`, `exec_stderr`, `exec_exit`, and `error` messages.

Use the REST `get_logs` endpoint when you only need logs after the process has
started.

## Related

* [WebSocket protocol](../reference/websocket-protocol)
* [Background processes](./background)
* [PTY sessions](../access/pty)
