Theme
Last updated: 2026-10-20
Use --json for machine-readable results. Parse stdout, keep diagnostics from stderr, and branch on the process exit code.
JSON output
Each command with --json returns one JSON object on stdout. Warnings and progress go to stderr. Login still requires an interactive terminal; prepare CI credentials before running authenticated jobs.
bash
evenhub ls --json > apps.json
evenhub ls com.example.myapp --verbose --json > builds.jsonEvery successful command result includes "ok": true.
The failure object
json
{
"ok": false,
"exit": 5,
"code": 1020,
"message": "version is referenced by branch(es): beta",
"hint": "Delist the referenced channel before deleting this build."
}| Key | Meaning |
|---|---|
ok | false |
exit | Process exit code |
code | Server error code, or null for a local failure |
message | Failure description |
hint | Suggested next step |
partial | Present when an operation already completed some effects |
Log message and hint together. Inspect partial before repeating a write.
Exit codes
| Code | Meaning | Response |
|---|---|---|
| 0 | Success | Continue |
| 1 | Usage or argument error | Correct the command |
| 2 | Missing or rejected authentication | Check the account and credentials |
| 3 | Target missing or not owned | Check the target and account |
| 4 | App or version already exists | Inspect the existing target |
| 5 | Version assigned to a channel | Delist before deletion |
| 6 | Server rejected the icon | Read the server message |
| 7 | Listing edit requires review | Use the portal |
| 8 | Quota or rate limit | Read code and hint |
| 9 | Local failure; server state unchanged | Repair the input or local step |
| 10 | Network or server error | Check state before retrying a write |
| 11 | Server effect completed; later local step failed | Inspect partial and repair the local step |
| 130 | Interrupted | Check state if a write was in progress |
Exit 9 means no server change occurred. Exit 11 identifies completed effects; replaying the command can repeat them. Exit 10 can leave a write's outcome uncertain.
Output keys per command
All rows below also include ok. Some fields depend on options or the result.
| Command | Success fields |
|---|---|
login | email, role |
self-check | id, namespace_id, email, role, username, contact_email |
logout | local_removed, server_invalidated |
init | path |
qr | url; path with --external; cleared with --clear |
pack | path, size, package_id, version, min_app_version, warnings |
create | package_id, draft_id, manifest, initial_build, icon, description_applied, next, warning |
upload | package_id, version, id, status, size, next |
update | package_id, updated |
ls | apps, total, next |
ls <package_id> | package_id, total, versions, next; app and review with --verbose |
promote, delist | package_id, branch, version, next |
testers | package_id, testers, total |
testers add | invited, skipped, reinvited, case_variants |
testers rm | removed |
rm | package_id, plus removed_version or removed_app |
download | package_id, version, path, size, public_key |
Log non-empty pack.warnings. The create.icon value is uploaded or default; default identifies a placeholder. Invitation arrays report submitted requests, not email delivery; see Testers.
What is checked locally
Invalid input is rejected before the corresponding write. Validation errors generally exit 9; command syntax errors exit 1.
| Input | Validation |
|---|---|
| Listing fields | Field lengths, whitespace and line-ending normalisation |
| Icon | PNG dimensions, colour and 2×2 blocks |
| Package | .ehpk extension, at most 100 MB |
| Valid format, at most 200 characters | |
| Package ID | Reverse-domain format with at least two segments |
| Public promotion | Refused locally; publish through the portal |
Rate limit
The CLI does not automatically retry rate-limited requests. For server code 1007, follow the hint to wait before retrying. Other errors sharing exit 8 can indicate an invitation or app quota; waiting alone may not resolve them.
ls fetches one request per page. ls --channels adds a channel request per app. Reuse the result of ls --json when a script needs the same app list repeatedly.
Draft reuse
A valid cached draft can avoid re-uploading the same package after an interrupted create or upload. Drafts expire after 30 minutes and are isolated by account, package and API base. See Builds for the full rules.
To reuse a draft across CI retries, retain the job's isolated config directory securely. A fresh directory or an expired draft requires another upload. Cache errors warn on stderr and do not fail an otherwise successful API operation.
A CI script
This Bash example updates an existing app. It requires jq, a working project build script and restored CLI credentials. Set CI_CACHE, SDK_VERSION and CI_COMMIT_TITLE, and replace PKG with your package ID.
An exit-4 retry below continues with the existing version. Use that path only when the version identifies the same intended artifact, such as a retry of the same build. If its origin or contents are uncertain, inspect it before promoting; the CLI does not compare package bytes for you.
bash
#!/usr/bin/env bash
set -uo pipefail
export XDG_CONFIG_HOME="$CI_CACHE/evenhub" # credentials + draft cache
PKG=com.example.myapp
# A stale dist/ from the previous run would pack and ship happily, so the build
# has to be checked itself.
npm run build || { echo "Build failed"; exit 1; }
evenhub pack app.json dist -o out.ehpk --sdk-ver "$SDK_VERSION" --json > pack.json
case $? in
0) ;;
9) echo "Manifest or project is wrong, not a transient failure"; exit 1 ;;
*) echo "Packing failed"; exit 1 ;;
esac
VERSION=$(jq -er .version pack.json) || exit 1
evenhub upload out.ehpk -m "$CI_COMMIT_TITLE" --package-id "$PKG" --json > upload.json
case $? in
0) echo "Uploaded $VERSION" ;;
4) echo "Version $VERSION is already on the server, carrying on" ;;
2) echo "Credentials expired, refresh the stored credentials.yaml"; exit 1 ;;
8)
jq -r '.message, .hint' upload.json
if jq -e '.code == 1007' upload.json > /dev/null; then exit 75; fi
exit 1 ;;
11) jq -r '.message, .hint, .partial' upload.json; echo "Inspect completed effects before continuing"; exit 1 ;;
*) jq -r '.message, .hint' upload.json; exit 1 ;;
esac
evenhub promote "$VERSION" --to beta --package-id "$PKG" --json > promote.json || {
jq -r '.message, .hint' promote.json
exit 1
}
# Both gates, before anyone reports the build missing. Each query's exit code is
# read first: jq failing on a failure object looks the same as jq finding no
# match, which would turn an expired credential into "no Active testers".
evenhub ls "$PKG" --json > builds.json || { jq -r '.message, .hint' builds.json; exit 1; }
jq -e --arg v "$VERSION" \
'.versions[] | select(.version == $v) | select(.channels | index("beta"))' \
builds.json > /dev/null || { echo "Build is not on beta"; exit 1; }
evenhub testers --package-id "$PKG" --json > testers.json \
|| { jq -r '.message, .hint' testers.json; exit 1; }
jq -e '[.testers[] | select(.status == "Active")] | length > 0' testers.json > /dev/null \
|| echo "Warning: no Active testers, nobody can install this yet"
exit 0The build must succeed before packing. An existing upload still needs promotion and channel verification. A failed tester query must fail the job; a successful query with no Active testers produces a warning.
Exit 11 does not prove a build was created. Inspect partial and repair the reported local failure before continuing.
Shell completion
Completion scripts support both evenhub and eh. Generate the script for your shell, then load it through your shell configuration.
Bash
bash
evenhub --completion-bash > ~/.evenhub-completion.bash
source ~/.evenhub-completion.bashAdd the source line to ~/.bashrc to load it in future sessions.
Zsh
zsh
mkdir -p ~/.zfunc
evenhub --completion-zsh > ~/.zfunc/_evenhubAdd ~/.zfunc to fpath before completion is initialised in ~/.zshrc:
zsh
fpath=(~/.zfunc $fpath)
autoload -Uz compinit
compinitIf your shell framework already calls compinit, add only the fpath line before that setup.
Fish
fish
mkdir -p ~/.config/fish/completions
evenhub --completion-fish > ~/.config/fish/completions/evenhub.fish
echo 'complete -c eh -w evenhub' > ~/.config/fish/completions/eh.fishFish loads completions by command name, so the alias uses its own file.