# 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.PackageCommands

*class* · *dataclass*

```python
class PackageCommands
```

Re-exported from: `ocx_sdk._client`

The `ocx package` command group — machine tier.

Package operations act on the `$OCX_HOME` store and its candidate and
current symlinks. They take no project path and are CWD-independent by
construction; a path appears only where the CLI itself takes one.

Every method is multi-identifier native, mirroring the CLI's `PKG...`
with one shared resolution.

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

### ocx_sdk.PackageCommands.copy

*method*

```python
def copy(source: PackageLike, *, to: str | None = None, identifier: str | None = None, platforms: Iterable[str] = (), cascade: bool = False, keep_tag: bool | None = None, referrers: bool | None = None, description: bool = False, annotations: Mapping[str, str] | None = None, dry_run: bool = False, timeout: MaybeTimeout = UNSET, retry: MaybeRetry = UNSET) -> CopyReport
```

Copy a package from one location to another, server-side (C-015).

`mutating=not dry_run` (D5): `copy(dry_run=True)` writes nothing
and is exactly the read a transient registry blip should retry, so
a flat `True` would wrongly disable retry for it. `dry_run=True`
reports `status="planned"` rather than describing a copy that
already happened.

Non-empty `sidecar_conflicts` on the result means the target
already had different content under a sidecar tag `copy` would have
written — that call exits 65 and raises. Recover the report,
conflicts included, with `partial_report(err)` and
`CopyReport.from_json` (D10).

`to`/`identifier` is mutual exclusion, **not** xor: giving neither
is a legal invocation — ocx falls back to the configured default
registry, keeping `source`'s repository and tag. Only giving both
is refused.

A digest-form `source` with no tag needs `identifier` (there is no
tag to keep at the target otherwise) and needs exactly one entry in
`platforms` (there is no single-platform default to fall back on).

Not guarded here, because it would take a second identifier parser:
ocx refuses a **target** that carries no tag, whichever route
produced it (`package_copy.rs:110`) — so a tagless `source` with no
`identifier`, or a tagless `identifier`, exits 64. Spell the tag out
at whichever end names the target.

**Parameters**

- `source` (`PackageLike`) — The package reference to copy.
- `to` (`str | None`) (default: `None`) — Rewrite only the target's registry host, keeping `source`'s repository path and tag (`package_copy.rs:19-24` — `value_name = "REGISTRY"`, a host, not a full reference). Refused alongside `identifier`.
- `identifier` (`str | None`) (default: `None`) — The full target reference, when the repository path or tag changes too. Refused alongside `to`. Required when `source` is a bare digest.
- `platforms` (`Iterable[str]`) (default: `()`) — Restrict to these platforms. Omitted copies all of them — except when `source` is a bare digest, which needs exactly one.
- `cascade` (`bool`) (default: `False`) — Also advance the rolling tags above this version at the target.
- `keep_tag` (`bool | None`) (default: `None`) — Write the `__ocx.keep.sha256-<hex>` tag for each platform manifest at the target. `None` leaves ocx's default, which writes it.
- `referrers` (`bool | None`) (default: `None`) — Copy OCI referrers-API attachments. `None` leaves ocx's default.
- `description` (`bool`) (default: `False`) — Also copy the source's description metadata.
- `annotations` (`Mapping[str, str] | None`) (default: `None`) — Extra OCI annotations for the copied index.
- `dry_run` (`bool`) (default: `False`) — Report what would be copied without writing.
- `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 (D5 resolves the mutating default per `dry_run`).

**Returns**

- (`CopyReport`) — What landed at the target, including per-platform rows and
- (`CopyReport`) — blob transfer counts.

**Raises**

- `ValueError` — Both `to` and `identifier` were given; or `source`
is a bare digest and `identifier` is absent, or `platforms`
does not name exactly one entry.
- `DataError` — `sidecar_conflicts` is non-empty (exit 65) — see the
partial-failure note above for how to recover the report.

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

### ocx_sdk.PackageCommands.inspect

*method*

```python
def inspect(*refs: PackageLike, platform: str | None = None, env: Mapping[str, EnvValue] | None = None, resolve: bool = False, closure: bool = False, timeout: MaybeTimeout = UNSET, retry: MaybeRetry = UNSET) -> InspectReport
```

Inspect packages straight from the registry or the store.

**Parameters**

- `*refs` (`PackageLike`) (default: `()`) — Package identifiers.
- `platform` (`str | None`) (default: `None`) — The platform to resolve against.
- `env` (`Mapping[str, EnvValue] | None`) (default: `None`) — Extra `[env]` entries for this call.
- `resolve` (`bool`) (default: `False`) — Add the pinned identifier, digest, layers, and the resolution chain, which at this tier starts with an index hop the project tier skips.
- `closure` (`bool`) (default: `False`) — Add the dependency closure, its surface, and any conflicts.
- `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**

- (`InspectReport`) — The inspected packages, whose `name` is the full requested
- (`InspectReport`) — identifier at this tier.

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