Resources

Declare resources so consumers can install the completions, man pages, CLI specifications, agent skills, SBOMs, or desktop files that match the tool version a user runs. Resources can live inside an artifact, in a separate release asset, or in the source repository; some can also be generated by the installed executable.

Declare each resource with a --resource flag on packslip create, a [[resource]] table in a TOML manifest, or a line in the GitHub action’s resources input.

Consumers choose which resource kinds they support. Declaring a resource describes where to get it; publish separate assets yourself and check archive paths before signing. See Release recipes for complete configurations.

Write a resource entry

A --resource value, like each line of the action’s resources input, has this form:

KIND[/QUALIFIERS][@SCOPE]=SOURCE:VALUE

KIND says what the file is. The qualifiers, separated by /, narrow it down, for example to a completion’s shell and executable. SCOPE limits the entry to one platform; see Scope resources to the right artifact. SOURCE is archive, asset, repo, or exec, and VALUE is the path it reads or the command it runs; see Choose a source. Repeat --resource for each entry.

For example, completion/zsh=archive:share/zsh/site-functions/_mytool declares a zsh completion script inside the archive. The equivalent TOML uses one [[resource]] table, with separate fields for the qualifier and source:

[[resource]]
kind = "completion"
shell = "zsh"
archive = "share/zsh/site-functions/_mytool"

Repeat the flag or table for each resource. If the archive has a top-level directory, include it in archive, for example mytool-1.2.3/share/zsh/site-functions/_mytool.

KindFlag formTOML fieldsNotes
completioncompletion/SHELL[/BIN], or completion/SHELL,SHELL[/BIN] with execshell, or shells with exec; binSeveral shells only with exec, whose command or env contains {shell}.
manman[/BIN]binThe file’s suffix gives the section, as in mytool.1.
cli-speccli-spec/FORMAT[/BIN]format, binThe documented format is usage.
skillskill/NAMEnameA directory that holds SKILL.md; as an asset, an archive of that directory. See Agent skills and desktop files.
sbomsbom/FORMATformatcyclonedx or spdx. Never from exec.
desktop, icondesktop, iconA freedesktop desktop entry, or an icon file.
appappA macOS .app bundle inside a dmg or zip. archive source only.
A kind of your own, such as fontKIND or KIND/NAMEnameConsumers ignore kinds they do not know.

BIN is the name of one of the release’s executables; see Completions and CLI specifications for when an entry must give it. See Resource kinds for what each kind means to a consumer.

Choose a source

A resource has a kind and exactly one source. The source decides what pins the content:

SourceExample flag valueWhat pins the content
archiveman=archive:share/man/man1/mytool.1The containing artifact’s digest.
assetsbom/cyclonedx=asset:dist/mytool.cdx.jsonThe separate file’s digest in the statement.
reposkill/mytool=repo:skills/mytoolsource.commit, which is required.
execcompletion/zsh=exec:mytool completion zshThe executable is verified; its output is not separately signed.

A repo entry needs the source repository and commit: pass --source-repo with the repository URL and --commit with the full commit SHA, or set source.repo and source.commit in TOML. The GitHub action sets both.

