Hands
Hands Docs

CLI Reference

Install and use @botiverse/hands-cli from local scripts or CI.

@botiverse/hands-cli is the command-line client for Hands. Use it from local scripts or CI to inspect apps, upload Android builds, and publish releases.

Install

Install it globally:

npm install -g @botiverse/hands-cli

Or run it without a permanent install:

npm exec --package @botiverse/hands-cli@0.3.2 -- hands --help

In CI, pin a version so release scripts stay reproducible.

Authentication

The CLI reads a Hands API server and bearer token from environment variables:

export HANDS_API=https://hands.build
export HANDS_BEARER_TOKEN=<deploy-token>

If HANDS_API is unset, the CLI defaults to https://hands.build. HANDS_AUTH_TOKEN is also accepted as an alias for HANDS_BEARER_TOKEN.

Legacy QUIVER_* names still work: every variable is read as HANDS_<name> first, then QUIVER_<name>, so existing CI keeps working unchanged.

Use app-scoped deploy tokens for CI. Mint them with hands deploy-tokens create (below) or in the app's Settings page, choose the minimum role required, and store the raw token in your CI secret store.

Raft Agent Login sessions are not exported to this CLI. raft integration login authenticates raft integration invoke; it does not populate HANDS_AUTH_TOKEN. An admin agent that needs to bootstrap CI should call the manifest create-deploy-token action, write the returned one-time token directly to the CI secret store, and then let the CLI consume that deploy token.

Deploy Tokens

App-scoped tokens for CI to publish (publisher) or read (viewer). Managing them requires app admin; if you lack it, the CLI prints an actionable error (which role you need, who can grant it, and that an admin can do it for you).

# Mint a publisher token for CI (printed ONCE — capture it immediately)
hands deploy-tokens create raft-android --name github-ci --role publisher

# Optional expiry, and machine-readable output for scripting
hands deploy-tokens create raft-android --name github-ci --expires-in-days 90 --json

# List an app's tokens (metadata only, never the secret)
hands deploy-tokens list raft-android

# Revoke by id
hands deploy-tokens revoke raft-android <tokenId>

The created token is what you set as HANDS_BEARER_TOKEN in CI. The server stores only a hash, so a lost token can only be revoked and re-minted, never recovered.

Basic Commands

Show the installed version:

hands version

List apps visible to the current token:

hands apps list

List builds for an app:

hands builds list raft-android

Publish Android

Use builds publish-android to upload an APK and create a release. Per the release policy, CI should pass --draft so a human or agent reviews the changelog before the release goes live.

hands builds publish-android raft-android \
  --apk ./androidApp-release.apk \
  --channel preview \
  --version-name 1.0.0 \
  --version-code 1000000 \
  --changelog-file ./changelog.txt \
  --draft

Package id, SDK levels, and the launcher icon are extracted from the APK automatically on the server — no extra flags needed.

Add support artifacts when available:

hands builds publish-android raft-android \
  --apk ./androidApp-release.apk \
  --mapping ./mapping.txt \
  --symbols ./native-symbols.zip \
  --metadata ./metadata.json \
  --channel preview \
  --version-name 1.0.0 \
  --version-code 1000000

Public update checks only use the installable artifact. Mapping files, native symbols, and metadata stay available through authenticated admin APIs.

Publish iOS / TestFlight

CI should upload the signed IPA exported by macOS/Xcode, not an unsigned intermediate IPA. Hands stores and parses the IPA, but Apple signing material stays in the CI secret boundary.

Use builds publish-ios after xcodebuild archive and xcodebuild -exportArchive:

hands builds publish-ios raft-ios \
  --ipa ./build/Raft.ipa \
  --dsym ./build/Raft.dSYM.zip \
  --channel main \
  --version-name 1.0.0 \
  --version-code 1000000 \
  --changelog-file zh=./changelog.zh.md \
  --changelog-file en=./changelog.en.md \
  --source-commit "$GITHUB_SHA" \
  --ci-provider github-actions \
  --ci-run-id "$GITHUB_RUN_ID" \
  --ci-url "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \
  --export-method app-store \
  --appstore-build-number 42 \
  --draft

--version-code must match the value the app reports (CFBundleVersion), or iOS crashes will not symbolicate against the right dSYM. The --dsym file is a .dSYM.zip of the archive's *.dSYM bundles; without it, iOS crashes for that version show only raw frames. --export-method, --appstore-build-number, and --testflight-status are recorded as build metadata.

The same signed IPA should then be uploaded to App Store Connect/TestFlight from the macOS CI job using Apple-supported tooling such as Transporter or fastlane pilot. Hands does not sign IPA files and should not receive Apple .p8, .p12, provisioning profiles, or passwords.

For raw CI drafts, a single --changelog-file ./changelog.txt is still valid. For reviewed notes, prefer repeatable lang=file entries such as --changelog-file zh=zh.md --changelog-file en=en.md.

Publish HarmonyOS / OHOS

Use builds publish-ohos after CI has assembled and signed the App Pack. The command stores both distribution paths on one build: .app for AppGallery and the standalone signed .hap for user sideloading.

hands builds publish-ohos raft-ohos \
  --app ./raft-ohos-1.0.0-1000000.app \
  --hap ./raft-ohos-entry-1.0.0-1000000.hap \
  --symbols ./ohos-symbols-1.0.0-1000000.tar.gz \
  --metadata ./ohos-release-metadata.json \
  --channel main \
  --version-name 1.0.0 \
  --version-code 1000000 \
  --source-commit "$GITHUB_SHA" \
  --ci-provider github-actions \
  --ci-run-id "$GITHUB_RUN_ID" \
  --ci-url "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \
  --draft

