Skip to content

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.json

Every 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."
}
KeyMeaning
okfalse
exitProcess exit code
codeServer error code, or null for a local failure
messageFailure description
hintSuggested next step
partialPresent when an operation already completed some effects

Log message and hint together. Inspect partial before repeating a write.

Exit codes ​

CodeMeaningResponse
0SuccessContinue
1Usage or argument errorCorrect the command
2Missing or rejected authenticationCheck the account and credentials
3Target missing or not ownedCheck the target and account
4App or version already existsInspect the existing target
5Version assigned to a channelDelist before deletion
6Server rejected the iconRead the server message
7Listing edit requires reviewUse the portal
8Quota or rate limitRead code and hint
9Local failure; server state unchangedRepair the input or local step
10Network or server errorCheck state before retrying a write
11Server effect completed; later local step failedInspect partial and repair the local step
130InterruptedCheck 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.

CommandSuccess fields
loginemail, role
self-checkid, namespace_id, email, role, username, contact_email
logoutlocal_removed, server_invalidated
initpath
qrurl; path with --external; cleared with --clear
packpath, size, package_id, version, min_app_version, warnings
createpackage_id, draft_id, manifest, initial_build, icon, description_applied, next, warning
uploadpackage_id, version, id, status, size, next
updatepackage_id, updated
lsapps, total, next
ls <package_id>package_id, total, versions, next; app and review with --verbose
promote, delistpackage_id, branch, version, next
testerspackage_id, testers, total
testers addinvited, skipped, reinvited, case_variants
testers rmremoved
rmpackage_id, plus removed_version or removed_app
downloadpackage_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.

InputValidation
Listing fieldsField lengths, whitespace and line-ending normalisation
IconPNG dimensions, colour and 2×2 blocks
Package.ehpk extension, at most 100 MB
EmailValid format, at most 200 characters
Package IDReverse-domain format with at least two segments
Public promotionRefused 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 0

The 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.bash

Add the source line to ~/.bashrc to load it in future sessions.

Zsh ​

zsh
mkdir -p ~/.zfunc
evenhub --completion-zsh > ~/.zfunc/_evenhub

Add ~/.zfunc to fpath before completion is initialised in ~/.zshrc:

zsh
fpath=(~/.zfunc $fpath)
autoload -Uz compinit
compinit

If 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.fish

Fish loads completions by command name, so the alias uses its own file.