play-release
Use when publishing an Android app to Google Play (upload an AAB to internal, alpha, beta or production, staged rollout), when changing the Play listing text…
Skill by tirupati17
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/tirupati17-play-release
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: writing
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/play-release
What's inside
- Skill name
play-release- Description
- Use when publishing an Android app to Google Play (upload an AAB to internal, alpha, beta or production, staged rollout), when changing the Play listing text, screenshots, feature graphic or TV banner, when creating or repricing one-time in-app products or subscription base plans and offers, when checking what a Play track serves right now, or when Play rejects an upload, a versionCode, or an edit.
- License
- MIT
- Instructions
- 97 lines in SKILL.md · view on GitHub
Before installing
- Instructions read files in your home folder (~/.ssh, ~/.aws…)
Files (1)
- SKILL.md
SKILL.md (first lines)
# Google Play: publish, listings, images, in-app products
## Credentials
`cpm creds setup play` asks for each one and says where it lives:
| Name | Where to get it |
|---|---|
| `PLAY_PACKAGE` | The app's `applicationId`, exactly. Or pass `--package`. |
| `PLAY_SERVICE_ACCOUNT_JSON` | Path to a service account key. GCP console > IAM > Service accounts > create one > Keys > Add key > JSON. `cpm creds` copies it into `~/.config/cpm/<project>/keys/`. |
Before the key works:
1. Enable the **Google Play Android Developer API** on that GCP project (APIs & Services > Library).
2. Play Console > **Users and permissions** > Invite new user > the service account's email.
3. Grant **App permissions** for this app (release to production/testing, manage store listing, manage in-app products), not only Account permissions. With account-level rights alone it reads the catalogue happily and then fails on anything that touches a release.
4. The app must already exist in Play Console, and its very first AAB must be uploaded by hand; the API cannot create an app.
Environment variables of the same names win over the store (CI). Needs
`pip install google-api-python-client google-auth` in the Python that runs `cpm`;
`--help` and every local check work without it.
## Commands
Every command that changes Play is a **dry run unless `--commit`**. The dry run
does the real work (upload, set, ask Play to validate the edit) and then deletes
the edit, so what you rehearse is what will happen. After a commit each command
reads the live state back instead of trusting the response.
```bash
cpm play status # what every track serves (read only)
cpm play stage # copy the built AAB/APK to artifacts/<version>/ + release.json
cpm play publish production # rehearse
cpm play publish production --rollout 20 --commit # staged rollout
cpm play publish internal --commit
cpm play listing [--languages en-US] [--create] [--forbid-dashes] [--commit]
cpm play images --dir store-images [--commit] # phone screenshots + feature graphic
cpm play images --device tv --dir play-assets/tv --size 1920x1080 [--commit]
cpm play products --spec play-products.json [--commit]
cpm play prices --spec ladder.json [--markdown table.md] [--commit] # base plans, offers, one-time prices
```
Paths default to the git toplevel of the current directory (`--repo` to override):
version from `app/build.gradle(.kts)` (`--gradle`), bundle from
`artifacts/<version>/release.json` (`--artifacts`), notes from
`release-notes/<version>/<play-language>.txt` (`--notes`), listing copy from
`store-listing/<play-language>.json`. `cpm play <command> --help` has the rest,
including the image folder layouts and the product spec format.
## Order
1. Bump `versionCode` and `versionName` in the app's build file. Literal values only.
2. Write `release-notes/<version>/en-US.txt` (plus other languages). Users read these: no flag names, no internals, at most 500 characters each.
3. Build and verify the signed AAB (signer certificate, target SDK, whatever the store requires), then stage it: your build script writes `release.json`, or run `cpm play stage`.
4. `cpm play status`, then `cpm play publish <track>` (dry run). Read what it says it is replacing.
5. `cpm play publish <track> --commit`. For production prefer `--rollout 10`/`20` first, then publish again at 100 once vitals look clean.
## Traps
- **A bundle upload times out after sending its bytes.** Resume the same Google
upload request: the next chunk checks the server's accepted byte range, including
whether the final chunk succeeded. The publisher uses 4 MiB chunks, progress
output, a 180-second request timeout and bounded transport retries. Do not bump
the version merely because the response was lost; check the existing library's
hash before deciding that a number is occupied by different bytes.
- **`RedirectMissingLocation` during a chunked upload.** Google's 308 means
"Resume Incomplete", not an HTTP redirect. When supplying an authenticated
HTTP client, construct its transport with `googleapiclient.http.build_http()`;
it removes 308 from httplib2's redirect codes. A plain `httplib2.Http` misses
that configuration and fails on the first incomplete chunk.
- **An old bundle ships and every log looks right.** `artifacts/<version>/` survives a failed build. `publish` rehashes the AAB against `release.json` and checks it against the build file's version; never hand-edit `release.json` to get past that. Check timestamps too, and make sure the build script actually ran (a non-executable script exits 126 and leaves last run's files looking new; call it with `bash`).
- **A staged rollout quietly becomes 100%.** Only rollout 100 is `completed`; anything less is `inProgress` with `userFraction`. Play rejects `userFraction` on a completed release.
- **"versionCode already used".** A versionCode that was ever uploaded cannot be reused, even if never released. Bump it. `publish` reuses a bundle already in Play's library only when its sha256 matches `release.json`; the same number with different bytes is refused.
- **"You must let us know whether your app uses any Foreground Service permissions."** Play refuses every release edit until App content > Foreground service permissions covers each `FOREGROUND_SERVICE_*` type in the bundle, and that form only lists types it has seen in an uploaded bundle. First release with a new type: upload the bundle in an edit with no track change and commit it, fill the form (description plus a video per type), then publish; the library reuse above means no versionCode bump.
- **"This release no longer supports N devices."** A dependency's manifest merged in permissions that imply required hardware (camera, Bluetooth, media projection). Remove unused ones with `tools:node="remove"` and declare hardware `required="false"`; diff `aapt2 dump badging` of the previous and new APK whenever a native SDK arrives.
- **Build config keys missing from a local release build.** Values read from `local.properties`/env compile to empty strings and silently switch features off in production (push, analytics). Make the release build refuse to run without each one.
- **Release notes rejected mid-edit.** Over 500 characters per language, or an empty file. Checked locally before any network call.
- **Listing text.** Title 30, short 80, full 4000 characters. No ranking or price words ("best", "#1", "top", "free", "new", "sale") in the title, no emoji, no all-caps words. Play ranks title > short > full description and indexes the full one: search phrase in the title, repeat core terms naturally in the full description, never a keyword list. Changes take hours to show in search.
- **Screenshots.** At most 8 per device type (more is refused, not truncated; choose the story with `--shots`). 24-bit PNG without alpha. Feature graphic 1024x500, TV banner 1280x720. Only languages that already have a listing are touched; `--create-listings` adds one from a reviewed `metadata.json`. Adding the TV or Wear form factor and sending it for review are Console steps, not API calls.
- **In-app product ids are a contract** with whatever grants the purchase (the app, a billing backend like RevenueCat). Change them there first. Consumable vs non-consumable is not a Play setting for one-time products: the app or backend consumes the purchase.
- **Regional prices.** Give a USD base; Play converts it per region. Hand-set a region (`"IN": "24 INR"`) when conversion lands on an odd number; use a region group at a lower USD price for lower-income economies. A stale `regionsVersion` is only named in the error; `products` retries once with the version Play asks for.
- **Subscription prices (`prices`).** Per region: a hand-set price, else the App Store price when the currency matches (`match`: the JSON from `cpm appstore prices --out`), else Play's conversion of the USD price rounded to a local ending; a converted offer price is the region's new base price times the USD ratio, so the discount holds. Changing base plan prices never moves existing subscribers (no `migratePrices`). A base plan's billing period is fixed once active: `prices` compares it with the spec's `billingPeriod` and refuses to touch a mismatch. Play has no API for its per-currency price limits; `limitsUsd` converted per region is a sanity check only.
- **Abandoned edits.** Every uncommitted edit (dry run, rejected upload, Ctrl-C) is deleted in a `finally`. If a command dies harder than that, the edit expires on its own; it does not block the next one.
- **ARCore dependency type is missing during upload.** An XR dependency can add
`com.google.ar.core.min_apk_version` without a dependency declaration. If AR is
optional, add application metadata `com.google.ar.core=optional`, then rebuild
and check device filtering. Do not declare AR required for a phone panorama.
- **A read-only screenshot backup edit disappears during an upload.** Opening a
second Play edit with the same identity invalidates the first. Serialize all
Play edits, including backups, validation runs, images and publishing.
- **Image upload exits with 503 after the commit.** The write may already have
succeeded and only the post-commit read failed. Inspect the failing call before
retrying the upload. Open one read-only edit, compare screenshot order and each
returned `sha256` with the local files, then discard that edit.From tirupati17/cpm, MIT.
More from tirupati17/cpm
- 0.565score
google-ads
tirupati17
Use when checking or changing Google Ads from the terminal (campaign spend, clicks and conversions, pausing or enabling a campaign, changing a daily budget…
SkillAFreshness: AClaude Code +9 - 0.565score
amplitude
tirupati17
Use when listing or checking Amplitude Experiment feature flags, turning a flag on or off (kill switch, rollout), shipping code that is gated behind a new…
SkillAFreshness: AClaude Code +9 - 0.565score
appstore-release
tirupati17
Use when shipping an iOS app to App Store review (archive, upload, attach the build, write What's New, submit), updating localized descriptions, keywords or…
SkillAFreshness: AClaude Code +9 - 0.565score
gcp
tirupati17
Use when a tool needs a Google Cloud service account (Google Play publishing, RevenueCat purchase validation, Firebase, any Google API), when checking which…
SkillAFreshness: AClaude Code +9 - 0.565score
onesignal
tirupati17
Use when sending a push notification to a specific user, a list of users, a segment or everyone; when checking how many devices can receive push or whether…
SkillAFreshness: AClaude Code +9 - 0.565score
revenuecat
tirupati17
Use when checking subscription metrics (MRR, revenue, active subscriptions, trials), looking up why a specific user does or does not have Pro, granting…
SkillAFreshness: AClaude Code +9 - 0.560score
cpm-learn
tirupati17
Use right after finishing a task that took real effort against an external service or API (a store console, billing, ads, analytics, push, cloud) and that is…
SkillAFreshness: AClaude Code +9 - 0.560score
service-task
tirupati17
One sentence an agent can match against a request.
SkillAFreshness: AClaude Code +9
Signals
- Stars
- 0
- Forks
- 0
- Open issues
- 0
- Last commit (this folder)
- Oct 11, 2026 (18 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/tirupati17/cpm
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from tirupati17
- 0.565score
google-ads
tirupati17
Use when checking or changing Google Ads from the terminal (campaign spend, clicks and conversions, pausing or enabling a campaign, changing a daily budget…
SkillAFreshness: AClaude Code +9 - 0.565score
amplitude
tirupati17
Use when listing or checking Amplitude Experiment feature flags, turning a flag on or off (kill switch, rollout), shipping code that is gated behind a new…
SkillAFreshness: AClaude Code +9 - 0.565score
appstore-release
tirupati17
Use when shipping an iOS app to App Store review (archive, upload, attach the build, write What's New, submit), updating localized descriptions, keywords or…
SkillAFreshness: AClaude Code +9 - 0.565score
gcp
tirupati17
Use when a tool needs a Google Cloud service account (Google Play publishing, RevenueCat purchase validation, Firebase, any Google API), when checking which…
SkillAFreshness: AClaude Code +9 - 0.565score
onesignal
tirupati17
Use when sending a push notification to a specific user, a list of users, a segment or everyone; when checking how many devices can receive push or whether…
SkillAFreshness: AClaude Code +9 - 0.565score
revenuecat
tirupati17
Use when checking subscription metrics (MRR, revenue, active subscriptions, trials), looking up why a specific user does or does not have Pro, granting…
SkillAFreshness: AClaude Code +9
Cite this page
play-release skill. Agent Mart. https://agentmart.dev/skills/tirupati17-play-release (last updated 2026-10-24)