Both .app and .hap are installable assets with distinct file types and distribution metadata. Public update consumers can request filetype=app or filetype=hap; authenticated build views expose both files individually. Signing certificates, profiles, P12 files, and passwords remain in CI and are never uploaded to Hands.

Publish Electron (generic provider)

Hands can host Electron apps that use electron-updater with the generic provider:

autoUpdater.setFeedURL({
  provider: "generic",
  url: "https://hands.build/electron/raft-desktop/main"
});

Hands hosts electron-builder output as-is. Use builds publish-electron to upload the files from dist/ as build assets on an electron-installer build/release, then create a draft release for review:

hands builds publish-electron raft-desktop \
  --channel main \
  --version-name 1.2.3 \
  --version-code 10203 \
  --platform win32 \
  --arch x64 \
  --metadata dist/latest.yml \
  --installer "dist/Raft Setup 1.2.3.exe" \
  --blockmap "dist/Raft Setup 1.2.3.exe.blockmap" \
  --changelog-file ./changelog.txt \
  --draft

--metadata, --installer, and --blockmap are repeatable. For multi-platform Electron apps, run the command once per platform/channel so each release has a clear platform/arch pair. The command preserves original filenames through variant and metadata_json.filename, which lets relative URLs inside latest*.yml resolve unchanged.

Required files depend on target OS:

TargetRequired files
Windows NSISlatest.yml, installer .exe, optional .exe.blockmap
macOSlatest-mac.yml, signed .zip for auto-update, optional .dmg for downloads, .blockmap files if generated
Linuxlatest-linux.yml, AppImage or other configured target, .blockmap files if generated

CI systems that need custom orchestration can also call the build, asset, and release APIs directly. Register the original filenames using the same fields:

Fileplatformarchfiletypeartifact_kindFilename field
latest.ymlwin32x64 or nullymlelectron-metadatavariant or metadata_json.filename
latest-mac.ymldarwinarm64 or x64ymlelectron-metadatavariant or metadata_json.filename
latest-linux.ymllinuxx64 or arm64ymlelectron-metadatavariant or metadata_json.filename
Raft Setup 1.2.3.exewin32x64exeinstallablemetadata_json.filename
Raft Setup 1.2.3.exe.blockmapwin32x64blockmapelectron-blockmapmetadata_json.filename

Keep the same draft-first policy as Android: CI creates a draft Electron release, then a human or agent reviews release notes and explicitly publishes. macOS update artifacts must be signed before upload; Hands hosts the signed files but does not sign Electron applications.

Review and Publish (draft flow)

CI creates drafts; publishing is an explicit step after changelog review:

# inspect the draft (status, rollout, changelog)
hands releases show raft-android <release-id>

# write the reviewed changelog; repeatable [lang=]file entries
hands releases update raft-android <release-id> \
  --changelog-file zh=changelog.zh.md \
  --changelog-file en=changelog.en.md

# make it live
hands releases publish raft-android <release-id>

Bilingual changelogs are stored per language; clients receive the language matching their locale (zh normalizes to zh-CN; plain single-value changelogs are served as-is).

hands releases share raft-android <release-id> --password <pw>   # password optional
hands releases shares raft-android <release-id>                   # list
hands releases update-share raft-android <release-id> <share-id> --ttl-seconds 1209600
hands releases revoke-share raft-android <release-id> <share-id>

--password can also come from HANDS_SHARE_PASSWORD (legacy QUIVER_SHARE_PASSWORD still works) to keep it out of shell history. Share URLs are printed once at creation; tokens are stored hashed.

Feedback Tickets

Agents can triage feedback and crash tickets entirely from the CLI (viewer role for read, publisher for changes):

hands feedback list raft-android --status open --kind crash
hands feedback show raft-android <ticket-id>
hands feedback update raft-android <ticket-id> --status in_progress --assignee me
hands feedback comment raft-android <ticket-id> "已复现,修复中"
hands feedback update raft-android <ticket-id> --status resolved

--assignee none unassigns. All subcommands accept --json for scripting.

CI Environment Variables

Every variable is read as HANDS_<name> first, then the legacy QUIVER_<name>, so existing CI keeps working unchanged.

VariableRequiredPurpose
HANDS_APINoHands business API base URL. Defaults to https://hands.build. HANDS_CLI_API takes precedence if set.
HANDS_BEARER_TOKENYesApp-scoped deploy token for CI.
HANDS_AUTH_TOKENNoAlias for HANDS_BEARER_TOKEN (tried first).

Legacy equivalents QUIVER_API, QUIVER_BEARER_TOKEN, QUIVER_AUTH_TOKEN are still accepted.

Versioning Guidance

For Android releases, keep APK versionCode and Hands version_code identical. Clients only update when the server release has a higher version code than the installed app.

One common scheme is:

versionCode = major * 1_000_000 + minor * 10_000 + patch * 100 + build
versionName = major.minor.patch[-suffix]

Example: 1.0.3-rc2 becomes versionName=1.0.3-rc2 and versionCode=1000302.

Security

Do not paste deploy tokens, package tokens, signing passwords, or keystore data into public chat, issue comments, logs, or release notes. Store them in the CI secret store and pass them to Hands through environment variables.