Files
opencode-mobile/distribution/strategy.md
Den 2b9b571d6e feat(privacy+dist): telemetry consent gate + app store distribution prep (#4)
* 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>
2026-05-25 17:42:03 -07:00

162 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```