Skip to content
ocx
install

Authoring packages

The author flow lives on Ocx.package — machine tier, alongside install/select/exec, not on Project. Three methods carry the core of it: create → test → push; the index side — announce, claim, and the rolling-tag audit cascade_check / cascade_repair — follows below.

# illustrative: needs a real ocx binary and a real package directory.
from ocx_sdk import Ocx
ocx = Ocx()
ocx.package.create("./my-tool", identifier="ocx.sh/me/my-tool:1.0.0", platform="linux/amd64")
result = ocx.package.test(
"ocx.sh/me/my-tool:1.0.0",
script="./test.star",
metadata="./my-tool/metadata.json",
)
if result.passed:
published = ocx.package.push("./my-tool.tar", identifier="ocx.sh/me/my-tool:1.0.0")
print(published.manifest_digest)

create bundles a local directory into a package archive, writing a build receipt beside it that test and push read back — identifier and platform given here don’t need repeating on the later calls. There is nothing to return: ocx prints no payload for this command.

What it recorded reads back through receipt, which wraps ocx package receipt and returns a BuildReceipt. A bundle with no receipt beside it is ocx’s exit 79 and answers None here — the ordinary state for a bundle handed over from elsewhere. A receipt ocx cannot read is exit 65 and stays a DataError: “unreadable” must never degrade into “absent”, because that turns a recorded value into a usage error about a flag the publisher never needed. receipt.ref is the identifier as a PackageRef.

from ocx_sdk import BuildReceipt
receipt = BuildReceipt.from_json('{"identifier": "ocx.sh/me/my-tool:1.0.0"}')
assert receipt.platform is None
assert str(receipt.ref) == "ocx.sh/me/my-tool:1.0.0"

Only the --script form of ocx package test is typed. The trailing -- CMD form prints the tested command’s raw stdout verbatim, even under --format json, so nothing here could parse it reliably — reach for invoke if you need that form.

The --script form runs a Starlark test script against a materialized copy of the package and returns a TestResult — the stable v1 envelope, one of the durable anchors re-verified on every ocx version bump. status decides pass or fail; when it fails, assertion.kind is the stable, machine-readable reason (assertion is None on a pass):

from ocx_sdk import TestResult
result = TestResult.from_json(
'{"status": "failed", '
'"run": {"exit_code": 1, "stdout": "", "stderr": "", "duration_ms": 3, "truncated": false}, '
'"assertion": {"kind": "exit_code_mismatch", "message": "expected 0, got 1"}}'
)
assert not result.passed
assert result.assertion is not None
assert result.assertion.kind == "exit_code_mismatch"

layers= takes layer archives or digest references, base first; metadata= is required whenever no file layers are given; private=True composes ocx’s --self surface for testing a package’s own private tooling.

push — publish, deliberately not retried by default

Section titled “push — publish, deliberately not retried by default”

push publishes a package’s layers and metadata to a registry and returns a PushResult — the published identifier, digest, and tags.

Like Ocx.login, push defaults its per-call retry to None regardless of session policy: a push is a registry write, and re-sending one after a timeout risks publishing twice. Pass retry= explicitly when the target registry is known to be idempotent-safe. cascade=True also advances the rolling tags above this version.

Continues on Authoring packages: announce.