An asset value is a local path: create digests the file and records only its file name, such as mytool.cdx.json. Upload the file with the release, and give its download URL with --url mytool.cdx.json=URL (the file name, without dist/), the entry’s url in TOML, or --url-base (url_base in TOML). The GitHub action sets --url-base to the release’s download URL. A file matched both as an artifact, for example by dist/*, and as an asset is recorded once, as the asset.

create checks that each entry is well formed, digests every asset, and confirms that an exec command and any bin qualifier name a release executable. It does not open archives to check archive paths, read the repository at repo paths, or run exec commands. A mistyped path is signed as written, and consumers install without that resource. An archive path starts at the archive’s root, including any top-level directory such as mytool-1.2.3/. List the archive’s contents (tar tf) and inspect the result with packslip show before publishing.

Completions and CLI specifications

packslip create ... \
  --resource 'completion/bash,zsh,fish=exec:mytool completion {shell}' \
  --resource 'cli-spec/usage/mytool=archive:share/usage/mytool.kdl'

The first entry tells a consumer to run mytool completion {shell}, with {shell} replaced by the requested shell. The second ships a usage spec, from which consumers generate completions, man pages, and docs. Declare either entry or both: when both apply, consumers generate completions from the spec before trying exec.

Completions generated from a usage spec need usage’s completion engine each time the shell completes. mise embeds the engine; other consumers install usage beside the tool. If your users may install the tool without mise or usage, ship static completion scripts as well. Consumers use a static script before they generate completions from the spec.

Every cli-spec names the executable it describes. The flag may leave the name out (cli-spec/usage=...) only when exactly one executable is given with --bin or the TOML manifest’s top-level bin. A completion or man entry must name its executable when the release has more than one, as in completion/zsh/mytool=archive:share/zsh/site-functions/_mytool or man/mytool=archive:share/man/man1/mytool.1; create refuses an entry that leaves it out. In TOML, set bin = "mytool" on the entry.

An exec command runs one of the release’s own executables: its first word after any NAME=value words must be a bin name, and create refuses any other program, such as sh. The CLI splits the value on whitespace and does not interpret shell quoting, pipes, or redirections. Leading NAME=value words become environment variables, so --resource 'completion/bash,zsh,fish/mytool=exec:COMPLETE={shell} mytool' runs mytool with COMPLETE set to each shell. In TOML, exec is an array, so an argument may contain spaces. This TOML entry is equivalent to that flag:

[[resource]]
kind = "completion"
bin = "mytool"
shells = ["bash", "zsh", "fish"]
exec = ["mytool"]
env = { COMPLETE = "{shell}" }

Consumers generate exec completions on demand and cache them. Other exec resources require permission to run vendor code during installation. See execution rules.

Man pages and SBOMs

packslip create ... \
  --resource 'man=archive:share/man/man1/mytool.1' \
  --resource 'sbom/cyclonedx=asset:dist/mytool.cdx.json'

The first entry ships a man page from the archive; the file’s suffix gives its section. Name the executable (man/mytool=...) when the release has more than one, as for a completion.

The second ships a CycloneDX SBOM as a separate release file whose digest the packslip records; use sbom/spdx for SPDX. An SBOM must come from an archive, asset, or repo source, so that a digest or commit covers it, and create refuses one from exec. For one SBOM per platform, declare one entry per platform with an @ scope (sbom/cyclonedx@linux/x86_64=...), or one per artifact with the TOML artifact field; see Scope resources to the right artifact.

Agent skills and desktop files

skill/mytool=repo:skills/mytool points at a directory containing SKILL.md at source.commit. For example:

[[resource]]
kind = "skill"
name = "mytool"
repo = "skills/mytool"

Set source.repo and the full source.commit in the manifest, or let the GitHub action set them. To ship a separate archive instead, use skill/mytool=asset:dist/mytool-skill.tar.gz; put SKILL.md at its root or under a single top-level directory. Both forms let consumers provide a skill that matches the installed tool version.

Desktop releases can declare desktop, icon, and app resources. An app entry points to a macOS application bundle in an artifact; Linux applications can pair a desktop entry with an icon. See the desktop recipe for the layout and artifact scopes, and resource kinds for the full rules.

Scope resources to the right artifact

An entry without a scope applies to every artifact. When archive layouts differ by platform, limit the entry to the artifacts of one platform. On the command line, add the scope to the kind with @:

packslip create ... \
  --resource 'man@linux=archive:share/man/man1/mytool.1' \
  --resource 'completion/zsh@darwin/aarch64=archive:share/zsh/site-functions/_mytool'

The scope is os, os/arch, or os/arch/libc. It is read from the kind rather than the value, so an @ inside an exec argv or a path is left alone. In TOML, set os, arch, or libc on the entry:

[[resource]]
kind = "man"
os = "linux"
archive = "share/man/man1/mytool.1"

Spell scope values as the vocabularies do, such as darwin and aarch64, not macos or arm64. create accepts a scope that matches no artifact, and consumers never use such an entry.

Whatever its scope, an archive entry never applies to a bare executable (format raw, gz, xz, zst, or bz2), which has no paths inside it. To give the bare build the same man page, add an asset or repo entry for it.

When archives for the same platform have different layouts, or a resource belongs to one variant, scope the entry to one exact artifact. The command line has no exact-artifact scope; use the artifact field of a TOML [[resource]] table, which names one release file:

[[resource]]
kind = "man"
artifact = "mytool-1.2.3-linux-x64.tar.gz"
archive = "mytool-1.2.3/share/man/man1/mytool.1"

This man page belongs only to the named archive, not to another format or variant for the same platform. The named artifact must be in the release and match any platform scope on the entry, or create refuses the entry. For complete configurations, see Release recipes.

When several entries for the same resource apply, the most specific wins; see How consumers choose among entries.

How consumers choose among entries

Consumers group entries by resource identity before selecting a source. For a completion, the identity is the executable and shell; for a CLI spec, the executable and format; for a skill, its name; for an SBOM, its format. Resource identity gives the identity of every other kind. A bash completion and a zsh completion are separate needs, not fallback choices.

Within one identity, selection proceeds in this order:

  1. Keep entries that apply to the selected artifact.
  2. Prefer exact artifact scope, then the most specific platform scope.
  3. Prefer archive, then asset, then repo sources. For a need a static CLI spec can generate, try that before running an exec resource.
  4. Break ties within the same scope and source type by declaration order. Stop once a usable entry satisfies the need.

For example, these equally scoped entries try the current skill directory first and the older location only if the first is unavailable. The release must also declare source.repo and source.commit.

[[resource]]
kind = "skill"
name = "mytool"
repo = "skills/mytool"

[[resource]]
kind = "skill"
name = "mytool"
repo = ".agents/skills/mytool"

Fallback cannot rescue a wrongly scoped entry. A more specific entry removes every less specific entry for the same resource before any source is tried, so an unscoped entry never backs up a scoped one whose path is wrong. Scope an archive resource to its artifact when other artifacts do not contain that path.

Every resource is optional: if a consumer cannot fetch any entry for a resource, it reports that and installs the executables without it. A digest or source-commit mismatch fails the installation; it never permits trying a different source. See Fallback and verification and consumer rule 8 for the full rules.