* fix(security): fail closed on biometric init error H-03: setting isAuthenticated: true on initialization failure was a security bypass — any crash during biometric setup granted full access. Fail closed instead; user sees auth prompt on next open. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(security): use Crypto.randomUUID for connection IDs H-04: Math.random() is not cryptographically random. Connection IDs are used as SecureStore key suffixes; switch to expo-crypto randomUUID for a secure source. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(deps): pin expo-crypto to ~15.0.9 15.0.10 does not exist on npm; ~15.0.9 is the latest stable in the 15.x series compatible with Expo SDK 54. * feat: add OpenCode Connect coming-soon waitlist card Adds a discoverable 'OpenCode Connect — Coming Soon' card to the add-connection quick-connect screen. Users can enter their email and tap 'Join Waitlist' to send a pre-filled mailto. No backend required. * fix(cua): detect actual screen dimensions and fix JSON parsing - Get real screen size via `wm size` instead of hardcoding 1080x2400; emulator is 1080x1920 so y-coordinates were systematically off - Extract first JSON object via regex when model returns multiple objects - Use AZURE_OPENAI_MODEL env var for deployment name (defaults gpt-5.4) - Add AZURE_DEV_AI_* path for Azure AI Foundry endpoints Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(security): SHA-pin upload-google-play and sanitize notification bodies M-02: Pin r0adkll/upload-google-play to commit SHA e738b9d (v1.1.5) to prevent supply-chain hijack via tag mutation. M-03: Sanitize all push notification bodies — strip control chars, truncate to 200 chars. Prevents server-supplied strings (error messages, file paths from permission patterns, session titles) from leaking unbounded text into the OS notification drawer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(privacy): add telemetry consent gate for Sentry crash reporting Sentry was always-on, violating F-Droid anti-feature policy and user trust norms. Now gated behind explicit opt-in: - First-launch consent modal (TelemetryConsentModal) shows once on fresh install; user can Allow or Decline. - Consent state persisted in expo-secure-store (survives restarts). - Settings > Privacy section: crash reporting toggle + privacy policy link. - initSentry() called only after consent granted — not on app start. Closes #3 (partial) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(config): add real icons and complete iOS/Android app.json config - Add 1024×1024 app icon, 432×432 adaptive icon foreground, 200×200 splash - iOS: push notification entitlement (aps-environment: production), speech/ microphone/camera/photo usage descriptions for future features, disable ITSAppUsesNonExemptEncryption - Android: adaptive icon with dark background (#0F172A), versionCode: 1 - expo-notifications plugin wired in app.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(dist): add iOS CI workflow, README rewrite, CONTRIBUTING, and LICENSE - publish-app-store.yml: EAS Build + TestFlight submission; runs on tag/release/ workflow_dispatch; bumps ios.buildNumber from github.run_number - README: full rewrite — features, install badges, connection guide, contributing - CONTRIBUTING.md: contribution guide for OSS contributors - LICENSE: MIT Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(dist): add store listings, strategy, privacy policy, F-Droid/IzzyOnDroid templates - distribution/strategy.md: monetization strategy (free client + opencode Cloud) - distribution/play-listing.md: Google Play store copy (name, description, tags) - distribution/app-store-listing.md: App Store listing copy - distribution/privacy-policy.{md,html}: GDPR-compliant privacy policy - distribution/PLAY_CONSOLE_SETUP.md: Play Console setup runbook - distribution/ios-enrollment-runbook.md: Apple Developer Program enrollment steps - distribution/SIGNING-KEY-FINGERPRINTS.md: keystore fingerprint for reproducible builds - distribution/fdroid-submission/: F-Droid metadata template - distribution/izzyondroid-submission/: IzzyOnDroid submission template - distribution/whatsnew/: Play Store release notes (en-US) - distribution/whatsnew-ios/: TestFlight release notes - distribution/play-graphics/: Play Store screenshot placeholders - distribution/app-store-graphics/: App Store screenshot placeholders Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(telemetry): handle SecureStore failure + Android back button - add .catch() on loadTelemetryConsent() so SecureStore rejection shows the consent modal instead of blocking startup forever - add onRequestClose={onDecline} to Modal so Android back button records the decline rather than silently dismissing - fix catch block in telemetry.ts to not clobber _resolved when SecureStore read fails mid-session Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ci): run gradlew clean to prevent stale modules.json duplicate Sentry Gradle plugin writes modules.json to src/main/assets; cached build intermediates contain an old copy → mergeReleaseAssets fails with 'Duplicate resources'. Running clean before assembleRelease clears the intermediate state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ci): remove android build output cache causing duplicate modules.json Caching android/app/build/intermediates and android/app/.cxx causes two issues: 1. Stale modules.json in intermediates → Duplicate resources error 2. .cxx CMake artifacts reference absolute paths → ninja clean fails Keeping only Gradle distribution cache (~/.gradle) which is safe. Expo prebuild regenerates android sources fresh each run anyway. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
162 lines
9.4 KiB
Markdown
162 lines
9.4 KiB
Markdown
# OpenCode Mobile — Distribution + Monetization Strategy
|
||
|
||
Date: 2026-05-24
|
||
Sources: 3 research reports (market analysis, monetization, F-Droid distribution) + Play Console signup state.
|
||
|
||
---
|
||
|
||
## Pick one model for everything downstream
|
||
|
||
**Free client everywhere + paid "opencode Cloud" hosted backend.**
|
||
|
||
Mirrors Tailscale ($45M ARR, OSS client, paid coordination service) and the opencode authors' own model (OSS CLI, paid OpenCode Zen gateway, several million ARR in 5 months).
|
||
|
||
- **Client (this repo, MIT)**: free on Play Store, IzzyOnDroid, and F-Droid. Identical binary on all three (same signing key via reproducible builds).
|
||
- **opencode Cloud (separate product, proprietary)**: managed opencode server hosting. One-tap connect option in the app alongside "self-hosted" and "tunnel". Target $10/mo individual, $30/mo team. **Not built yet — see Action items.**
|
||
- **Donations layer**: GitHub Sponsors / OpenCollective to cover Sentry + CI costs (~$60/mo) while cloud revenue scales.
|
||
|
||
Rejected alternatives:
|
||
- ❌ **Paid Play + free F-Droid** — MIT allows redistribution; community resentment when users find F-Droid version; license-check callbacks always get stripped in forks. DAVx⁵ exception works only because it's a one-time donation, not a feature gate.
|
||
- ❌ **Subscription gated in client** — telemetry license checks earn F-Droid `Tracking` anti-feature and instantly get stripped by community forks.
|
||
- ❌ **Ads** — kills OSS credibility, contradicts a developer audience.
|
||
|
||
---
|
||
|
||
## Distribution channels — priority order
|
||
|
||
| # | Channel | Identity | Status | Time to live | Notes |
|
||
|---|---|---|---|---|---|
|
||
| 1 | **Google Play (Internal)** | `ai.opencode.mobile` | ⏸ blocked on identity verification | Days after ID approved | CI ready. Track: `internal` first, then closed testing (12+ testers / 14d) before production. |
|
||
| 2 | **IzzyOnDroid** | `ai.opencode.mobile` (same key) | ❌ not started | 1–3 days | Submit prebuilt APK to https://codeberg.org/IzzyOnDroid/repodata/issues. Fastest OSS channel. |
|
||
| 3 | **F-Droid mainline** | `ai.opencode.mobile` (same key, reproducible build) | ❌ not started | 4–12 weeks | File MR at https://gitlab.com/fdroid/fdroiddata. Requires `expo-notifications` FCM audit + Sentry opt-in gate. |
|
||
| 4 | **Apple App Store** | `ai.opencode.mobile` | ⏸ pending iOS prep agent | Weeks (Apple enrollment $99 + review) | iOS agent running — separate report. |
|
||
|
||
**All channels: same package id (`ai.opencode.mobile`), same signing key.** Lets users update across stores in-place.
|
||
|
||
---
|
||
|
||
## Blockers + outstanding work
|
||
|
||
### Hard blockers (need user)
|
||
|
||
1. **Google Play identity verification** — upload governor ID (Dzianis Vashchuk). Unlocks: API access, Create app, AAB upload, CI publish.
|
||
2. **Apple Developer Program enrollment** — $99/year, D-U-N-S 142059652 ready. iOS agent will produce runbook.
|
||
3. **App icon + adaptive icon + feature graphic** — current `assets/*.json` are placeholders. Need real PNGs before either Play or App Store publish.
|
||
4. **Privacy policy URL** — must be live at https://opencode.vibebrowser.app/privacy before Play publish. Template in `play-listing.md`.
|
||
|
||
### Soft blockers (we can fix without user)
|
||
|
||
5. **Sentry opt-in consent gate** — currently always-on; needed for F-Droid `Tracking` anti-feature avoidance. Add settings toggle + first-launch consent screen. Persist in `expo-secure-store`.
|
||
6. ~~**Audit `expo-notifications` FCM usage**~~ — ✅ done 2026-05-24. `src/lib/notifications.ts` uses local-only (`scheduleNotificationAsync`); no `getExpoPushTokenAsync`/`getDevicePushTokenAsync` anywhere. Remaining concern: library still compiles FCM receiver classes — F-Droid scanner may flag. Fix later with a `fdroid` Gradle flavor that excludes the FCM artifact. **Non-blocker for IzzyOnDroid** (more tolerant). For mainline F-Droid: add `productFlavors { fdroid { /* exclude FCM */ } }` to `android/app/build.gradle`.
|
||
7. **APK size check** — IzzyOnDroid limit 30 MB. AAB currently 58.5 MB but that's universal — per-ABI splits typically 15–20 MB.
|
||
8. **Fastlane metadata** — `fastlane/metadata/android/en-US/{short_description.txt,full_description.txt,images/}` so F-Droid auto-pulls listing.
|
||
9. **Reproducible build verification** — F-Droid builds from source, compares to our signed APK. Need to verify our build is reproducible (no embedded timestamps, no machine-specific paths).
|
||
|
||
### Pre-launch tasks (low priority, optional)
|
||
|
||
10. **opencode Cloud MVP** — managed opencode hosting service. Stripe billing. The actual revenue product. **Big scope, separate project.**
|
||
11. **GitHub Sponsors profile** — VIBE TECHNOLOGIES, LLC org. Tiers: $5 / $15 / $50.
|
||
12. **Closed testing tester recruitment** — 12+ testers for 14d before production. Recruit from opencode community / dev Twitter / r/androiddev.
|
||
|
||
---
|
||
|
||
## What's already done (this session)
|
||
|
||
| Item | Status |
|
||
|---|---|
|
||
| Google Play developer account (org) | ✅ Created, ID 8842655543970815326, $25 paid |
|
||
| Mercury virtual card | ✅ Saved to Bitwarden |
|
||
| D-U-N-S 142059652 | ✅ Retrieved, saved to Bitwarden + skill |
|
||
| GCP project `opencode-mobile-deploy` | ✅ Created |
|
||
| androidpublisher API | ✅ Enabled |
|
||
| Service account `playstore-deploy@…` | ✅ Created, JSON key saved to Bitwarden + GitHub secret |
|
||
| Signed AAB | ✅ Built at `android/app/build/outputs/bundle/release/app-release.aab` (58.5 MB) |
|
||
| Website verification | ✅ www.vibebrowser.app verified via Search Console auto-detection |
|
||
| Contact email verification | ✅ support@vibebrowser.app verified via Play Console code |
|
||
| Payments profile | ✅ Linked with D-U-N-S 142059652 |
|
||
| CI workflow audit + fixes | ✅ `publish-play-store.yml` — versionCode auto-bump, r0adkll@v1.1.5, whatsNewDirectory wired |
|
||
| Play Store listing copy | ✅ `distribution/play-listing.md` |
|
||
| Skill `vibetechnologies-llc` | ✅ Created with company facts |
|
||
| Subagent `vibetechnologies-llc-curator` | ✅ Created for auto-maintenance |
|
||
|
||
---
|
||
|
||
## Per-channel publishing recipe
|
||
|
||
### Google Play (after identity verified)
|
||
|
||
1. (manual, in browser) Home → Verify your identity → upload governor ID. Wait days.
|
||
2. (manual) Setup → API access → Link `opencode-mobile-deploy`. Grant `playstore-deploy@…` "Release to production".
|
||
3. (manual) Create app `ai.opencode.mobile`. Fill listing from `play-listing.md`. Upload icon + feature graphic + screenshots. Complete Data safety + Content rating + App access.
|
||
4. (manual, first time) Upload `app-release.aab` to Internal testing track. Add tester emails.
|
||
5. (automated thereafter) `git tag v0.2.4 && git push --tags` → CI builds + publishes to Internal.
|
||
|
||
### IzzyOnDroid (after first Play AAB exists for parity)
|
||
|
||
1. Tag GitHub release `v0.2.x` with signed universal APK attached (not AAB — APK).
|
||
2. File issue at https://codeberg.org/IzzyOnDroid/repodata/issues:
|
||
```
|
||
App name: OpenCode Mobile
|
||
Package: ai.opencode.mobile
|
||
License: MIT
|
||
GitHub release: https://github.com/dzianisv/opencode-mobile/releases
|
||
APK SHA-256: <sha256>
|
||
Description: Mobile client for the opencode AI coding agent CLI. Self-hosted backend.
|
||
Note: NonFreeNet anti-feature applies (connects to user-self-hosted opencode server).
|
||
```
|
||
3. Wait 1–3 days for inclusion. Updates auto-pulled from each new GitHub release tag.
|
||
|
||
### F-Droid mainline (after Sentry opt-in + FCM audit done)
|
||
|
||
1. Fork https://gitlab.com/fdroid/fdroiddata.
|
||
2. Create `metadata/ai.opencode.mobile.yml` with reproducible-build config (template in F-Droid report).
|
||
3. Set `AllowedAPKSigningKeys: <sha256-fingerprint>` so F-Droid serves our pre-signed APK.
|
||
4. File MR. Iterate on build failures with reviewers. 4–12 week timeline.
|
||
5. Once accepted, request IzzyOnDroid delisting (they auto-remove when mainline accepts).
|
||
|
||
### Apple App Store
|
||
|
||
Per iOS agent report (pending) — runbook at `distribution/ios-enrollment-runbook.md` (to be created).
|
||
|
||
---
|
||
|
||
## Trigger for auto-publish (item 3 from user's plan)
|
||
|
||
"As soon as we get the ability to publish" = identity verified + app created + first manual AAB uploaded.
|
||
|
||
Once those manual steps are complete:
|
||
|
||
- Tag `v0.2.4` (or whatever the next version is) → existing `publish-play-store.yml` CI runs:
|
||
- Bumps `android.versionCode` from `github.run_number`
|
||
- Builds signed AAB
|
||
- Uploads to Internal track with what's-new notes
|
||
|
||
No additional infrastructure needed — CI is already wired for this. Just push the tag.
|
||
|
||
For email notification when Google approves identity: Google sends to vibeteaichnologies@gmail.com. Add a `gws gmail` poll script that watches for "Developer account verification" subject from `noreply@google.com` and pings via webhook / posts a GitHub issue.
|
||
|
||
---
|
||
|
||
## Files in this repo related to distribution
|
||
|
||
```
|
||
distribution/
|
||
├── PLAY_CONSOLE_SETUP.md # original handoff doc (now mostly historical)
|
||
├── play-listing.md # full Play Store copy + answers
|
||
├── strategy.md # this file
|
||
├── whatsnew/
|
||
│ └── whatsnew-en-US # release notes consumed by CI per-release
|
||
└── (pending)
|
||
├── app-store-listing.md # iOS agent will create
|
||
├── ios-enrollment-runbook.md # iOS agent will create
|
||
└── whatsnew-ios/
|
||
└── release-notes-en-US.txt # iOS first release notes
|
||
|
||
.github/workflows/
|
||
├── build.yml # Android dev build CI
|
||
├── cua-smoke.yml # CUA smoke tests
|
||
├── publish-play-store.yml # AUTOMATED Play publish on tag/release
|
||
└── (pending)
|
||
└── publish-app-store.yml # iOS agent will draft
|
||
```
|