iOS Distribute - NowStack Mobile
SkillDev toolsPrepare and submit NowStack Mobile iOS App Store releases. Use for real 6.5-inch app screenshots, metadata, IAP, review details, validation, and submit.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the iOS Distribute - NowStack Mobile skill
What this skill tells your AI
The instructions your AI receives, as published by melvynx/saveit.now in .agents/skills/ns-ios-distribute/SKILL.md and read by ahel’s review.
Take a TestFlight-ready app all the way to App Store review submission: store screenshots, metadata, IAP pricing/availability, age rating, review details with demo account, validation, and submit. If no build is on TestFlight yet, run ns-ios-testflight first.
Everything runs with repo tools and public CLIs only:
mobile-app/scripts/asc-api.mjsfor all App Store Connect API calls (ASC_KEY_ID/ASC_ISSUER_ID/ASC_P8_PATHenv vars).ascCLI for uploads (screenshots, subscription assets) and validation.mobile-app/scripts/store-screenshots.mjs+ Maestro +xcrun simctlfor captures.
Sequencing with the rest of the lifecycle:
- No processed build yet → run the
ns-ios-testflightskill first. - Screenshot capture details →
.agents/skills/ns-generate-store-screenshots/SKILL.md. - This file — version, metadata, IAP, compliance, review, submit.
For deep platform specifics use
.agents/skills/ns-ios-deploy-app/SKILL.md/.agents/skills/ns-deploy-android-app/SKILL.md.
<state_variables>
| Variable | Source |
|---|---|
{asc_app_id} | GET /v1/apps?filter[bundleId]={bundle_id} |
{version_id} | the appStoreVersions id in PREPARE_FOR_SUBMISSION |
{version_loc_id} | en-US appStoreVersionLocalizations id |
{app_info_id} / {app_info_loc_id} | from GET /v1/apps/{asc_app_id}/appInfos (+ its localizations) |
{build_db_id} | processed build id from GET /v1/builds?filter[app]={asc_app_id} |
{subscription_id} / {sub_group_id} | if the app sells a subscription |
{screenshots_dir} | dated output of the screenshot run, e.g. documents/store-screenshots/<date>/ios/iphone-65/en-US |
| </state_variables> |
<critical_safety>
- Every mutation against App Store Connect is externally visible. Confirm with the user before: uploading screenshots, changing pricing/availability, and ABOVE ALL the final review submission.
- Never print or commit ASC credentials.
documents/store-screenshots/is gitignored — keep it that way. - Screenshots must contain only demo data (the seeded demo account), never real user content.
- Screenshots must be raw captures of the real app UI. Do not create screenshot-only routes, fake marketing screens, fake text overlays, fake cards, fake product claims, or composite promo images unless the user explicitly asks for framed marketing assets.
- If a previous agent created
/store/*,store/[shot], or any other fake screenshot route, stop and remove/ignore it before capturing or uploading App Store screenshots. - Metadata values (URLs, support email, price) come from
site-config.ts— do not invent them. </critical_safety>
<asc_entity_map> App Store Connect data model — knowing which entity owns which field saves hours:
| Field | Entity | Endpoint |
|---|---|---|
| description, keywords, promotionalText, supportUrl, marketingUrl | appStoreVersionLocalizations | PATCH /v1/appStoreVersionLocalizations/{id} |
| subtitle, privacyPolicyUrl | appInfoLocalizations | PATCH /v1/appInfoLocalizations/{id} |
| primaryCategory | appInfos | PATCH /v1/appInfos/{id} (relationships) |
| copyright, release type | appStoreVersions | PATCH /v1/appStoreVersions/{id} |
| attached build | appStoreVersions relationship | PATCH /v1/appStoreVersions/{id}/relationships/build |
| age rating answers | ageRatingDeclarations | PATCH /v1/ageRatingDeclarations/{id} |
| contact + demo account | appStoreReviewDetails | POST /v1/appStoreReviewDetails |
| content rights | apps | asc apps update --id {asc_app_id} --content-rights ... |
| </asc_entity_map> |
- No processed build → run the
ns-ios-testflightskill first. - Resolve
{version_id}:GET /v1/apps/{asc_app_id}/appStoreVersions?filter[appStoreState]=PREPARE_FOR_SUBMISSION. If none exists, create it:POST /v1/appStoreVersionswith{platform: "IOS", versionString: "<version>"}+ app relationship. - Read
site-config.ts: urls (website/privacy/terms), support email, payment product IDs, price. - The web app must be LIVE (privacy + terms URLs are review gates) — if not, stop and point to
docs/production-checklist.mdstage 2.
- Real app UI only: capture the application screens as a user would see them: onboarding, auth, signed-in home/dashboard, real feature screen, real paywall, and real settings/account/privacy screen. Demo data is allowed; fake screenshot-only pages, fake cards, marketing layouts, and invented overlay copy are not allowed.
- Demo data/session: create/verify a seed function (e.g.
convex/seedStoreDemo.ts, app-specific) so real screens show clean demo content. Sign in with the built-in review accountappstoretest@email.com/ OTP123456for auth-gated screens. Never use real personal data. - Paywall capture: temporarily set
EXPO_PUBLIC_ALLOW_DEV_PAYMENT_BYPASS=falseso the real paywall renders (restore afterwards). The paywall screenshot must be the actual in-app paywall, not a recreated promo screen. - Device: always use the App Store "iPhone 6.5 Display" slot: iPhone 11 Pro Max / XS Max class simulator → native 1242x2688 = Apple display type
IPHONE_65. Do not use iPhone 14 Plus / iPhone 15 Pro Max / 6.7-inch screenshots unless the user explicitly asks for another slot.
xcrun simctl create "store-iphone-65" "com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro-Max" <runtime>
cd mobile-app && npm run screenshots:store:ios # dry-run first, always
npm run screenshots:store:ios -- --execute # after user confirms
- Maestro flows live in
mobile-app/maestro/store/(Maestro needs JDK 17:export JAVA_HOME=/opt/homebrew/opt/openjdk@17). Robust flow patterns:extendedWaitUntil: {visible: "<text>", timeout: 10000}before every tap; conditional paywall dismissal withrunFlow: when: visible:. - Validate:
sips -g pixelWidth -g pixelHeight *.png— every file must be exactly 1242x2688. Then visual QA each PNG (read the images): real app screen, no fake route, no dev overlays, no status-bar clutter, no placeholder copy, no invented claims.
Write metadata (draft it from docs/templates/app-store-metadata.example.json + site-config.ts, show the user for approval first):
PATCH /v1/appStoreVersionLocalizations/{version_loc_id}— description (appendTerms of Use: <terms-url>andPrivacy Policy: <privacy-url>at the end — Apple requires visible terms for subscription apps), keywords (100 chars max, comma-separated, no spaces after commas), promotionalText, supportUrl, marketingUrl.PATCH /v1/appInfoLocalizations/{app_info_loc_id}— subtitle (30 chars max), privacyPolicyUrl.PATCH /v1/appInfos/{app_info_id}— primaryCategory relationship (e.g.{"type":"appCategories","id":"LIFESTYLE"}).PATCH /v1/appStoreVersions/{version_id}— copyright (<year> <company name>).
Upload screenshots:
asc screenshots upload --version-localization "{version_loc_id}" \
--path "{screenshots_dir}" --device-type "IPHONE_65"
# names + descriptions testers/reviewers see
asc subscriptions groups localizations create --group {sub_group_id} --locale en-US --name "<group display name>"
asc subscriptions localizations create --subscription-id {subscription_id} --app {asc_app_id} \
--locale en-US --name "<plan name>" --description "<one-line benefit>"
# pricing: set one base price, propagate everywhere
asc subscriptions pricing equalize --subscription-id {subscription_id} \
--base-price "<price>" --base-territory "<ISO3>" --confirm
# availability: if "app availability not found", initialize via the v2 API with ALL territory ids
node mobile-app/scripts/asc-api.mjs GET "/v1/territories?limit=200" # collect ids
# POST /v2/appAvailabilities with app relationship + territoryAvailabilities for every territory
# review screenshot for the subscription/IAP (use the real in-app paywall capture)
asc subscriptions review screenshots create --subscription-id {subscription_id} \
--file "{screenshots_dir}/<paywall>.png"
Target state: subscription shows READY_TO_SUBMIT. If a screenshot asset gets stuck in processing, delete it and re-upload.
age rating: fetch, then PATCH honest answers (everything NONE/false except what the app has)
node mobile-app/scripts/asc-api.mjs GET "/v1/appInfos/{app_info_id}/ageRatingDeclaration" node mobile-app/scripts/asc-api.mjs PATCH /v1/ageRatingDeclarations/ /tmp/age-req.json
- **Export compliance**: add `"ITSAppUsesNonExemptEncryption": false` to `infoPlist` in `mobile-app/app.config.ts` (standard HTTPS-only apps) so every build auto-answers it.
- **App Privacy questionnaire is WEB-ONLY** — the API cannot complete it. Give the user the direct link `https://appstoreconnect.apple.com/apps/{asc_app_id}/distribution/privacy` plus the exact declarations to click, derived from what the app actually collects (this boilerplate baseline: email + user content, linked to identity, no tracking). This is the one manual gate before submission.
</phase>
<phase n="F" title="Review details, validate, submit">
```bash
# review details — the demo account is built into the boilerplate auth
node mobile-app/scripts/asc-api.mjs POST /v1/appStoreReviewDetails /tmp/review-req.json
review-req.json: contact name/email/phone from the user, demoAccountName: "appstoretest@email.com", demoAccountPassword: "123456", demoAccountRequired: true, and notes explaining the OTP flow: "Sign in with the email OTP flow: enter appstoretest@email.com, the one-time code is fixed to 123456 for this account." Describe the core flow in 2-3 sentences.
asc validate --app {asc_app_id} --version "<version>" --platform IOS --output json
Fix every severity: error (the phases above cover all standard ones). When clean AND the user confirms App Privacy is published:
asc review submit --app {asc_app_id} --version "<version>" --dry-run --output table
asc review submit --app {asc_app_id} --version "<version>" --confirm # only after explicit user confirmation
Report: version, build number, what was submitted, expected review timeline, and how to monitor (asc status --app {asc_app_id}).
<failure_modes>
asc validateerror: missing screenshots → wrong display type or wrong dimensions. App Store "iPhone 6.5 Display" =IPHONE_65; screenshots must be exactly 1242x2688.- Screenshots show a fake
/storeroute, marketing card, or invented overlay copy → discard the set. Capture the real app screens only, with demo data if needed. pricing availability edit: app availability not found→ availability was never initialized; usePOST /v2/appAvailabilities(v2!) with all territories, then retry.- Subscription review screenshot stuck in AWAITING_UPLOAD/processing → delete the asset and re-upload the PNG.
- Paywall screenshot shows "Continue (Dev)" →
EXPO_PUBLIC_ALLOW_DEV_PAYMENT_BYPASSwas true during capture; set it false, relaunch, recapture. - Maestro types stray characters into inputs → erase and retype (
eraseText: 50theninputText), keep one input per flow step. - Wrong app loads in simulator → another Metro on port 8081; kill it or pin ports (see
docs/troubleshooting.md). - Review rejection: terms not visible → subscription apps must link Terms of Use in the description or EULA field; Phase C appends it.
- Submission blocked on App Privacy → web-only questionnaire not published yet; it must show "published" before submit. </failure_modes>
<success_metrics>
- All screenshots are 1242x2688 raw captures of the real app UI, demo data only, uploaded to the right localization +
IPHONE_65display type. - Metadata, category, copyright, age rating, content rights all set via API (idempotent — re-running is safe).
- Subscription (if any)
READY_TO_SUBMITwith localized name, price, availability, and review screenshot. asc validatereturns zero errors.- Review submitted only after explicit user confirmation, with the demo account in review notes. </success_metrics>
Signals
- GitHub stars
- 31
- Forks
- 5
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
ns-ios-distribute- Source
- github.com/melvynx/saveit.now