Release a bundle with the artific command¶
The concept behind publications is explained in Sharing and reuse.
artific is a command that releases a bundle kept as files. The prompts, agents, toolsets and
other members of a bundle sit in a directory you keep in git, and the command checks that
directory against Core, tries it out in a test organisation, publishes it as a new version and
submits it for the marketplace. It calls the same routes as
Publish and install through the API, so what it publishes is an ordinary
publication: subscribers install and sync it exactly as one you publish in the console.
Use it when a bundle changes through pull requests, or when a pipeline should publish it. For a bundle you assemble and publish by hand, the console is the shorter route.
publish, try and submit are safe to repeat: they write only what differs from what Core
holds, and a second run against an unchanged project writes nothing and says so.
What you need¶
- A key of the organisation that publishes, holding
management. The command acts for that organisation and for nothing else. Manage organisations through the API shows how to switch that capability on; Keys and policy in the console shows how to create a key and keep its secret. - The address of your Core, such as
https://core.example.com.
That is all. Your own Core key, as ARTIFIC_API_KEY, with your Core's address, as
ARTIFIC_ENDPOINT, is the only credential the command and its GitHub Action ever need. You need no
GitHub account, token or access to an Artific repository: the downloads and the action are public.
The token Artific's own release workflow uses is Artific's alone; it only publishes new versions of
the command and is never one you create.
Install the command¶
Each release of the command is a GitHub release of the public repository
artific-ai/artific-cli with the tag
v<version>, such as v0.1.0. It holds one archive per system and a SHA256SUMS file with the
checksum of each archive. An archive holds the artific binary alone. You need no account or token
to download one.
| Your system | Archive |
|---|---|
| Linux, x86-64 | artific-<version>-x86_64-unknown-linux-musl.tar.gz |
| Linux, ARM64 | artific-<version>-aarch64-unknown-linux-musl.tar.gz |
| macOS, Apple silicon | artific-<version>-aarch64-apple-darwin.tar.gz |
| macOS, Intel | artific-<version>-x86_64-apple-darwin.tar.gz |
The Linux binaries are static, so they run on any distribution and in any container. There is no Windows build.
To install one:
-
Download the archive for your system and the checksums. On an Apple silicon Mac:
VERSION=0.1.0 ARCHIVE=artific-$VERSION-aarch64-apple-darwin.tar.gz BASE=https://github.com/artific-ai/artific-cli/releases/download/v$VERSION curl -fLO "$BASE/$ARCHIVE" curl -fLO "$BASE/SHA256SUMS" -
Check the archive against
SHA256SUMS. The command prints<archive>: OK; anything else means the download is not the released file, so delete it and do not unpack it.grep " $ARCHIVE\$" SHA256SUMS | shasum -a 256 -cOn Linux,
sha256sum -cdoes the same asshasum -a 256 -c. -
Unpack the binary and move it to a directory on your
PATHthat you can write to. For/usr/local/binyou often needsudo:tar -xzf "$ARCHIVE" artific sudo mv artific /usr/local/bin/ artific --version
Always name an exact version: the archive name carries it, and there is no "latest". A released version never changes; a fix comes in a later version.
Point it at Core¶
Every step except artific build reads two environment variables:
export ARTIFIC_ENDPOINT=https://core.example.com
export ARTIFIC_API_KEY=ack_...
ARTIFIC_ENDPOINT is the address of your Core without /api/v1: the command adds the path of
each route itself. This differs from the CORE_URL the other pages of this guide use.
The address must start with https://, so the key never travels unencrypted; http:// is accepted
only for localhost, 127.0.0.1 and [::1]. The key is sent only in the Authorization header,
and nothing the command prints contains it. When a variable is missing, the command stops with
artific: ARTIFIC_ENDPOINT is not set (or ARTIFIC_API_KEY).
Lay out the project¶
A bundle is a directory. Long text sits in files of its own, so a pull request shows a changed prompt as a text diff:
support-desk/
artific.yaml the bundle: name, description, visibility, terms, parameters
members/
prompt/triage.yaml one member per file: the kind is the directory, the name the file
prompt/triage.template.md text that triage.yaml includes with !file
agent/desk.yaml
toolset/desk-tools.yaml
script/house_deck.yaml a script member
script/house_deck/ that script's files
packages/
deckkit-1.0.0-py3-none-any.whl a package file a script carries
covers/
listing.png the picture on the catalogue card
agent/desk.png the cover of one member
CHANGELOG.md its top section becomes the version's changelog
The quickest way to a correct project is to build the bundle once in the console, publish a version, and pull it: the command writes that version as a directory in exactly this shape, and you continue from there.
The bundle file¶
artific.yaml describes the publication and the parts of the bundle that belong to no member:
name: support-desk
description: Triage and answer support tickets.
visibility: subtree
terms:
fork: false
parameters:
company:
type: string
description: The company name the agent uses in its answers.
| Key | What it is |
|---|---|
name |
The publication's name in your organisation: lowercase letters, digits, - and _, starting with a letter or a digit, at most 64 characters. Required. |
description |
What the bundle is for, at most 1,024 characters. |
visibility |
subtree (organisations below yours), tenants (the organisations in targets) or catalogue (the marketplace). Without this key the bundle goes to the marketplace. |
targets |
For tenants only: the ids of the organisations that may subscribe. |
auto_install |
For subtree only: true subscribes every new organisation below yours straight away. |
terms |
The version's terms: fork, sealed, redistribute, own_agents_only, entitlement, license and price, as described in Publish and install in the console. |
parameters, requires, policy |
Written as in a manifest; a pulled project shows them for a version you already published. |
visibility, targets and auto_install take effect when the command creates the publication.
After that, a publication's name and visibility stay as they are.
Members¶
Each file under members/<kind>/ is one member: its kind is the directory (prompt, agent,
toolset, script, …), its name is the file name without .yaml, and its content is the member's
definition. A member refers to another member of the bundle as {$member: <name>}, and to a
parameter as {$param: <name>}. A member installed only for a certain choice of the installer
states that condition under $when.
# members/prompt/triage.yaml
description: Sorts an incoming ticket.
template: !file triage.template.md
!file reads a text file, relative to the YAML file that names it. It cannot name a file outside
the project directory, and no other YAML tag is accepted, so a project never reads anything from
the machine it is built on.
A script's files are the directory with the script's name next to its member file
(members/script/house_deck/). The command lists every file in it with its size and checksum, so
the member file itself never states files. That directory may hold only regular files, not
symbolic links. A package that only your organisation approved travels with the bundle
(Packages in a bundle): the member names it with
ecosystem, name, version and filename, and the file sits in packages/ under that
filename. The command adds its size and checksum.
Secrets never go in a project. A connector's credential is something the installer supplies, so no field of a member can hold one.
Covers and changelog¶
covers/listing.png is the picture on the catalogue card. A member cover goes in
covers/<kind>/<name>.png, for an agent, a toolset, an API connector, an MCP server or a tool
alias. A cover is a .png, .jpg or .webp of at most 8 MiB.
The changelog of a version is the text under the first ## heading of CHANGELOG.md, up to the
next one, at most 4,096 characters. The heading itself, such as ## 1.4.0, names the release for
you; Core numbers its versions itself, 1, 2, 3 and on.
One manifest file instead¶
A bundle may also stay one manifest.json: the manifest field of what
GET /publications/{pid}/versions/{v} returns. Next to it, artific.yaml then holds only what the manifest does not: visibility,
targets, auto_install and terms. The command refuses an artific.yaml that states the name,
the description or anything else the manifest already holds.
See what the project builds¶
artific build --project support-desk
prints the one manifest the project builds and contacts nothing. Every step except pull takes
--project, which names the project directory or a manifest.json; it defaults to the current
directory.
Release a version¶
| Step | What it does |
|---|---|
artific check |
Asks Core whether it would accept the bundle. Publishes nothing. |
artific try --org <id> |
Installs the bundle in a test organisation as a preview. |
artific publish |
Creates the publication when needed and publishes a version when the bundle changed. |
artific submit |
Submits a marketplace bundle for review. |
Check¶
artific check --project support-desk
Core validates the bundle with the same rules a publish runs: every member, every reference and parameter, and the terms. On success the command says whether a publish would write a version and which capabilities a subscriber's organisation needs:
support-desk: accepted; publish would write version 4
capabilities a subscriber needs: tooling, computer
On a refusal it prints each field Core named, with Core's own message, and exits with code 1. A price without an entitlement, for example (Set a price):
artific: checking support-desk: 422 Unprocessable Content: ...
terms.price: ...
A check belongs to a publication, so the very first release of a bundle cannot be checked yet; the
command says artific publish creates the publication. Core checks a script only over files the
publication holds, so check uploads any script or package file the publication does not hold
yet. It writes no version.
Try it in a test organisation¶
An organisation cannot install its own marketplace bundle. To see the install as a subscriber will see it, try the bundle in an organisation below yours:
artific try --project support-desk --org ten_9Fb2...
This publishes the bundle as a separate publication, support-desk-preview, visible only to that
organisation, and subscribes the organisation to it. Open the test organisation and look under
Installed: the parameters are asked, the capabilities checked and the copies written as for any
subscriber. When the command prints subscribed, needs_configuration, a required parameter has no
value yet: fill it in on the subscription under Installed
(Installing). Run try again after a change and the preview gets
the new version.
The preview's terms never ask for an entitlement and carry no price, because a test organisation holds no entitlement. A preview stays until you withdraw it:
artific try --project support-desk --withdraw
A withdrawal is final and the name stays taken, so the next preview is called
support-desk-preview-2, and so on. While a preview targets one organisation, a try in another
is refused until you withdraw it.
Publish¶
artific publish --project support-desk
The command creates the publication when your organisation has none by that name: a marketplace publication as a draft, the others listed right away. It then uploads the script files and packages the publication does not hold yet, publishes a version when the manifest or the terms differ from the highest version, uploads covers whose bytes changed and sets the description when it changed. It prints one line for each thing it wrote:
publication support-desk: created (listed)
version 1: published
cover listing: uploaded
What happens to a new version depends on the visibility:
- Organisations below or named organisations: every subscription that follows the latest
version syncs to it straight away. Run
checkandtryfirst. - Marketplace: a draft is offered to nobody until Artific lists it. On a bundle already listed, the new version waits for review, and subscribers keep the version they have. A changed description of a listed marketplace bundle also waits, and the command says so.
Submit for the marketplace¶
artific submit --project support-desk
submits a marketplace draft for review, as Submit for review does in the console (Marketplace). A listed bundle with a newer version waits for review already, and the command leaves it alone. A bundle for organisations below or named organisations is never submitted; the command refuses it. Listing the bundle stays Artific's decision.
publish and submit are separate steps, so a pipeline can publish on every merge and submit only
when you tag a release.
Turn a published version into files¶
artific pull support-desk
artific pull support-desk 3 --into support-desk-v3
writes a version of one of your organisation's publications as a project directory: the highest
version unless you name one, into a directory with the publication's name unless you give
--into. The directory must be empty or not exist yet. Multi-line text in a definition is written
to <member>.<field>.md next to the member file, and the script files, packages, covers and the
changelog come along. Building the pulled directory gives back the manifest, terms, changelog and
covers of that version, so a bundle first assembled in the console becomes a project this way.
Messages and exit codes¶
| Exit code | Meaning |
|---|---|
0 |
The step finished, whether or not it wrote anything. |
1 |
Core refused the request, or the project or the environment is wrong. The message on standard error says what. |
2 |
The command line does not parse: an unknown step or flag, or a missing argument. |
An error line starts with artific: and names what the command was doing, such as
artific: publishing a version of support-desk: ..., followed by the status and title of Core's
answer when Core refused. A refusal that names fields follows
with one indented line per field. artific <step> --help lists a step's flags.
Set a price¶
A bundle can show a price in the marketplace. It is a term of the version:
terms:
entitlement: true
price:
amount: "49.00"
currency: EUR
period: month
period is once, month or year, and amount is a decimal with at most two decimals. Core
stores the price and shows it, and never charges it: an organisation installs a bundle that asks
for an entitlement only once it holds one, and the payment happens outside Core
(When a bundle needs an entitlement). That is why a price without
entitlement: true is refused (422 on terms.price). A changed price is changed terms, so every
subscription waits for its organisation to accept the new version's terms; no subscriber moves to a
new price unasked.
Release from a GitHub pipeline¶
The same public repository also holds a GitHub Action, artific-ai/artific-cli, that installs one release
of the command, checks it against SHA256SUMS, and runs one step with it. It works in any
repository, in any organisation, without a token: the only secret you need is the key.
| Input | What it is |
|---|---|
version |
The exact release, such as 0.1.0. Required; there is no "latest". |
command |
The step: build, check, try, publish, submit or pull. |
args |
Further arguments on one line, such as --project bundles/support-desk. They are split on spaces, so an argument cannot contain one, and never expanded by a shell. |
endpoint |
ARTIFIC_ENDPOINT. Every step except build needs it. |
api-key |
ARTIFIC_API_KEY, from a secret. |
A workflow that checks every pull request, publishes every merge to main, and submits on a tag:
name: bundles
on:
pull_request:
push:
branches: [main]
tags: ["v*"]
permissions:
contents: read
jobs:
check:
if: github.event_name == 'pull_request'
runs-on: ubuntu-24.04
environment: core
steps:
- uses: actions/checkout@v4
- uses: artific-ai/artific-cli@v0.1.0
with:
version: 0.1.0
command: check
args: --project bundles/support-desk
endpoint: ${{ vars.ARTIFIC_ENDPOINT }}
api-key: ${{ secrets.ARTIFIC_API_KEY }}
release:
if: github.event_name == 'push'
runs-on: ubuntu-24.04
environment: core
steps:
- uses: actions/checkout@v4
- uses: artific-ai/artific-cli@v0.1.0
with:
version: 0.1.0
command: ${{ github.ref_type == 'tag' && 'submit' || 'publish' }}
args: --project bundles/support-desk
endpoint: ${{ vars.ARTIFIC_ENDPOINT }}
api-key: ${{ secrets.ARTIFIC_API_KEY }}
The GitHub environment core holds the address as the variable ARTIFIC_ENDPOINT and the key as
the secret ARTIFIC_API_KEY. With several Core installations, such as a test and a production one,
give each its own environment and run the step once per environment. A repository with several
bundles repeats the step for each project.
- Pin both.
@v0.1.0afteruses:picks the action,versionthe binary; change them together. The tags ofartific-ai/artific-cliare never moved. If your repository pins every action to a commit, name the full commitv0.1.0points at, with the tag as a comment. - Check needs the publication. The first pull request of a new bundle fails at
checkuntilpublishhas created the publication once. - The key is a credential like any other. Rotate it as described in Rotate a key, and update the secret.