- docs
- Reference
- Reference
- Metadata
- Metadata: Entry Points and Executables
Metadata: Entry Points and Executables
The entrypoints and binaries fields of metadata.json declare the commands a package puts on PATH.
Entry Points
Section titled “Entry Points”The entrypoints object declares named launchers that ocx package install generates into an entrypoints/ directory inside the package directory. Each launcher is a .sh script on Unix, or a native <name>.exe shim plus a one-line <name>.shim sidecar on Windows.
- Each launcher re-enters through
ocx launcher execwith the package root baked in, and resolves its dispatch command against the composedPATHof the package’senvblock. - The dispatch command is the entry point’s own name unless the optional
commandfield sets another. - A package with no entrypoints produces no
entrypoints/directory.
See Entry points in depth for how launchers are generated, reached and reported.
Wire Shape
Section titled “Wire Shape”entrypoints is a JSON object keyed by the invocable name, and each value is an object of per-entry fields.
| Position | Type | Required | Description |
|---|---|---|---|
| Key | string | Yes | The invocable name: the launcher script filename and the command users invoke. Must match ^[a-z0-9][a-z0-9_-]*$ and be at most 64 bytes. |
| Value | object | Yes | Per-entry fields. {} is the common case: the invocable name is the dispatched command. |
command | string | No | Dispatch target resolved on the composed PATH when it differs from the invocable name. Same slug rule and 64-byte limit as the key. Not interpolated, so it must be a plain slug and not a path. |
args | array of strings | No | Fixed leading arguments prepended before user-supplied arguments at dispatch. Each element is one argv token with no shell word-splitting. ${installPath} and ${self.installPath} (with the :native / :posix modifier) are interpolated per element; ${deps.*} and ${self.env.*} are rejected at publish time with DisallowedToken. Omitted and [] are wire-identical. |
{ "entrypoints": { "cmake": {}, "ctest": {}, "hello": { "command": "hello-bin" }, "mytool": { "command": "python", "args": ["${installPath}/app/main.py"] } }}Invoking mytool a b runs python <content>/app/main.py a b, with baked arguments first and user arguments after. A command reaches a dependency’s binary only when that dependency exposes it through interface or public env entries.
Uniqueness
Section titled “Uniqueness”Duplicate keys in the on-wire JSON are rejected at deserialization with a descriptive error, instead of the serde_json default of last wins. Name collisions across different selected packages are detected at select time.
Executables
Section titled “Executables”The optional binaries array lists bare executable names the package exposes on its interface PATH surface. A consumer can look a name up without installing the package.
- Claim: the array is a publisher-declared claim that OCX does not verify against the package content
- Launchers: a generated entry-point launcher name never appears in it
- Same name in both:
binariesandentrypointsmay both namecmake, becauseentrypoints/is prepended ahead ofbin/on the composedPATHand so the launcher, not the raw binary, resolves
{ "$schema": "https://ocx.sh/schemas/metadata/v1.json", "type": "bundle", "version": 1, "env": [ { "key": "PATH", "type": "path", "value": "${installPath}/bin", "visibility": "public" } ], "binaries": ["cmake", "cpack", "ctest"]}ocx package create--bin-scan/--no-bin-scancan fill or verify the claim against the local content tree. A foreign or reused layer added later atpushtime is never part of that scan.- On Windows the claim reflects the default executable-resolution set (
.exe,.com,.bat,.cmd), the fixed allowlist the create-time scan uses. A hardened child environment with a customizedPATHEXTmay resolve fewer of these. A name backed only by a.bat,.cmdor.comfile may then fail to resolve.
See Declaring executables for why the claim is unverified and how it differs from entry points.
Interface Surface, Own Package Only
Section titled “Interface Surface, Own Package Only”A name is eligible only if it is reachable through a ${installPath}- or ${self.installPath}-rooted path variable with interface visibility.
- Private,
libexec-style directories are never candidates. - The claim is own-package only and never transitive: a package never lists a dependency’s executables. What is reachable through a dependency chain is reported by
ocx envandocx package envin theirbinariesarray, attributed per admitted package. - A render modifier on the
PATHvalue takes it out of scan scope, so the segment is silently excluded from the auto-filled claim. On Linux andanythe libc lint ofocx package createrefuses such a value at publish time. On darwin and Windows nothing diagnoses it.
None vs an Empty Array
Section titled “None vs an Empty Array”The two wire states carry different meaning, so a consumer reading metadata.json directly can tell “nobody declared this yet” from “the publisher asserts zero executables”.
{ "type": "bundle", "version": 1, "binaries": [] }- Omitted: undeclared. The package predates the field, or the publisher never set it.
[]: declared, with zero executables onPATH(for example an env-only package).
Writing the Field
Section titled “Writing the Field”binaries is a plain array of bare strings, sorted and de-duplicated, with no per-entry object form. Each name is validated against a grammar looser than an entry point key, admitting names such as python3.13, c++ and MSBuild.
- ASCII printable characters only, no whitespace anywhere.
- Forbidden characters:
/ \ < > : " | ? *, the Windows-reserved filename characters. - No leading
-, and no leading or trailing.. - Non-empty, at most 64 bytes.
- Reserved Windows device names (
CON,PRN,AUX,NUL,COM0toCOM9,LPT0toLPT9) are rejected case-insensitively, checked against the basename before the first., soCON.txtis rejected too. - Two names that differ only by case (
Cmakeandcmake) are rejected. - Bare names only, never
.exeor any other extension.