# ocx_sdk

*module*

Python SDK for [OCX](https://github.com/ocx-sh/ocx).

`ocx-sdk` drives the ocx binary rather than reimplementing it: ocx owns
resolution, verification, and the identifier grammar, and this package gives
you typed, CWD-independent handles over the commands it exposes.

```python
from ocx_sdk import Ocx, bootstrap

ocx = Ocx(exe=bootstrap.ensure())
project = ocx.project("/srv/build")
project.pull()
project.exec(["task", "verify"])
```

**This module is the API.** Everything listed in `__all__` is the stable
surface; every other module is underscored and package-private, and the one
public submodule is `ocx_sdk.bootstrap`. Reaching into an underscored path
means the next release may move it without notice — pre-1.0, breaking
changes ship without shims.

Start at `Ocx` for the runtime API and `bootstrap.ensure` for provisioning.

[View source](https://github.com/ocx-sh/ocx-sdk-python/blob/main/src/ocx_sdk/__init__.py#L1-L1)

## ocx_sdk.Ocx

*class*

```python
class Ocx
```

Re-exported from: `ocx_sdk._client`

A handle on one ocx binary.

Construction resolves the binary once — the explicit `exe`, then
`OCX_SDK_EXE`, then `PATH`, then ocx's own install symlink — and pins the
result for the handle's lifetime. Every call composes its own argv and
child environment, and the handle exposes no mutators, so it is safe to
share across threads, tasks, and event loops.

The first typed call probes `ocx version` and refuses a binary older than
`MIN_SUPPORTED`; newer than `TESTED_OCX_VERSION` is expected and only
noted at DEBUG.

A long-lived handle keeps running the binary it resolved, so a consumer
shaped like a daemon should reconstruct `Ocx()` periodically to pick up
what an `ocx self update` replaced underneath it.

> **Example**
>
> ```python
> from ocx_sdk import Ocx, OcxConfig
> 
> ocx = Ocx(config=OcxConfig(offline=True))
> project = ocx.project("/srv/build")
> project.pull()
> result = project.exec(["task", "verify"])
> ```

**Attributes**

- `exe` (`Path`) — The resolved binary this handle runs.
- `session_config` (`OcxConfig`) — The `OcxConfig` every call spawns under.

[View source](https://github.com/ocx-sh/ocx-sdk-python/blob/main/src/ocx_sdk/_client.py#L216-L648)

### ocx_sdk.Ocx.clean

*method*

```python
def clean(dry_run: bool = False, force: bool = False, timeout: MaybeTimeout = UNSET, retry: MaybeRetry = UNSET) -> tuple[CleanEntry, ...]
```

Remove unreferenced objects from the local object store.

`mutating=not dry_run` (D5), as `copy`: the preview writes nothing.
The rows are the read-only view of what the store holds loose — the
`kind: "consent"` entries among them are the stamps `OcxConfig.consent`
governs, so a preview says which projects a real run would deactivate.

**Parameters**

- `dry_run` (`bool`) (default: `False`) — Report what would be removed and remove nothing.
- `force` (`bool`) (default: `False`) — Ignore the per-user project registry and collect every unreferenced package, including ones a registered `ocx.lock` still pins. Live install symlinks are always honoured regardless.
- `timeout` (`MaybeTimeout`) (default: `UNSET`) — Seconds per attempt. Omitted takes the config's.
- `retry` (`MaybeRetry`) (default: `UNSET`) — Retry policy. `None` opts out; omitted takes the config's, which D5 resolves to no retries unless `dry_run`.

**Returns**

- (`tuple[CleanEntry, ...]`) — One row per removal, or intended removal.

[View source](https://github.com/ocx-sh/ocx-sdk-python/blob/main/src/ocx_sdk/_client.py#L511-L541)

### ocx_sdk.Ocx.invoke

*method*

```python
def invoke(argv: Sequence[str], *, check: bool = True, capture: bool = True, timeout: MaybeTimeout = UNSET, retry: MaybeRetry = UNSET) -> CommandResult
```

Run an arbitrary ocx command line — the raw escape hatch.

The configured globals and the composed child environment still
apply; presentation does not, so ask for `--format json` yourself if
you intend to parse the output. Commands reached this way are
possible but unsupported: nothing pins their shape, and the
compatibility gate does not run.

**Parameters**

- `argv` (`Sequence[str]`) — The command and its arguments, without the binary.
- `check` (`bool`) (default: `True`) — Raise on a non-zero exit instead of returning it.
- `capture` (`bool`) (default: `True`) — Pipe and capture both streams.
- `timeout` (`MaybeTimeout`) (default: `UNSET`) — Seconds per attempt. Omitted takes the config's.
- `retry` (`MaybeRetry`) (default: `UNSET`) — Retry policy. `None` opts out; omitted takes the config's.

**Returns**

- (`CommandResult`) — The exit code and captured output.

**Raises**

- `OcxProcessError` — A non-zero exit under `check`.
- `OcxTimeoutError` — The timeout expired.

[View source](https://github.com/ocx-sh/ocx-sdk-python/blob/main/src/ocx_sdk/_client.py#L543-L575)

### ocx_sdk.Ocx.invoke_async

*method* · *async*

```python
async def invoke_async(argv: Sequence[str], *, check: bool = True, capture: bool = True, timeout: MaybeTimeout = UNSET, retry: MaybeRetry = UNSET) -> CommandResult
```

Run an arbitrary ocx command line on the event loop.

The async twin of `invoke`. Cancelling the awaiting task terminates
the child before the `CancelledError` propagates.

**Parameters**

- `argv` (`Sequence[str]`) — The command and its arguments, without the binary.
- `check` (`bool`) (default: `True`) — Raise on a non-zero exit instead of returning it.
- `capture` (`bool`) (default: `True`) — Pipe and capture both streams.
- `timeout` (`MaybeTimeout`) (default: `UNSET`) — Seconds per attempt. Omitted takes the config's.
- `retry` (`MaybeRetry`) (default: `UNSET`) — Retry policy. `None` opts out; omitted takes the config's.

**Returns**

- (`CommandResult`) — The exit code and captured output.

**Raises**

- `ValueError` — The handle carries an `on_log` callback, which v0.1
refuses on async paths.
- `OcxProcessError` — A non-zero exit under `check`.
- `OcxTimeoutError` — The timeout expired.

[View source](https://github.com/ocx-sh/ocx-sdk-python/blob/main/src/ocx_sdk/_client.py#L577-L608)

### ocx_sdk.Ocx.logout

*method*

```python
def logout(registry: str | None = None, *, timeout: MaybeTimeout = UNSET, retry: MaybeRetry = UNSET) -> LogoutResult
```

Drop the stored credentials for a registry.

**Parameters**

- `registry` (`str | None`) (default: `None`) — The registry hostname. `None` falls back to ocx's `OCX_DEFAULT_REGISTRY`.
- `timeout` (`MaybeTimeout`) (default: `UNSET`) — Seconds per attempt. Omitted takes the config's.
- `retry` (`MaybeRetry`) (default: `UNSET`) — Retry policy. `None` opts out; omitted takes the config's.

**Returns**

- (`LogoutResult`) — The registry ocx cleared. Clearing an absent entry is not an
- (`LogoutResult`) — error.

[View source](https://github.com/ocx-sh/ocx-sdk-python/blob/main/src/ocx_sdk/_client.py#L488-L509)
