From a762c5b07fa97fb55516239979b3850c1e1dca75 Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Sat, 4 Apr 2026 23:36:50 -0700 Subject: [PATCH] docs: add Apple App Store + Google Play Store release guides - APPLE/README.md: API key details, App Store Connect setup, TestFlight workflow, submission checklist, provisioning, screenshots/metadata requirements - GOOGLE/README.md: Play Console setup, service account config, signing keys, AAB upload, testing tracks, data safety, content rating, submission checklist --- docs/_MobileApps/APPLE/README.md | 283 +++++++++++++++++++++++++ docs/_MobileApps/GOOGLE/README.md | 331 ++++++++++++++++++++++++++++++ 2 files changed, 614 insertions(+) create mode 100644 docs/_MobileApps/APPLE/README.md create mode 100644 docs/_MobileApps/GOOGLE/README.md diff --git a/docs/_MobileApps/APPLE/README.md b/docs/_MobileApps/APPLE/README.md new file mode 100644 index 00000000..04d57e0a --- /dev/null +++ b/docs/_MobileApps/APPLE/README.md @@ -0,0 +1,283 @@ +# Apple Developer — App Store & TestFlight Guide + +## API Key + +| Field | Value | +| --------- | -------------------------------------- | +| Key ID | `PPATU9GL73` | +| Issuer ID | `1dbc2980-1621-4fb9-940b-e28257e6322c` | +| Role | App Manager | +| File | `AuthKey_PPATU9GL73.p8` | + +> **⚠️ The `.p8` file is NOT checked into git** (blocked by secret scanner). It lives at `~/.appstoreconnect/private_keys/AuthKey_PPATU9GL73.p8` on each machine. Copy it manually between machines or re-download from App Store Connect (one-time download only — keep a backup). + +### Setup on any machine + +```bash +mkdir -p ~/.appstoreconnect/private_keys +# Copy the .p8 from another machine or your backup +cp /path/to/AuthKey_PPATU9GL73.p8 ~/.appstoreconnect/private_keys/ +chmod 600 ~/.appstoreconnect/private_keys/AuthKey_PPATU9GL73.p8 +``` + +--- + +## Apple Developer Account + +| Field | Value | +| ----------------- | ------------------------------------------------------------------ | +| Team ID | `748N7QPX7J` | +| Apple ID | `saravanakumardb1@gmail.com` | +| Portal | [developer.apple.com/account](https://developer.apple.com/account) | +| App Store Connect | [appstoreconnect.apple.com](https://appstoreconnect.apple.com) | + +--- + +## Registered Apps + +| Product | Bundle ID | App Store Connect | SKU | Status | +| ------------------ | --------------------------------- | ----------------- | ---------- | ------------------------------------------ | +| LysnrAI | `com.bytelyst.LysnrAI` | ✅ Created | lysnrai | In TestFlight (build 59) | +| ChronoMind | `com.saravana.chronomind` | ❌ Needs creation | chronomind | Bundle ID registered, pending app creation | +| ChronoMind Widgets | `com.saravana.chronomind.widgets` | N/A (extension) | — | Bundle ID registered | + +--- + +## App Store Connect — Creating a New App + +1. Go to [App Store Connect → My Apps](https://appstoreconnect.apple.com/apps) +2. Click **"+"** → **"New App"** +3. Fill in: + - **Platforms:** iOS + - **Name:** Product display name (e.g., `ChronoMind`) + - **Primary Language:** English (U.S.) + - **Bundle ID:** Select from dropdown (must be registered first — see below) + - **SKU:** Lowercase product ID (e.g., `chronomind`) + - **User Access:** Full Access +4. Click **Create** + +--- + +## Registering a New Bundle ID + +### Via API (preferred) + +```python +import jwt, time, json, urllib.request + +with open('AuthKey_PPATU9GL73.p8', 'r') as f: + key = f.read() + +token = jwt.encode( + {'iss': '1dbc2980-1621-4fb9-940b-e28257e6322c', 'iat': int(time.time()), + 'exp': int(time.time()) + 1200, 'aud': 'appstoreconnect-v1'}, + key, algorithm='ES256', headers={'kid': 'PPATU9GL73', 'typ': 'JWT'} +) + +data = json.dumps({'data': {'type': 'bundleIds', 'attributes': { + 'identifier': 'com.saravana.PRODUCT', + 'name': 'ProductName', + 'platform': 'IOS' +}}}).encode() + +req = urllib.request.Request( + 'https://api.appstoreconnect.apple.com/v1/bundleIds', + data=data, + headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'}, + method='POST' +) +resp = urllib.request.urlopen(req) +print(json.loads(resp.read())['data']['id']) +``` + +### Via Portal + +1. Go to [Certificates, Identifiers & Profiles → Identifiers](https://developer.apple.com/account/resources/identifiers/list) +2. Click **"+"** → **App IDs** → **App** +3. Enter description + bundle ID +4. Enable capabilities (App Groups, Push Notifications, etc.) +5. Click **Register** + +--- + +## TestFlight — Uploading a Build + +### Automated (recommended) + +Each product repo has a `release-testflight.sh` script: + +```bash +# LysnrAI +cd learning_voice_ai_agent/mobile_app/ios +# (uses manual pbxproj — see .windsurf/workflows/release-testflight.md) + +# ChronoMind +cd learning_ai_clock/ios +bash release-testflight.sh +``` + +### Manual CLI + +```bash +# 1. Archive +xcodebuild archive \ + -project MyApp.xcodeproj \ + -scheme MyApp \ + -configuration Release \ + -archivePath /tmp/MyApp.xcarchive \ + -destination 'generic/platform=iOS' + +# 2. Export + Upload +xcodebuild -exportArchive \ + -archivePath /tmp/MyApp.xcarchive \ + -exportPath /tmp/MyApp_export \ + -exportOptionsPlist ExportOptions.plist \ + -allowProvisioningUpdates \ + -authenticationKeyPath ~/.appstoreconnect/private_keys/AuthKey_PPATU9GL73.p8 \ + -authenticationKeyID PPATU9GL73 \ + -authenticationKeyIssuerID 1dbc2980-1621-4fb9-940b-e28257e6322c +``` + +### ExportOptions.plist (template) + +```xml + + + + + method + app-store-connect + teamID + 748N7QPX7J + destination + upload + signingStyle + automatic + uploadSymbols + + + +``` + +--- + +## App Store Submission Checklist + +Before submitting for App Store review, ensure: + +### Required Assets + +| Asset | Spec | Notes | +| ------------------ | ----------------------- | ---------------------------------------- | +| App Icon | 1024×1024 PNG | No alpha channel, no rounded corners | +| Screenshots (6.7") | 1290×2796 | iPhone 15 Pro Max — required | +| Screenshots (6.5") | 1284×2778 | iPhone 14 Plus — required | +| Screenshots (5.5") | 1242×2208 | iPhone 8 Plus — optional but recommended | +| iPad Screenshots | 2048×2732 | Required if app supports iPad | +| App Preview Video | 1920×1080 or device res | Optional, up to 30 seconds | + +### Required Metadata + +| Field | Notes | +| ------------------ | ------------------------------------------------ | +| App Name | Max 30 characters | +| Subtitle | Max 30 characters | +| Description | Required, no max (but first 3 lines matter most) | +| Keywords | Max 100 characters, comma-separated | +| Support URL | Must be a valid, reachable URL | +| Privacy Policy URL | **Required** — must be a valid, reachable URL | +| Category | Primary + optional secondary | +| Age Rating | Answer the questionnaire honestly | +| Copyright | e.g., `© 2026 ByteLyst` | + +### Required for Review + +| Requirement | Details | +| ---------------------- | --------------------------------------------------------------- | +| Privacy Policy URL | Must be publicly accessible | +| App Review Information | Contact info (name, phone, email) for reviewer | +| Demo Account | If app requires login, provide test credentials | +| Notes for Review | Explain any non-obvious features or permissions | +| Content Rights | Confirm you have rights to all content | +| IDFA Declaration | If using AdSupport framework | +| Export Compliance | Encryption usage (most apps: standard encryption = YES, exempt) | + +### Privacy & Permissions + +For each permission your app uses, you must: + +1. Add a usage description string in Info.plist (e.g., `NSMicrophoneUsageDescription`) +2. Declare it in App Store Connect → App Privacy + +Common permissions for ByteLyst apps: + +| Permission | Info.plist Key | Apps | +| ------------------ | ------------------------------------- | ----------------- | +| Microphone | `NSMicrophoneUsageDescription` | LysnrAI | +| Speech Recognition | `NSSpeechRecognitionUsageDescription` | LysnrAI | +| Location | `NSLocationWhenInUseUsageDescription` | PeakPulse | +| Health | `NSHealthShareUsageDescription` | PeakPulse, NomGap | +| Notifications | `NSUserNotificationsUsageDescription` | All | +| Camera | `NSCameraUsageDescription` | MindLyst | + +### App Privacy (Data Collection) + +In App Store Connect → App Privacy, declare: + +- What data types your app collects +- Whether data is linked to user identity +- Whether data is used for tracking +- Purpose of each data type + +--- + +## TestFlight Beta Testing + +### Internal Testing (up to 100 testers) + +1. Upload build (see above) +2. Wait ~15-30 min for processing +3. Go to App Store Connect → TestFlight → Internal Testing +4. Add testers by Apple ID email +5. Testers install via TestFlight app + +### External Testing (up to 10,000 testers) + +1. Create a beta group in TestFlight +2. Add a build to the group +3. Submit for **Beta App Review** (required for external testers) +4. Review takes ~24-48 hours +5. Once approved, distribute via link or email + +### Beta App Review Requirements + +- Same as App Store review but slightly more lenient +- Still needs: privacy policy URL, app description, contact info +- Crashes or obvious bugs will be rejected + +--- + +## Certificates & Signing + +| Type | Purpose | Managed By | +| ------------------ | ----------------------------- | ------------------------------ | +| Apple Development | Debug builds, simulator | Xcode (automatic) | +| Apple Distribution | App Store / TestFlight builds | Xcode (automatic with API key) | + +With `-allowProvisioningUpdates` and the API key, Xcode automatically manages certificates and provisioning profiles. No manual certificate management needed. + +--- + +## Troubleshooting + +| Error | Fix | +| ------------------------------------------- | -------------------------------------------------------- | +| `No signing certificate "iOS Distribution"` | Use `-allowProvisioningUpdates` with API key | +| `Authentication failed` | Check .p8 key path, Key ID, Issuer ID | +| `Error Downloading App Information` | Create app record in App Store Connect first | +| `App Groups capability` | Register App Group in developer portal | +| `FORBIDDEN_ERROR` on app creation | API key needs Admin role (App Manager can't create apps) | +| `Bundle ID not available` | Someone else registered it — use a different one | +| `Missing compliance` | Set Export Compliance in App Store Connect | +| `Build processing stuck` | Wait up to 1 hour; check email for processing errors | diff --git a/docs/_MobileApps/GOOGLE/README.md b/docs/_MobileApps/GOOGLE/README.md new file mode 100644 index 00000000..17267e4f --- /dev/null +++ b/docs/_MobileApps/GOOGLE/README.md @@ -0,0 +1,331 @@ +# Google Play Store — Android App Distribution Guide + +## Google Play Console + +| Field | Value | +| ----------------- | ---------------------------------------------------------- | +| Console URL | [play.google.com/console](https://play.google.com/console) | +| Developer Account | ByteLyst / saravanakumardb1@gmail.com | + +--- + +## Registered Apps + +| Product | Package Name | Play Console | Status | +| ---------- | ---------------------- | ----------------- | -------------------------------- | +| LysnrAI | `com.bytelyst.lysnrai` | ❌ Needs creation | Gradle builds, pending setup | +| ChronoMind | `com.chronomind.app` | ❌ Needs creation | Gradle builds, pending setup | +| MindLyst | `com.mindlyst.android` | ❌ Needs creation | KMP shared module, pending setup | +| JarvisJr | `com.jarvisjr.app` | ❌ Needs creation | Jetpack Compose, pending setup | + +--- + +## One-Time Setup + +### 1. Google Play Console Account + +If you don't have one yet: + +1. Go to [play.google.com/console/signup](https://play.google.com/console/signup) +2. Pay the one-time $25 registration fee +3. Complete identity verification (can take 48 hours) + +### 2. Create a Service Account (for CLI uploads) + +1. Go to [Google Cloud Console → IAM → Service Accounts](https://console.cloud.google.com/iam-admin/serviceaccounts) +2. Select or create a project (e.g., `bytelyst-releases`) +3. Click **"+ Create Service Account"** + - Name: `play-upload` + - Role: (skip — we'll grant access in Play Console) +4. Click **"..."** → **"Manage keys"** → **"Add Key"** → **"Create new key"** → **JSON** +5. Save the JSON key file to this directory and `~/.google-play/` + +```bash +mkdir -p ~/.google-play +cp service-account-key.json ~/.google-play/ +chmod 600 ~/.google-play/service-account-key.json +``` + +### 3. Link Service Account to Play Console + +1. Go to [Play Console → Settings → API access](https://play.google.com/console/developers/api-access) +2. Click **"Link"** next to Google Cloud project +3. Under Service Accounts, find `play-upload` → **"Grant access"** +4. Set permissions: **Release manager** (or **Admin** for full control) +5. Add the apps it can manage + +--- + +## Creating a New App in Play Console + +1. Go to [Play Console → All apps](https://play.google.com/console/developers) +2. Click **"Create app"** +3. Fill in: + - **App name:** Product name (e.g., `ChronoMind`) + - **Default language:** English (United States) + - **App or Game:** App + - **Free or Paid:** Free +4. Accept declarations and click **"Create app"** + +### After Creation — Complete the Store Listing + +Navigate through the **Dashboard** checklist: + +| Section | Required For | +| --------------- | ------------------------------------------------ | +| App access | If app requires login → provide test credentials | +| Ads | Declare if app contains ads | +| Content rating | Complete IARC questionnaire | +| Target audience | Select age groups | +| News apps | Declare if it's a news app | +| COVID-19 apps | Declare if related | +| Data safety | Privacy questionnaire (like Apple's App Privacy) | +| Government apps | Declare if government-related | + +--- + +## Building an Android Release + +### Signing Key Setup (one-time per app) + +```bash +# Generate an upload keystore +keytool -genkey -v \ + -keystore ~/keystores/chronomind-upload.jks \ + -keyalg RSA -keysize 2048 -validity 10000 \ + -alias upload \ + -storepass \ + -keypass \ + -dname "CN=ByteLyst, O=ByteLyst, L=Dallas, ST=TX, C=US" +``` + +> **⚠️ CRITICAL:** Back up the keystore file and passwords. If you lose them, you cannot update the app. Store a copy in this directory. + +### Configure signing in the app + +Create or edit `android/keystore.properties` (gitignored): + +```properties +storePassword= +keyPassword= +keyAlias=upload +storeFile=/Users//keystores/chronomind-upload.jks +``` + +Reference in `android/app/build.gradle.kts`: + +```kotlin +val keystoreProperties = Properties() +val keystoreFile = rootProject.file("keystore.properties") +if (keystoreFile.exists()) keystoreProperties.load(keystoreFile.inputStream()) + +android { + signingConfigs { + create("release") { + keyAlias = keystoreProperties["keyAlias"] as? String + keyPassword = keystoreProperties["keyPassword"] as? String + storeFile = file(keystoreProperties["storeFile"] as? String ?: "") + storePassword = keystoreProperties["storePassword"] as? String + } + } + buildTypes { + release { + signingConfig = signingConfigs.getByName("release") + } + } +} +``` + +### Build the Release AAB + +```bash +cd android +./gradlew :app:bundleRelease +# Output: app/build/outputs/bundle/release/app-release.aab +``` + +### Build a Release APK (for direct install) + +```bash +./gradlew :app:assembleRelease +# Output: app/build/outputs/apk/release/app-release.apk +``` + +--- + +## Uploading to Play Console + +### Manual Upload (Play Console UI) + +1. Go to app → **Production** (or **Internal testing** / **Closed testing**) +2. Click **"Create new release"** +3. Upload the `.aab` file +4. Add release notes +5. Click **"Review release"** → **"Start rollout"** + +### CLI Upload via bundletool + Google API + +```bash +pip install google-api-python-client google-auth + +python3 -c " +from googleapiclient.discovery import build +from google.oauth2 import service_account + +creds = service_account.Credentials.from_service_account_file( + '$HOME/.google-play/service-account-key.json', + scopes=['https://www.googleapis.com/auth/androidpublisher'] +) + +service = build('androidpublisher', 'v3', credentials=creds) +package = 'com.chronomind.app' + +edit = service.edits().insert(body={}, packageName=package).execute() +edit_id = edit['id'] + +service.edits().bundles().upload( + editId=edit_id, packageName=package, + media_body='app/build/outputs/bundle/release/app-release.aab', + media_mime_type='application/octet-stream' +).execute() + +service.edits().tracks().update( + editId=edit_id, packageName=package, track='internal', + body={'releases': [{'status': 'completed', 'versionCodes': ['1']}]} +).execute() + +service.edits().commit(editId=edit_id, packageName=package).execute() +print('Upload complete!') +" +``` + +### Using Gradle Play Publisher Plugin (recommended for CI) + +Add to `android/build.gradle.kts`: + +```kotlin +plugins { + id("com.github.triplet.play") version "3.11.0" +} + +play { + serviceAccountCredentials.set(file("$HOME/.google-play/service-account-key.json")) + track.set("internal") // internal, alpha, beta, production + defaultToAppBundles.set(true) +} +``` + +Then: + +```bash +./gradlew publishBundle # Upload AAB to configured track +./gradlew publishInternalBundle # Upload to internal testing +./gradlew publishAlphaBundle # Upload to closed testing +./gradlew publishBundle --track production # Upload to production +``` + +--- + +## Testing Tracks + +| Track | Testers | Review Required | Purpose | +| ---------------------- | -------------------- | -------------------- | ----------------- | +| Internal testing | Up to 100 (by email) | No | Quick dev testing | +| Closed testing (Alpha) | Invite-only | No (first time: yes) | Wider beta | +| Open testing (Beta) | Anyone with link | Yes (first time) | Public beta | +| Production | Everyone | Yes | Full release | + +### Internal Testing Setup + +1. Go to app → **Testing** → **Internal testing** +2. Click **"Create new release"** +3. Upload `.aab` +4. Click **"Review release"** → **"Start rollout"** +5. Go to **Testers** tab → Create email list → Add tester emails +6. Share the opt-in link with testers + +--- + +## Play Store Submission Checklist + +### Required Assets + +| Asset | Spec | Notes | +| ------------------------ | ------------------------ | ----------------------------- | +| App Icon | 512×512 PNG | 32-bit, no alpha | +| Feature Graphic | 1024×500 PNG or JPG | Required for store listing | +| Screenshots (Phone) | 320-3840px, 16:9 or 9:16 | Min 2, max 8 per device type | +| Screenshots (7" Tablet) | Same as phone | Required if targeting tablets | +| Screenshots (10" Tablet) | Same as phone | Required if targeting tablets | +| Promo Video | YouTube URL | Optional | + +### Required Metadata + +| Field | Notes | +| ------------------- | --------------------------- | +| App name | Max 30 characters | +| Short description | Max 80 characters | +| Full description | Max 4000 characters | +| App category | Select from predefined list | +| Contact email | Required, public | +| Privacy Policy URL | **Required** | +| App type & category | App or Game + category | + +### Data Safety Declaration + +In Play Console → App content → Data safety: + +- Declare all data types collected +- Whether data is encrypted in transit +- Whether users can request deletion +- Whether data is shared with third parties +- Purpose of each data type + +### Content Rating (IARC) + +1. Go to App content → Content rating +2. Complete the IARC questionnaire +3. Receive ratings for all regions automatically + +--- + +## Corporate Network / Gradle Proxy + +When building on the corporate network: + +```bash +# Ensure NETWORK=corp is set (switch-network.sh) +echo $GRADLE_OPTS +# Should show -Djavax.net.ssl.trustStore=~/.gradle/ssl/gradle-cacerts.jks + +# If not set: +export GRADLE_OPTS="-Djavax.net.ssl.trustStore=$HOME/.gradle/ssl/gradle-cacerts.jks -Djavax.net.ssl.trustStorePassword=changeit" +``` + +See `learning_ai_common_plat/AGENTS.md` § 9 for full proxy setup docs. + +--- + +## Troubleshooting + +| Error | Fix | +| ----------------------------- | ------------------------------------------------------------ | +| `Keystore was tampered with` | Wrong password or corrupted keystore | +| `No key with alias 'upload'` | Check `keyAlias` in keystore.properties | +| `Version code already exists` | Bump `versionCode` in build.gradle.kts | +| `APK/AAB not signed` | Check signingConfig in release buildType | +| `Deobfuscation file missing` | Upload mapping.txt alongside the AAB | +| `Data safety form incomplete` | Complete all sections in App content → Data safety | +| `Target API level` | Google requires targeting latest Android API (currently 34+) | +| Gradle proxy failure | Set `GRADLE_OPTS` with truststore (see above) | +| `SDK not found` | Set `sdk.dir` in `local.properties` | + +--- + +## Key Files + +| File | Location | Purpose | +| ------------------- | ------------------------------------------ | -------------------- | +| Service Account Key | `~/.google-play/service-account-key.json` | CLI uploads | +| Upload Keystore | `~/keystores/-upload.jks` | Release signing | +| keystore.properties | `android/keystore.properties` (gitignored) | Local signing config |