diff --git a/README.md b/README.md index 57eb398..ed2c08e 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # OpenCode Mobile **The open-source Android client for the [opencode](https://github.com/sst/opencode) AI coding agent.** -AI-assisted coding from your phone — Android, via F-Droid or a direct APK. +AI-assisted coding from your phone — Android, via Google Play, F-Droid, or a direct APK. [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![F-Droid repo](https://img.shields.io/badge/F--Droid-add_our_repo-1976D2?logo=f-droid)](https://dzianisv.github.io/opencode-mobile/fdroid/repo) [![Download APK](https://img.shields.io/badge/Download-APK-green?logo=android)](https://github.com/dzianisv/opencode-mobile/releases/latest) -[![Google Play](https://img.shields.io/badge/Google_Play-coming_soon-lightgrey?logo=google-play)](#) +[![Google Play](https://img.shields.io/badge/Google_Play-Available-4CAF50?logo=google-play)](https://play.google.com/store/apps/details?id=cc.agentlabs.opencode) > **Not affiliated with opencode.** OpenCode Mobile is an independent, community-built client and is > not made by, endorsed by, or affiliated with the opencode / Anomaly team. It talks to an opencode @@ -14,20 +14,26 @@ AI-assisted coding from your phone — Android, via F-Droid or a direct APK. --- +**New: tap "Try a Demo" in the app to see the agent fix a real bug — reasoning, a grep, a diff, a permission prompt — in about 30 seconds, no server needed.** + +--- + ## Install (Android) -There are **two working ways** to install OpenCode Mobile today, both for Android: +There are **three working ways** to install OpenCode Mobile today, all for Android: -1. **F-Droid (recommended)** — add our self-hosted repo to any F-Droid client, then install/update from there: +1. **Google Play** — **https://play.google.com/store/apps/details?id=cc.agentlabs.opencode** + +2. **F-Droid (self-hosted repo)** — add our self-hosted repo to any F-Droid client, then install/update from there: ``` https://dzianisv.github.io/opencode-mobile/fdroid/repo ``` In the F-Droid app: **Settings → Repositories → + (add)** and paste the URL above. Current version: **v0.4.3**. -2. **Direct signed APK** — download the latest release and install it manually: +3. **Direct signed APK** — download the latest release and install it manually: **https://github.com/dzianisv/opencode-mobile/releases/latest** -> iOS is not available (see [Roadmap](#roadmap)). Google Play is in internal testing only (no public listing yet). IzzyOnDroid submission is pending. +> iOS is not available (see [Roadmap](#roadmap)). IzzyOnDroid submission is pending. --- @@ -45,6 +51,7 @@ OpenCode Mobile is a React Native / Expo app that brings the power of the [openc ## Features +- **Offline demo mode** — tap "Try a Demo" to see a full bug-fix walkthrough (reasoning → grep → diff → permission prompt) with zero setup, right from the empty state - **Multi-connection** — manage multiple opencode servers (local network, Cloudflare Tunnel, ngrok, or Tailscale) - **Biometric unlock** — Face ID, Touch ID, or Android fingerprint protects the app and individual message sends - **Streaming chat** — token-by-token streaming responses directly from your opencode server @@ -61,18 +68,20 @@ Package: `cc.agentlabs.opencode` · Android only · current version v0.4.3 | Channel | Status | How | |---|---|---| +| **Google Play** | **Live** | [play.google.com/store/apps/details?id=cc.agentlabs.opencode](https://play.google.com/store/apps/details?id=cc.agentlabs.opencode) | | **F-Droid (self-hosted repo)** | **Live** | Add [`https://dzianisv.github.io/opencode-mobile/fdroid/repo`](https://dzianisv.github.io/opencode-mobile/fdroid/repo) in your F-Droid client | | **Direct APK** | **Live** | [github.com/dzianisv/opencode-mobile/releases/latest](https://github.com/dzianisv/opencode-mobile/releases/latest) | -| Google Play | Internal testing / coming soon | No public listing yet | | IzzyOnDroid | Submission pending | Not live yet | | Apple App Store / iOS | Not available | See [Roadmap](#roadmap) | -> The two live, supported install channels are the **F-Droid self-hosted repo** and the **direct signed APK**, both Android. Google Play is internal-testing only, IzzyOnDroid is pending, and there is no iOS build. +> The three live, supported install channels are **Google Play**, the **F-Droid self-hosted repo**, and the **direct signed APK**, all Android. IzzyOnDroid is pending, and there is no iOS build. --- ## Quick Start +**Don't have a server yet?** Install the app and tap **Try a Demo** on the Sessions screen first — no setup required. It plays back a scripted bug-fix session through the app's real chat, diff, and permission-approval UI, offline, in about 30 seconds. + **Step 1 — Start opencode on your machine** ```bash @@ -83,7 +92,7 @@ npm install -g opencode OPENCODE_SERVER_PASSWORD=yourpassword opencode serve --hostname 0.0.0.0 --port 4096 ``` -**Step 2 — Install OpenCode Mobile** via the [F-Droid self-hosted repo or direct APK](#install-android) (or build from source — see [CONTRIBUTING.md](CONTRIBUTING.md)). +**Step 2 — Install OpenCode Mobile** via [Google Play, F-Droid, or a direct APK](#install-android) (or build from source — see [CONTRIBUTING.md](CONTRIBUTING.md)). **Step 3 — Add a connection in the app** @@ -132,6 +141,8 @@ OpenCode Mobile is a thin client. It speaks the opencode HTTP + SSE API: listing | Feature | Status | |---|---| +| Offline demo mode | Stable | +| First-run onboarding clarity | Stable | | Multi-connection management | Stable | | Session list + creation | Stable | | Streaming chat | Stable | diff --git a/app/demo.tsx b/app/demo.tsx index df14092..8ab03c0 100644 --- a/app/demo.tsx +++ b/app/demo.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from "react" +import { useCallback, useEffect, useMemo, useState } from "react" import { View, Text, ScrollView, TouchableOpacity, StyleSheet, useColorScheme, Linking } from "react-native" import { Stack, useRouter } from "expo-router" import { Ionicons } from "@expo/vector-icons" @@ -6,6 +6,13 @@ import { useTranslation } from "react-i18next" import { MessageBubble, PermissionPrompt } from "../src/components/chat" import { buildDemoScript, buildDemoCompletionMessage, buildDemoDenialMessage } from "../src/lib/demo-script" import { SETUP_GUIDE_URL } from "../src/lib/links" +import { track, AnalyticsEvent } from "../src/lib/analytics" +import { + demoStepAdvancedProps, + demoCompletedOutcome, + demoExitedToConnectProps, + type DemoPermissionReply, +} from "../src/lib/demo-analytics" // Fully offline, scripted walkthrough for installers with no self-hosted // opencode server. ISOLATION: every message/part below is hardcoded local @@ -22,12 +29,26 @@ export default function DemoScreen() { // Built once per mount from pure, hardcoded data (src/lib/demo-script.ts). const script = useMemo(() => buildDemoScript(), []) - const [reply, setReply] = useState<"once" | "always" | "reject" | null>(null) + const [reply, setReply] = useState(null) - const handleReply = useCallback((r: "once" | "always" | "reject") => { - setReply(r) + // Fires once per screen mount — this is a fully offline, consent-gated + // event (track() is a no-op without telemetry consent, same as every + // other analytics call site). + useEffect(() => { + track(AnalyticsEvent.DemoStarted) }, []) + const handleReply = useCallback((r: DemoPermissionReply) => { + setReply(r) + track(AnalyticsEvent.DemoStepAdvanced, demoStepAdvancedProps(r)) + track(AnalyticsEvent.DemoCompleted, { outcome: demoCompletedOutcome(r) }) + }, []) + + const handleConnectPress = useCallback(() => { + track(AnalyticsEvent.DemoExitedToConnect, demoExitedToConnectProps(reply !== null)) + router.push("/connection/add") + }, [reply, router]) + const completion = useMemo(() => { if (!reply) return null return reply === "reject" ? buildDemoDenialMessage() : buildDemoCompletionMessage() @@ -59,7 +80,7 @@ export default function DemoScreen() { {t("demo.ctaSubtitle")} router.push("/connection/add")} + onPress={handleConnectPress} testID="demo-connect-button" > {t("demo.connectButton")} diff --git a/distribution/play-listing.md b/distribution/play-listing.md index 07c9f8b..abaa720 100644 --- a/distribution/play-listing.md +++ b/distribution/play-listing.md @@ -1,8 +1,14 @@ # Google Play Store Listing — OpenCode Mobile -Copy-paste reference for completing the Play Console store listing for `cc.agentlabs.opencode`. +Copy-paste reference for the Play Console store listing for `cc.agentlabs.opencode`. Paste these values directly into Play Console. Fields are validated against Play limits. +> **Status: live.** The app is publicly available on the production track — +> https://play.google.com/store/apps/details?id=cc.agentlabs.opencode (1K+ installs; +> see `distribution/retention-analysis.md`). The "First release strategy" and "Pending +> before first publish" sections below are kept as a historical record of how it got +> there — do not read them as the current state. + --- ## Main store listing @@ -322,25 +328,28 @@ Bump this file before tagging a release. Keep it under 500 chars. Per-language v --- -## First release strategy +## First release strategy (historical — completed) -1. **Internal testing** track first (up to 100 testers, no review) — what CI is wired for. +1. **Internal testing** track first (up to 100 testers, no review) — what CI was wired for. 2. **Closed testing** — 14+ days, 12+ testers required for new org accounts before promoting to production (Google's 2023 policy). 3. **Open testing** — optional intermediate step. 4. **Production** — only after Closed testing requirements met. -CI currently publishes to `internal` track. ✅ +Result: the app cleared this path and is now live on the production track (see the +status note at the top of this file). CI's default dispatch track (`internal`) is +still used for routine test builds; production releases are dispatched explicitly +(`track=production`) — see `.github/workflows/publish-play-store.yml`. --- -## Pending before first publish +## Pending before first publish (historical — resolved) -- [ ] Identity verification (government ID upload, Google review) -- [ ] App icon (real PNG, not placeholder) -- [ ] Adaptive icon (real PNG) -- [ ] Feature graphic 1024×500 -- [ ] At least 2 phone screenshots -- [ ] Privacy policy live at https://dzianisv.github.io/opencode-mobile/privacy/ -- [ ] Decide pricing model -- [ ] Run IARC content rating questionnaire (after app created in Play Console) -- [ ] Complete Data safety form (after app created in Play Console) +- [x] Identity verification (government ID upload, Google review) +- [x] App icon (real PNG, not placeholder) +- [x] Adaptive icon (real PNG) +- [x] Feature graphic 1024×500 +- [x] At least 2 phone screenshots +- [x] Privacy policy live at https://dzianisv.github.io/opencode-mobile/privacy/ +- [x] Decide pricing model (free, no ads, no IAP) +- [x] Run IARC content rating questionnaire (after app created in Play Console) +- [x] Complete Data safety form (after app created in Play Console) diff --git a/distribution/privacy-policy.html b/distribution/privacy-policy.html index bb90a42..6e3a028 100644 --- a/distribution/privacy-policy.html +++ b/distribution/privacy-policy.html @@ -88,7 +88,7 @@

