# skillctl — Full Reference > Package, sign, and distribute AI agent skills as OCI images. skillctl is a CLI tool and Go library for treating AI agent skills exactly like container images. Skills are directories containing a SKILL.md (instructions) and skill.yaml (metadata), packaged as standard OCI images and distributed via any OCI registry (Quay, GHCR, Harbor, Zot). Project: https://github.com/redhat-et/skillimage Author: Pavel Anni, Office of CTO, Red Hat License: Apache-2.0 --- ## Install Homebrew (macOS / Linux): ``` brew install pavelanni/tap/skillctl ``` Install script: ``` curl -fsSL https://raw.githubusercontent.com/redhat-et/skillimage/main/install.sh | sh ``` Go install: ``` go install github.com/redhat-et/skillimage/cmd/skillctl@latest ``` Container: ``` podman run --rm ghcr.io/redhat-et/skillctl:latest version ``` Pre-built binaries: https://github.com/redhat-et/skillimage/releases --- ## What is a Skill? A skill is a directory following the Agent Skills specification (agentskills.io). It contains: - **SKILL.md** — instructions the agent follows (prompt content) - **skill.yaml** — structured metadata (SkillCard) - Any supporting files (templates, examples, scripts) Example directory: ``` resume-screener/ SKILL.md skill.yaml ``` --- ## SkillCard (skill.yaml) The SkillCard is the machine-readable identity of a skill: ```yaml apiVersion: skillimage.io/v1alpha1 kind: SkillCard metadata: name: resume-screener namespace: official version: 1.0.0 description: Screen resumes against a job description. authors: - name: Red Hat ET email: et@redhat.com license: Apache-2.0 tags: - hr - screening compatibility: claude-3.5-sonnet spec: prompt: SKILL.md tools: - read_file resources: memory: 32Mi cpu: 100m ``` Fields: - `name`: lowercase alphanumeric with hyphens - `namespace`: organizational scope (e.g. "official", "community") - `version`: semantic version (major.minor.patch) - `description`: free-text description (first 256 chars stored in OCI annotations) - `authors`: list of {name, email} pairs - `license`: SPDX identifier - `tags`: searchable labels - `compatibility`: target agent/model - `tools`: list of tools the skill requires - `resources`: CPU and memory hints for deployment --- ## CLI Reference ### skillctl pack Package a skill directory into a local OCI image. ``` skillctl pack [--tag ] [--media-type ] ``` - `--tag`: override the default tag (default: `-draft`) - `--media-type`: "standard" (default) or "redhat" (for oc-mirror compatibility) The skill directory must contain a valid skill.yaml. The resulting image is stored in a local OCI layout at `~/.skillctl/store/`. ### skillctl push Push a local skill image to a remote OCI registry. ``` skillctl push ``` The ref is a full OCI reference: `registry/namespace/name:tag`. Uses existing podman/docker credentials from `~/.docker/config.json` or Podman's `auth.json`. ### skillctl pull Pull a skill image from a remote registry. ``` skillctl pull [-o ] ``` - `-o`: unpack skill files into the specified directory (creates a subdirectory named after the skill) ### skillctl inspect Show detailed metadata for a skill image. ``` skillctl inspect ``` Works with both local refs (e.g. `test/my-skill:1.0.0-draft`) and remote refs (e.g. `quay.io/org/skill:1.0.0`). Remote inspect reads OCI manifest annotations without downloading the image layers. Output includes: name, version, status, description, authors, license, tags, compatibility, word count, digest, media type, layer count. ### skillctl validate Validate a skill.yaml against the JSON Schema. ``` skillctl validate ``` ### skillctl list List all skill images in the local store. ``` skillctl list ``` ### skillctl promote Promote a skill through lifecycle states. ``` skillctl promote --to --local ``` Valid transitions: draft -> testing -> published -> deprecated -> archived. Promotion updates the `io.skillimage.status` annotation and retags the image. Image content (layers) is never modified. ### skillctl tag Add a new tag to an existing local image. ``` skillctl tag ``` Useful for preparing images for push: `skillctl tag ns/name:1.0.0-draft quay.io/org/name:1.0.0-draft`. --- ## Lifecycle States ``` draft -> testing -> published -> deprecated -> archived ``` | State | OCI Tag | Example | |-------|---------|---------| | draft | `-draft` | `1.0.0-draft` | | testing | `-testing` | `1.0.0-testing` | | published | `` + `latest` | `1.0.0` | | deprecated | `` | `1.0.0` | | archived | tag removed | digest only | Status is stored in OCI manifest annotation `io.skillimage.status`. Image content is immutable across all promotions — only annotations and tags change. --- ## OCI Architecture Skills are packaged as standard OCI images (FROM scratch), not ORAS artifacts. This means: - `podman pull` / `skopeo copy` / `crane pull` work out of the box - Kubernetes ImageVolumes can mount skills directly into pods - Standard registries index and serve them normally - cosign/sigstore signing works the same as for container images ### Image Structure - **Layer**: single tar.gz containing all skill files - **Config**: OCI image config with rootfs and diff_ids - **Manifest**: OCI image manifest with SkillCard metadata in annotations ### Media Types Default (standard): - Layer: `application/vnd.oci.image.layer.v1.tar+gzip` - Config: `application/vnd.oci.image.config.v1+json` - Manifest: `application/vnd.oci.image.manifest.v1+json` Red Hat profile (`--media-type redhat`): - Layer: `application/vnd.redhat.agentskill.layer.v1.tar+gzip` - Config: `application/vnd.redhat.agentskill.config.v1+json` - Manifest: `application/vnd.oci.image.manifest.v1+json` (unchanged) The Red Hat profile enables oc-mirror to identify skill artifacts for disconnected/air-gapped mirroring. ### OCI Annotations Standard OCI annotations used: - `org.opencontainers.image.title` — skill display name - `org.opencontainers.image.description` — first 256 chars of description - `org.opencontainers.image.version` — semantic version - `org.opencontainers.image.authors` — comma-separated authors - `org.opencontainers.image.licenses` — SPDX license - `org.opencontainers.image.vendor` — namespace - `org.opencontainers.image.created` — RFC 3339 timestamp - `org.opencontainers.image.source` — source repository URL - `org.opencontainers.image.revision` — git commit Custom annotations: - `io.skillimage.status` — lifecycle state - `io.skillimage.tags` — JSON array of searchable tags - `io.skillimage.compatibility` — target agent/model - `io.skillimage.wordcount` — word count of SKILL.md (excluding frontmatter) --- ## Kubernetes Deployment ### Image Volumes (K8s 1.33+ / OpenShift 4.20+) ```yaml volumes: - name: skill-resume-screener image: reference: quay.io/org/skill-resume-screener:1.0.0 pullPolicy: IfNotPresent containers: - name: agent volumeMounts: - name: skill-resume-screener mountPath: /skills/resume-screener ``` The kubelet pulls and caches the image. Read-only mount, no init container needed. ### Init Container (older clusters) ```yaml initContainers: - name: skill-puller image: ghcr.io/redhat-et/skillctl:latest command: ["skillctl", "pull", "--verify", "-o", "/skills", "quay.io/org/skill-resume-screener:1.0.0"] volumeMounts: - name: skills mountPath: /skills ``` --- ## Go Library Module path: `github.com/redhat-et/skillimage` ### Package: pkg/oci Core OCI operations. ```go client, _ := oci.NewClient("/path/to/store") // Pack desc, _ := client.Pack(ctx, "path/to/skill/", oci.PackOptions{ Tag: "1.0.0", MediaType: oci.MediaTypeStandard, }) // Push to remote client.Push(ctx, "quay.io/org/skill:1.0.0", oci.PushOptions{}) // Pull from remote client.Pull(ctx, "quay.io/org/skill:1.0.0", oci.PullOptions{ OutputDir: "/skills", }) // Inspect (local or remote) result, _ := client.Inspect(ctx, "ns/name:tag") result, _ := client.InspectRemote(ctx, "quay.io/org/skill:1.0.0") ``` ### Package: pkg/skillcard Parse and validate SkillCard files. ```go f, _ := os.Open("skill.yaml") sc, _ := skillcard.Parse(f) errors, _ := skillcard.Validate(sc) ``` ### Package: pkg/lifecycle State machine for lifecycle transitions. ```go lifecycle.ValidTransition(lifecycle.Draft, lifecycle.Testing) // true lifecycle.ValidTransition(lifecycle.Draft, lifecycle.Published) // false lifecycle.TagForState("1.0.0", lifecycle.Draft) // "1.0.0-draft" ``` --- ## Project Structure | Path | Description | |------|-------------| | `cmd/skillctl/` | CLI entry point | | `internal/cli/` | Cobra commands | | `pkg/oci/` | OCI pack/push/pull/inspect/promote | | `pkg/skillcard/` | SkillCard parse, validate, serialize | | `pkg/lifecycle/` | State machine, tag rules | | `pkg/verify/` | Sigstore signature verification | | `schemas/` | JSON Schema for SkillCard | | `examples/` | Sample skills | --- ## Related Projects - [Agent Skills Specification](https://agentskills.io) — the standard for skill directories - [agentoperations/agent-registry](https://github.com/agentoperations/agent-registry) — metadata and governance layer (companion project) - [ORAS](https://oras.land) — OCI Registry As Storage - [Zot](https://zotregistry.dev) — lightweight OCI registry for testing