Ship it, roll it out, hear it break, fix it.
The release platform for client apps, full loop.
Hands runs the whole release loop: CI lands builds as drafts, agents review and publish with bilingual changelogs, staged rollouts meter exposure by device cohort, share pages handle ad-hoc distribution, and in-app feedback and crash reports come back as tickets — grouped by signature, symbolicated, and triageable by humans and AI agents through the same API. Reporting SDKs cover Android, iOS, HarmonyOS, and Electron — fully Cloudflare-native (Workers + Container + D1 + R2).
- Dashboard: https://app.hands.build
- Business origin: https://hands.build
- Docs: https://hands.build/docs · Admin guide · CLI reference
- API explorer: https://hands.build/api-docs
- CLI on npm:
@botiverse/hands-cli
The "Hands" metaphor: admins load build arrows into channels; clients pick the right one for their channel — and tell you where it landed.
- Channels — keep main, preview, nightly, or debug releases separated by app. Publish to
mainfor stable users,previewfor validation,nightlyfor fast internal iteration. - Update checks & staged rollouts — public latest/update responses with signed downloads, percentage rollouts bucketed by stable device id, and per-language changelogs; the Android SDK (
clients/android) handles in-app checks, installation, and feedback/crash submission; iOS, HarmonyOS (ArkTS), and Electron (@botiverse/hands-electron) SDKs mirror the feedback and crash lanes. - Share pages & version history — revocable, expiring, optionally password-protected download pages with QR codes and view/download stats, plus an opt-in public version history page per app.
- Feedback tickets — in-app feedback (attachments, device context) lands in a built-in ticket system with assignees, statuses, comments, and webhooks; submissions are authenticated with a per-app client key.
- Crash reporting — crash capture across Android, iOS, HarmonyOS, and Electron uploads on next launch, groups by signature, and symbolicates stacks server-side against uploaded R8/ProGuard mappings, native symbols, dSYMs, or Breakpad symbols (minidumps).
- Draft-first releases — CI creates draft releases with generated changelogs; an agent reviews, writes bilingual notes, and publishes explicitly (
docs/release-runbook.md). - Raft access — Login with Raft, org roles, direct app members, per-server visibility grants, and app-level deploy tokens for CI and agents.
- CI-friendly publishing — the public npm CLI publishes Android releases and creates share links from GitHub Actions, local packaging lanes, or Raft agents:
$ npm exec --package @botiverse/hands-cli -- hands builds publish-android raft-android
uploading APK and metadata...
creating release on channel main...
release: 14998dba-cfde-4002-8c01-230a2760f662
share: https://hands.build/share/...┌────────────────────────────────────────────────────────┐
│ User / Admin / CI │
└──────┬──────────────────────────┬─────────────────────┘
│ upload / list / download │
▼ │
┌────────────────────────────────────────────────────────┐
│ Cloudflare Worker (Hands) │
│ - API routes │
│ - Admin SPA static assets │
│ - Auth (Login with Raft + signed Hands JWT) │
│ - Signed URL issuance for R2 │
│ - D1 read/write for metadata │
└──────┬───────────────────────────┬────────────────────┘
│ multipart upload │ parse APK
▼ ▼
┌─────────────────┐ ┌────────────────────────────────┐
│ R2 Bucket │ │ Cloudflare Container │
│ (raw APK + icon)│ ◀────── │ (apk-parser) │
└─────────────────┘ icon │ - aapt/apksigner │
▲ │ - returns metadata + icon │
│ └────────────────────────────────┘
│ │
│ ▼
│ ┌─────────────────┐
└─────────────────────│ D1 Database │
│ apps/versions/ │
│ channels/audit/ │
│ raft sessions │
└─────────────────┘
worker/— Cloudflare Worker (Hono) — admin SPA, API routes, Login with Raft, D1 CRUD, R2 signed URLsadmin/— admin SPA and public landing (React + Vite + Tailwind) served by the Workercontainer/— Cloudflare Container — APK metadata parser (aapt + apksigner)packages/cli/—@botiverse/hands-clinpm packageclients/android/— Hands Android Updater SDK (update checks + APK install)docs/— admin user guide, CLI reference, public API reference, architecture notesmigrations/— D1 SQL schema migrations
Admin access uses Login with Raft as the only production login path.
Register the app in Raft with callback URL:
https://hands.build/login/raft/callback
Worker configuration:
RAFT_CLIENT_IDinworker/wrangler.jsoncRAFT_CLIENT_SECRETas a Worker secret (wrangler secret put RAFT_CLIENT_SECRET)app.hands.buildis the canonical dashboard/login origin.hands.buildremains the business/API origin for SDKs, CLI/agents, share/download pages, release notes, and docs.- Product links and docs use
app.hands.buildfor the dashboard andhands.buildfor business surfaces. Both hostnames continue serving compatible Worker routes during the transition; there is no forced cross-domain redirect. - Login starts from
app.hands.build, uses the registeredhttps://hands.build/login/raft/callback, then returns a signed Hands JWT to the dashboard in the URL fragment. The SPA stores the JWT locally and sendsAuthorization: Bearer; no browser session cookie is used. - Optional
RAFT_ALLOWED_SERVER_IDS/RAFT_ALLOWED_SERVER_SLUGScan restrict admin login to specific Raft servers
Do not put Raft client secrets in browser JavaScript, repository files, logs, or public channels.
# install
pnpm install
# local worker dev (D1 + R2 local emulators)
pnpm --filter @botiverse/hands-worker dev
# local admin UI
pnpm --filter @botiverse/hands-admin dev
# local container (Docker required)
docker build -t apk-parser container/See CONTRIBUTING.md for the worktree workflow and merge rules.
GitHub Actions owns production publishing so local machines do not need long-lived npm or Cloudflare credentials.
Publishing and deploys read their npm, Cloudflare, and Hands Worker secrets from the repository's GitHub Actions secrets — configured in the repository settings, not documented here.
Workflows:
Publish Hands Node SDKpublishes@botiverse/hands-nodeto npm with the repository npm token. Trigger it manually with the package version frompackages/node/package.json, or push a tag likenode-v0.1.0.Publish CLIpublishes@botiverse/hands-cliwith the repository npm token. Publish the package's declared@botiverse/hands-nodeversion first; the workflow verifies it exists, packs with pnpm so the workspace range becomes a normal npm semver range, and then publishes the tarball. Trigger it manually with the package version frompackages/cli/package.json, or push a tag likecli-v0.5.1.Deploy Quiver Serveris the legacy compatibility deployment forquiver.oranix.io; it no longer owns the Hands dashboard or business data plane.Deploy Hands Serverdeploys one Worker/admin bundle to the custom domainshands.build(business/API) andapp.hands.build(dashboard/login) in the separate Hands Cloudflare account. It bootstrapshands-dbandhands-artifactsif they do not exist, applies D1 migrations, and deploys withworker/wrangler.hands.jsonc. This workflow is manual-only so it cannot replace the existing Quiver deploy path accidentally.
Reference feature set inspired by Zealot (tryzealot/zealot) and Sentry — whose DSN model informs the client-key (app-identifier) and crash-reporting design.