OpenCode Mobile — Privacy Policy

- Effective date: 2026-05-24  |  + Effective date: 2026-07-18  |  Operator: VIBE TECHNOLOGIES, LLC  |  App: OpenCode Mobile (cc.agentlabs.opencode)

@@ -232,6 +232,27 @@ An agent response finishes — + + demo_started + You open the offline "Try a Demo" screen (no server, no network) + — + + + demo_step_advanced + You reply to the demo's scripted permission prompt + step_index, step_name, reply ("once", "always", or + "reject") + + + demo_completed + The scripted demo reaches its end + outcome ("completed" or "denied") + + + demo_exited_to_connect + You tap "Connect your own server" on the demo's CTA + reached_completion (true/false) + @@ -239,7 +260,8 @@ What analytics events never contain: your server URL, hostname, IP address, or port; prompts, messages, or AI responses; code or file contents; tokens or credentials; raw error messages. Connection failures are reduced to a fixed list of coarse categories - before being sent. + before being sent. The demo screen is fully offline and hardcoded — these events describe + interaction with the scripted walkthrough, never real session content.

Analytics data is sent to PostHog's EU region (eu.i.posthog.com) @@ -462,7 +484,7 @@

© 2026 VIBE TECHNOLOGIES, LLC. OpenCode Mobile is MIT-licensed open-source software. - Privacy policy effective 2026-05-24. + Privacy policy effective 2026-07-18.
diff --git a/distribution/privacy-policy.md b/distribution/privacy-policy.md index c22ea3e..c966fdc 100644 --- a/distribution/privacy-policy.md +++ b/distribution/privacy-policy.md @@ -1,6 +1,6 @@ # OpenCode Mobile — Privacy Policy -**Effective date:** 2026-05-24 +**Effective date:** 2026-07-18 **Operator:** VIBE TECHNOLOGIES, LLC **App:** OpenCode Mobile (`cc.agentlabs.opencode`) @@ -63,8 +63,12 @@ Events collected, with their only properties: | `connection_failed` | The connection test fails | `source`, `error_class` (a coarse category such as "timeout" or "unauthorized" — never the raw error text) | | `message_sent` | You send a message to an agent session | — | | `response_received` | An agent response finishes | — | +| `demo_started` | You open the offline "Try a Demo" screen (no server, no network) | — | +| `demo_step_advanced` | You reply to the demo's scripted permission prompt | `step_index`, `step_name`, `reply` ("once", "always", or "reject") | +| `demo_completed` | The scripted demo reaches its end | `outcome` ("completed" or "denied") | +| `demo_exited_to_connect` | You tap "Connect your own server" on the demo's CTA | `reached_completion` (true/false) | -What analytics events **never** contain: your server URL, hostname, IP address, or port; prompts, messages, or AI responses; code or file contents; tokens or credentials; raw error messages. Connection failures are reduced to a fixed list of coarse categories before being sent. +What analytics events **never** contain: your server URL, hostname, IP address, or port; prompts, messages, or AI responses; code or file contents; tokens or credentials; raw error messages. Connection failures are reduced to a fixed list of coarse categories before being sent. The demo screen is fully offline and hardcoded — these events describe interaction with the scripted walkthrough, never real session content. Analytics data is sent to PostHog's **EU region** (`eu.i.posthog.com`) and is identified only by a random, app-generated anonymous ID — not linked to your name, email, or any account. diff --git a/docs/analytics.md b/docs/analytics.md index b48b80f..02e04fb 100644 --- a/docs/analytics.md +++ b/docs/analytics.md @@ -26,7 +26,7 @@ and reach first value (message sent → response received)?** Nothing else is tr | Destination | PostHog **EU region** — `https://eu.i.posthog.com` (override: `EXPO_PUBLIC_POSTHOG_HOST`) | | API key | `EXPO_PUBLIC_POSTHOG_KEY` (CI secret; unset ⇒ analytics is a strict no-op) | | Identity | PostHog's random app-generated anonymous ID only; no `identify()` calls, no user IDs | -| Code | `src/lib/analytics.ts` (wrapper), `src/lib/analytics-classify.ts` (error bucketing), `src/lib/telemetry.ts` (consent gate) | +| Code | `src/lib/analytics.ts` (wrapper), `src/lib/analytics-classify.ts` (error bucketing), `src/lib/demo-analytics.ts` (demo-funnel property derivation), `src/lib/telemetry.ts` (consent gate) | ## Event schema @@ -42,6 +42,10 @@ section 3a of `distribution/privacy-policy.md`. | `connection_failed` | Health check fails | `source`, `error_class` | `src/stores/connections.ts` | | `message_sent` | User sends a prompt to an agent session (excludes slash commands) | — | `src/stores/sessions.ts` | | `response_received` | Agent response finishes streaming (busy → idle), excluding user-aborted runs | — | `src/stores/events.ts` | +| `demo_started` | The offline `/demo` screen mounts (no server, no network) | — | `app/demo.tsx` | +| `demo_step_advanced` | User advances a step in the scripted demo (currently: replies to the demo's permission prompt) | `step_index`, `step_name`, `reply` (`"once"` \| `"always"` \| `"reject"`) | `app/demo.tsx`, `src/lib/demo-analytics.ts` | +| `demo_completed` | The scripted demo reaches its end (completion or denial message shown) — the key demo activation metric | `outcome` (`"completed"` \| `"denied"`) | `app/demo.tsx`, `src/lib/demo-analytics.ts` | +| `demo_exited_to_connect` | User taps "Connect your own server" on the demo's CTA card | `reached_completion` (boolean) | `app/demo.tsx` | `error_class` is one of a fixed enum — `malformed-url`, `no-internet`, `server-unreachable`, `unauthorized`, `tls-error`, `timeout`, `unknown` (`src/lib/analytics-classify.ts`). The raw diff --git a/docs/privacy/index.html b/docs/privacy/index.html index 3eb7a88..99805ef 100644 --- a/docs/privacy/index.html +++ b/docs/privacy/index.html @@ -95,7 +95,7 @@

OpenCode Mobile — Privacy Policy

- Effective date: 2026-05-24  |  + Effective date: 2026-07-18  |  Operator: VIBE TECHNOLOGIES, LLC  |  App: OpenCode Mobile (cc.agentlabs.opencode)

@@ -239,6 +239,27 @@ An agent response finishes — + + demo_started + You open the offline "Try a Demo" screen (no server, no network) + — + + + demo_step_advanced + You reply to the demo's scripted permission prompt + step_index, step_name, reply ("once", "always", or + "reject") + + + demo_completed + The scripted demo reaches its end + outcome ("completed" or "denied") + + + demo_exited_to_connect + You tap "Connect your own server" on the demo's CTA + reached_completion (true/false) + @@ -246,7 +267,8 @@ What analytics events never contain: your server URL, hostname, IP address, or port; prompts, messages, or AI responses; code or file contents; tokens or credentials; raw error messages. Connection failures are reduced to a fixed list of coarse categories - before being sent. + before being sent. The demo screen is fully offline and hardcoded — these events describe + interaction with the scripted walkthrough, never real session content.

Analytics data is sent to PostHog's EU region (eu.i.posthog.com) @@ -469,7 +491,7 @@