Publishing an app (people, CLI, loader and AI agents)
For: anyone putting an app on a D9010 VmApp registry: by hand, from the loader, from a terminal, or from an AI agent or CI pipeline. New to D9010 VmApp? Do levels 1–5 of Start here first; you don't need a registry to build, test or share an app. For trust levels (verified publishers) and how downloads are counted, see Distributing.
1. How ids, URLs, versions and tokens fit together#
| Thing | Where it comes from | Changes? |
|---|---|---|
App id (com.you.myapp) |
id in your vmapp.json, chosen by you |
never: it identifies your app everywhere (users' data, grants, the registry page) |
| App page | <registry>/apps/<app id> |
never |
Version (1.4.2) |
version in your vmapp.json |
bump it for every publish; it must be higher than any version already published |
| Download URL | <registry>/api/apps/<app id>/<version>/download (or /latest/download) |
one per version, immutable |
| Owner token | returned once by your first publish | keep it secret; it's the only proof that later versions are yours |
| Package id | the hash of your manifest, printed by vmapp inspect |
different for every build; it identifies exactly what you published |
The first publish of an id claims it. After that, the registry accepts new versions of that id only with its owner token. One token can own several apps: send it with the first publish of another id, and that app joins your publisher.
2. Build the artifact#
tools/vmapp pack <project dir> -o dist/<name>-<version>.vmapp # the one file you publish
tools/vmapp verify dist/<name>-<version>.vmapp # the same checks the registry runs
tools/vmapp inspect dist/<name>-<version>.vmapp # id, version, package id, sizes
3. Four ways to publish#
3.1 From the loader (no terminal, no website)#
Open the app's card and choose Publish… (it works on any installed app; the usual case is a developer build made with Run project folder…, then Rebuild & run after each change):
- Enter the registry address.
- The first time, leave the owner token empty and enter your publisher details (all optional; see DISTRIBUTING.md, "What users see about you", for what is shown publicly).
- Click Publish. You get the app page address and, on a first publish, your owner token once.
The loader uploads the package it installed, keeps your owner token in its own data folder
(publish-tokens.json, readable only by you, the same file vmapp publish uses) and fills it in for later versions of
the same app. Errors appear in the sheet with the plain-words meaning of §3.4's table. A plain http:// registry
on another computer gets a warning (development only).
3.2 From the website#
Open <registry>/publish and choose the file. The first time, copy the owner token the page shows; it's shown only
once. For later versions, paste the token into the token field.
3.3 From the CLI#
export VMAPP_REGISTRY=https://registry.example.org # or pass --registry
tools/vmapp publish dist/my-app-1.0.0.vmapp --name "Ada Lovelace" --email ada@example.com
# → app page URL + owner token (shown once, and saved to $VMAPP_HOME/publish-tokens.json, mode 0600)
tools/vmapp publish dist/my-app-1.0.1.vmapp # later versions: the saved token is used automatically
To use the token on another machine or in CI, set VMAPP_PUBLISH_TOKEN, or pass --token.
A registry that keeps its packages in cloud storage (GET <registry>/api/info lists "presigned" among publishModes) receives
the file there directly: the CLI and the loader ask POST /api/publish/init {size, sha256} for a 15-minute upload address, PUT
the package to it, and finish with POST /api/publish/complete {uploadId}, where the registry fetches the file and verifies it
exactly as for a direct upload. You notice nothing except that big packages work behind a proxy that limits uploads (Cloudflare:
100 MB). vmapp publish --direct sends the file through the registry instead.
3.4 From an AI agent or CI (the HTTP API)#
First publish (it claims the id and creates your publisher):
curl -sS -X POST "$VMAPP_REGISTRY/api/publish" \
-H 'Content-Type: application/octet-stream' \
-H 'X-Vmapp-Publisher: {"name":"Ada Lovelace","email":"ada@example.com","url":"https://example.com"}' \
--data-binary @dist/my-app-1.0.0.vmapp
# 201 {"id":"com.you.myapp","version":"1.0.0","packageId":"…","url":"…/app/com.you.myapp","ownerToken":"…"}
Store ownerToken as a secret, for example VMAPP_PUBLISH_TOKEN in your CI secrets. Never commit it.
Every later version:
curl -sS -X POST "$VMAPP_REGISTRY/api/publish" \
-H "Authorization: Bearer $VMAPP_PUBLISH_TOKEN" \
-H 'Content-Type: application/octet-stream' \
--data-binary @dist/my-app-1.0.1.vmapp
What the responses mean:
| Status | Meaning | What to do |
|---|---|---|
| 201 | published | report the url |
| 400 | the package is invalid (the message says why) | run vmapp verify, fix, re-pack |
| 401 | this id exists and you sent no token | use the token from the first publish |
| 403 | the token doesn't own this id | wrong token, or someone else claimed the id; pick another id only if it isn't yours |
| 409 | this version exists or isn't higher | bump version in vmapp.json, re-pack |
| 413 / 415 | too large / wrong content type | max 2047 MiB (SPEC §2; a registry may set less); send application/octet-stream |
| 429 | rate limited (10 publishes per hour per address by default) | wait, then retry |
| 502 / 503 | the registry's storage could not be written | nothing was published; retry later |
For a package larger than the registry's proxy accepts, use the presigned form: GET $VMAPP_REGISTRY/api/info; if publishModes
lists presigned, POST /api/publish/init with {"size": <bytes>, "sha256": "<hex>"} (same Authorization header), PUT the
file to the returned url (exactly size bytes, within 15 minutes), then POST /api/publish/complete with {"uploadId": "…"} and
the same headers a direct publish uses (Authorization, or X-Vmapp-Publisher on a first publish). The answers are the same 201
and error codes (plus 400 upload_missing / upload_mismatch, 404 unknown_upload, 409 upload_changed).
The agent's procedure (copy into your agent's instructions):
To publish the app in
<project dir>to$VMAPP_REGISTRY:
- Read
idandversionfromvmapp.json. Check the app's existing versions:GET $VMAPP_REGISTRY/api/apps/<id>/versions. A 404 means a first publish.- If the id exists, make sure
versionis higher than the highest listed. If it isn't, bump the patch number (or as instructed) invmapp.json.tools/vmapp pack <project dir> -o dist/<name>-<version>.vmapp, thentools/vmapp verifyit. Stop on errors.- Publish with
tools/vmapp publish <file>(it handles the token), or with the curl calls above using$VMAPP_PUBLISH_TOKEN.- On a first publish, save the returned owner token only to the secret store you were given (never to the repository or logs), and tell the human it exists.
- Report the app page URL and the package id.
- Never change the app id to get around a 403. Report it instead.
4. Updates, choices and withdrawing#
- Ship an update: bump
version, pack and publish. Users see it on the same page. - Version numbers mean something to users. Their loader's Latest compatible choice never moves them across a
major version, so put breaking changes in a new major (
2.0.0). - A bad version: withdraw it, with a reason users will see. It's never chosen automatically again, but users
who pinned it can still get it.
The API equivalents aretools/vmapp withdraw com.you.myapp 1.0.1 --reason "Saving fails on large notes; use 1.0.2" tools/vmapp restore com.you.myapp 1.0.1POST /api/apps/<id>/<version>/withdraw {"reason":"…"}and/restore, each with the owner token. - New permissions in an update: users are always asked before such an update installs, whatever their update choice. Explain the change in your description.
5. Losing your token#
There's no account recovery in this version of the registry. A lost token means you can't publish new versions of that id. Keep the token in a password manager or secret store. Account recovery comes with publisher accounts, later.