Happy release
git clone https://github.com/slopus/happy
T=$(mktemp -d) && git clone --depth=1 https://github.com/slopus/happy "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.agents/skills/release" ~/.claude/skills/slopus-happy-release && rm -rf "$T"
.agents/skills/release/SKILL.mdRelease
You are the release operator for the Happy monorepo. When invoked, walk the user through releasing the component they choose.
Step 1: Pick a target
Ask which component to release:
- CLI — npm package
happy - Mobile — Expo/EAS builds for iOS + Android
- Web — Docker image + K8s deploy via TeamCity
- Server — Docker image + K8s deploy via TeamCity
- Docs — GitHub Pages (separate repo)
Present these as options. Wait for the user to pick.
CLI Release
Package: packages/happy-cli npm name: happy Registry: https://registry.npmjs.org Git tags: cli-{version}
Tag namespace note:
- CLI releases use
cli-X.Y.Z - Native releases use
native-<runtime-version> - OTA releases use
ota-<ota-version> - Do not use a bare
tag for Happy releases because multiple release streams coexist in this repovX.Y.Z
Step 2: Gather state
Run these in parallel:
— see current latest + betanpm view happy dist-tags
— local versioncat packages/happy-cli/package.json | grep version
— check for dirty stategit status --short
— confirm branchgit branch --show-current
— recent commits for release notes contextgit log --oneline -10
Present a summary:
Local version: X.Y.Z npm latest: X.Y.Z npm beta: X.Y.Z-N Branch: main Working tree: clean / dirty
Step 3: Pick channel and version
Ask the user:
- Channel:
orlatestbeta - Bump type: For latest:
,patch
,minor
. For beta:major
(appendsprerelease
), or explicit version.-N
Suggest a sensible default based on the current state. For beta, the next prerelease of the current version. For latest, a patch bump.
Present as options. Wait for confirmation.
Step 4: Version bump
Edit
packages/happy-cli/package.json directly — do NOT use npm version (it chokes on pnpm workspace protocol).
IMPORTANT: do this before build/test for the CLI. The build imports
package.json and bakes the version into the generated bundle. If you build first and bump later, happy --version can still report the old prerelease version even though npm metadata shows the new one.
Step 5: Build
cd packages/happy-cli pnpm --filter happy run build
Report success/failure. Stop on failure.
Step 6: Test (unit only)
cd packages/happy-cli pnpm --filter happy exec vitest run --project unit
Integration tests are slow and flaky — skip them for releases. Unit tests are the gate. Expect the unit suite to take around a minute;
src/utils/serverConnectionErrors.test.ts is particularly slow, so don't mistake a long run for a hang.
Report results. If failures, ask the user whether to proceed or abort.
Step 7: Publish
cd packages/happy-cli pnpm publish --tag {channel} --no-git-checks --ignore-scripts
: allows dirty working tree (we already verified state)--no-git-checks
: skips--ignore-scripts
(we already built and tested)prepublishOnly
Step 8: Verify
npm view happy dist-tags
Confirm the new version appears under the correct tag. If
latest doesn't move immediately, wait 10-15 seconds and check again; npm tag propagation is not always instant.
Step 9: Git tag + commit (latest only)
For
latest releases only:
- Commit the version bump:
Release version X.Y.Z - Tag:
git tag cli-X.Y.Z - Push:
git push && git push --tags
For
beta releases: ask the user if they want to commit the version bump or leave it uncommitted.
If
git push is rejected because origin/main advanced while releasing, fetch and rebase the release commit before retrying:
git fetch origin main git rebase --autostash origin/main git tag -f cli-X.Y.Z git push && git push --tags
Use
--autostash when the worktree is dirty from unrelated local changes so those edits are preserved. Recreate the tag after rebase because the release commit hash changes.
Step 10: GitHub Release (latest only)
For
latest releases, create a GitHub release:
gh release create cli-X.Y.Z --generate-notes --title "cli-X.Y.Z"
Step 11: Install + verify locally
npm i -g happy@{channel} happy --version happy daemon status
Report the installed version and daemon status. The smoke check must confirm that
happy --version matches the published version, not just npm metadata. If it reports the old version, rebuild after the version bump and cut a corrective patch release.
Mobile Release
Package: packages/happy-app Variants: development, preview, production Platform: Expo SDK 54 / React Native 0.81.4
Build types
Ask the user what kind of release. OTA is the most common — suggest it first:
-
OTA update (preview) — push JS update to preview channel (most common)
pnpm --filter happy-app run ota -
OTA update (production) — push JS update to production channel
pnpm --filter happy-app run ota:production
Native builds are rare — only needed when native code changes:
-
Dev builds — development + preview variants (internal distribution)
pnpm --filter happy-app run release:build:developer -
App Store — production builds with auto-submit
pnpm --filter happy-app run release:build:appstore
EAS Build Profiles
Profile Distribution Channel development internal development development-store store development preview internal preview preview-store store preview production store production
Version source is remote (EAS manages build numbers, auto-incremented). Runtime version "20" — bump when native code changes to invalidate OTA.
App Store Connect
Apple ID: steve@bulkovo.com ASC App ID: 126165711 Team ID: 466DQWDR8C
Web Release
Package: packages/happy-app (same Expo app, web export) Dockerfile: Dockerfile.webapp Image: docker.korshakov.com/happy-app:{version} K8s: packages/happy-app/deploy/happy-app.yaml (3 replicas)
Web releases go through TeamCity (
Lab_HappyWeb). The config is in the TeamCity UI, not in the repo.
Flow:
expo export --platform web -> nginx:alpine static serve -> Docker build -> push -> K8s deploy.
Build args:
POSTHOG_API_KEY, REVENUE_CAT_STRIPE.
Guide the user to trigger the TeamCity build, or help with manual Docker builds if needed.
Server Release
Package: packages/happy-server Dockerfile: Dockerfile.server (production), Dockerfile (standalone w/ PGlite) Image: docker.korshakov.com/handy-server:{version} K8s: packages/happy-server/deploy/handy.yaml (1 replica, port 3005)
Server releases go through TeamCity (
Lab_HappyServer). The config is in the TeamCity UI, not in the repo.
Build: node:20 + python3 + ffmpeg, builds happy-wire + happy-server. Secrets from Vault: handy-db, handy-master, handy-github, handy-files, handy-e2b, handy-revenuecat, handy-elevenlabs. Redis: happy-redis StatefulSet (redis:7-alpine, 1Gi persistent volume).
Guide the user to trigger the TeamCity build.
Docs Release
Site: happy.engineering (GitHub Pages) Repo: github.com/slopus/slopus.github.io
Separate repo, not part of this monorepo. Guide the user to push to that repo.
Rules
- Always present options — never assume which component, channel, or version.
- Always verify before publishing — show the user what will be published and get confirmation.
- Unit tests are the gate, not integration tests — integration tests are slow and have flaky abort/interrupt tests.
- Use pnpm publish, not npm publish — avoids workspace protocol issues.
- Use --ignore-scripts — we build and test explicitly, no need for prepublishOnly to redo it.
- Never force-push tags — if a tag exists, stop and ask.