Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .agents/memory/2026-07-24.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# 2026-07-24

- Scheduler check: exact prompt already configured as `opencode-mobile-owner-hourly-v4` (`0 * * * *`, 50-minute timeout, `commander`, `github-copilot/gpt-5.6-sol`). Job was running at 08:00 PDT. No duplicate or update created.
9 changes: 9 additions & 0 deletions .agents/memory/2026-07-25.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

## 2026-07-25 — Founding-member cohort qualified; Stripe test key founder-gated

- **Stripe BLOCKED (founder-gated):** vault has only `OpenClawBot - STRIPE_SECRET_KEY` = `sk_live_` (107 chars). Test mode requires a new Bitwarden Secure Note `STRIPE_TEST_SECRET_KEY` in folder `opencode-mobile`. Stripe dashboard login is anti-automation; CDP browser could not complete it. Did NOT touch the live key.
- **8 leads qualified** from real engagement, not scraping: PylotLight (#134 asked for OIDC hosted — strongest paid signal), cloph-dsp (4 issues + PR #54 closed-but-shipped-as-#148), zFitness (2 issues + fork this week, works @shopee), 2740653660 (shipped i18n #68 + #67), e-lie/SDBdevelopment/Med-amine0/mathysIN (forks).
- **No public emails on any of the 8** → contact channel is GitHub @mention on their own thread. Warmer than cold email.
- Artifact: `distribution/founding-members.md`, branch `growth/founding-members`, commit `bf56cba`.
- **Send rule recorded:** do not send outreach until Stripe test key exists — "would you pay?" with no way to pay burns the warmest leads.
- Baseline stays honest: MRR $0, replies 0, willingness-to-pay unknown.
123 changes: 123 additions & 0 deletions distribution/founding-members.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Founding Members — first paid cohort

**Status:** leads qualified, outreach drafted, **send BLOCKED on Stripe test key** (see § Blocker).
**Goal:** OpenCode Mobile's first dollar. Current MRR: **$0**. No Stripe product exists yet.
**Date qualified:** 2026-07-25

---

## Blocker (one-line ask for Den)

> **Den — add a Bitwarden Secure Note named `STRIPE_TEST_SECRET_KEY` (folder `opencode-mobile`) containing the `sk_test_...` key from https://dashboard.stripe.com/test/apikeys; the vault only has `OpenClawBot - STRIPE_SECRET_KEY` which is `sk_live_` and is off-limits.**

Why it's founder-gated: creating the test key requires an interactive login to the Stripe Dashboard on the
VIBE TECHNOLOGIES account (`vibeteaichnologies@gmail.com`). Stripe's login is anti-automation and the
CDP-driven browser session could not complete it.

Once that vault entry exists, everything below is unblocked and scriptable — no further founder input needed.

| Item | State |
|---|---|
| `STRIPE_TEST_SECRET_KEY` in Bitwarden (`opencode-mobile`) | **MISSING — founder-gated** |
| `OpenClawBot - STRIPE_SECRET_KEY` (`sk_live_`) | exists — **must not be used**, live mode |
| Stripe products (Free / $4.99 / $9.99) | not created — needs test key |
| Test checkout link | not created — needs products |
| Outreach message | **ready** (below) |
| Lead list | **ready** (below) |

---

## The 8 leads

Qualified from real repository engagement (issues filed, code contributed, forks, stars) — not scraped.
**None publish a public email**, so the contact channel is a GitHub `@mention` on the specific thread
they already opened. That is warmer than cold email anyway: they get a notification tied to their own words.

Ranked by purchase intent.

| # | Handle | Name / Locale | Contact channel | Evidence of intent | Fit rationale | Tier to pitch |
|---|---|---|---|---|---|---|
| 1 | `PylotLight` | "Light" — DevOps/Go dev | `@PylotLight` on **issue #134** (open) | Asked: *"Any chance at adding support for public hosted instances behind oidc/openid auth?"* | **Asked for the hosted product by name.** Wants a public hosted instance with SSO — that IS OpenCode Connect. DevOps background = pays for infra to not run infra. | **$9.99 Team** |
| 2 | `cloph-dsp` | Pedro Castro — Lisbon, PT | `@cloph-dsp` on **issue #53** | 4 issues (#53, #55, #56, #57), 1 PR (#54), fork, star | Heaviest external contributor. Reported the keyboard bug **and wrote a fix**. His PR was closed but the bug shipped fixed in #148 — owed a "you were right, it shipped" note. Deep daily user. | **$4.99 Pro** |
| 3 | `zFitness` | Shenzhen, CN — works at **@shopee** | `@zFitness` on **issue #150** | 2 issues (#147 keyboard, #150 refresh), fork **2026-07-24** | Active *this week*. Employed at a large tech co (Shopee) — corporate card, low price sensitivity. Blog: `zfitness.me`. | **$9.99 Team** |
| 4 | `2740653660` | "yyxxd" | `@2740653660` on **PR #68** | Shipped 2 features: #68 i18n + Simplified Chinese, #67 group sessions by project dir | Contributed *code*, not complaints. Highest goodwill in the pool. Ideal early advocate + zh-Hans channel. | **$4.99 Pro** (offer free year for contribution) |
| 5 | `e-lie` | Elie Gavoty | `@e-lie` via fork / GitHub profile | Forked **2026-07-25** — freshest signal in the pool | Forked within 24h of qualification — actively evaluating right now. Strike while hot. | **$4.99 Pro** |
| 6 | `SDBdevelopment` | — | `@SDBdevelopment` via fork | Forked 2026-07-18 | Handle implies a dev shop/agency → multi-seat potential. | **$9.99 Team** |
| 7 | `Med-amine0` | — | `@Med-amine0` via fork | Forked 2026-06-12 | Sustained interest (forked >6wk ago, repo still forked). Lower urgency. | **$4.99 Pro** |
| 8 | `mathysIN` | "mathys" — France | `@mathysIN` via fork | Forked 2026-05-20 — earliest external fork | Earliest believer, pre-dates most stars. Nostalgia/OG angle: "you found this before anyone." | **$4.99 Pro** |

**Reserve pool** (if any of the 8 go cold): stargazers with recent dates —
`Vitalii953`, `wsuff`, `FANATFANATA`, `2gn`, `song19931218`, `iAdanos`, `kiyou`, `heyalucardvania-cmyk`.

---

## Outreach message — ready to send

Post as a **comment on the person's own thread** (or a GitHub issue `@mention` for fork-only leads).
Personalize the `[HOOK]` line per the table above; everything else is fixed.

### Template

> **[HOOK]**
>
> I'm the maintainer of OpenCode Mobile. I'm opening a small **Founding Member** group — 8 people —
> before I turn on billing, and you're one of them.
>
> Here's the deal, plainly:
>
> - **OpenCode Connect** is the hosted version. No server to run, no Tailscale, no port forwarding — you
> open the app and it works. It's what people ask for most, and it's the thing that costs me money to run.
> - Founding Members get **50% off for life** on whatever tier they pick, and a direct line to me for
> feature requests. Your issues skip the queue. Permanently.
> - Free tier stays free, forever. Self-hosting stays free, forever. This does not paywall anything that
> works today.
>
> Planned pricing (Founding Member price in bold):
>
> | | Free | Pro | Team |
> |---|---|---|---|
> | Price | $0 | ~~$4.99~~ **$2.49**/mo | ~~$9.99~~ **$4.99**/mo |
> | Connect to your own server | ✅ | ✅ | ✅ |
> | Hosted OpenCode Connect | — | ✅ 1 workspace | ✅ 5 workspaces |
> | SSO / OIDC | — | — | ✅ |
> | Priority support | — | ✅ | ✅ |
>
> I'm not asking for money in this message. I'm asking one question: **would you pay for this, and at
> which tier?** A one-word answer is a complete answer. If it's "no," tell me why — that's just as useful
> and I'll take it on the chin.
>
> — Den

### Per-lead `[HOOK]` lines

| Lead | `[HOOK]` |
|---|---|
| `PylotLight` | Hey — you asked in #134 about hosted instances behind OIDC. I'm building exactly that, and I want you in the first group. |
| `cloph-dsp` | Pedro — you reported the keyboard bug in #53 *and* sent a fix in #54. I closed your PR and then shipped the same fix myself in #148. That was a bad look on my part; you were right. Thank you for four issues that all made the app better. |
| `zFitness` | 你好 — you filed #147 and #150 this week and forked the repo. You're one of the most active users I have right now. |
| `2740653660` | You shipped Simplified Chinese support (#68) and project-directory grouping (#67) into this app. I owe you one — so this offer comes with a free year on me, no strings. |
| `e-lie` | Elie — you forked this yesterday, so you're evaluating it right now. Let me save you some time. |
| `SDBdevelopment` | You forked OpenCode Mobile last week — if you're deploying this for a team, I want to talk before I set pricing. |
| `Med-amine0` | You forked this back in June and it's still on your account. Curious what you're using it for. |
| `mathysIN` | mathys — you forked this in May. You were the first person outside my own account to do that. |

### Sending rules

- **Do not send until `STRIPE_TEST_SECRET_KEY` exists** — a "would you pay?" message with no way to
actually pay burns the warmest leads in the list. One shot each.
- Send in **two waves**: leads 1–4 (high intent) first, measure reply rate, then 5–8.
- Stop/continue rule: **if fewer than 2 of the first 4 reply within 72h, stop and rewrite the offer** —
the problem is the pitch, not the audience.
- Log every reply in this file under a `## Replies` section with date + tier answer.

---

## Success criteria

| Metric | Baseline (verified) | Target | Deadline |
|---|---|---|---|
| MRR | **$0** | > $0 (first dollar) | 14 days after Stripe unblock |
| Founding Member replies | 0 | ≥ 4 of 8 | 7 days after send |
| Stated willingness to pay | unknown | ≥ 2 say Pro or Team | 7 days after send |

Do not report a metric here as moved without a Stripe dashboard or GitHub thread link proving it.
87 changes: 87 additions & 0 deletions docs/data-safety-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Data Safety Audit — Email Address (Play rejection versionCode 142)

App: **OpenCode: AI Coding Agent** · package `cc.agentlabs.opencode`
Rejection: Google Play rejected update **versionCode 142** for *"Invalid Data safety form"* — an **Email Address** transmitted off-device that is not declared in the Data safety form.

---

## 1. Which commit == versionCode 142

**versionCode is assigned remotely by EAS, not committed in git.** `eas.json` sets:

```json
"cli": { "appVersionSource": "remote" }
```

With `appVersionSource: "remote"`, Expo/EAS auto-increments and owns the Android `versionCode` on its build servers. The values committed in the tree (`android/app/build.gradle` → `versionCode 34`, `app.json` → `versionCode 37`) are **stale and ignored for production app-bundle builds**. `git log`/`git tag` top out at versionCode 39 (tag `v0.4.12`, SHA `3ac7eb7`); **142 never appears anywhere in git history**, tags, `app.json`, `app.config.*`, or `eas.json`. EAS CLI is not installed on this machine, so the exact build→SHA map could not be pulled from `eas build:list`.

**Consequence for this audit:** email egress is a *property of the app source*, not of a specific `versionCode`. It does not vary between builds around 142. The audit was therefore performed at the authoritative current source: **`origin/main` @ `6c103ac`** (fetched fresh). This is the code that produces the production bundle and is the correct target for the Data-safety declaration.

Relevant already-merged history:
- `d6e24e9` — *fix(compliance): disclose email collection in Play Data Safety* (PR #146, closes #143). This is the prior fix for **this same rejection** (Play rejected `cc.agentlabs.opencode` on 2026-07-22 for undisclosed Email Address). It updated the listing/privacy docs to declare the waitlist email → Brevo. versionCode 142 is the build that triggered/carried this rejection cycle.

> Audit revision: **`origin/main` @ 6c103ac** (read via `git show <rev>:<path>`; working tree kept clean).

---

## 2. Email egress paths found

Grepped the entire app+src tree at `origin/main` for `email|setUser|identify|posthog|amplitude|analytics|sentry|revenuecat|Purchases.logIn|segment` and read every hit. **Exactly one** path sends an email off-device.

| Source (file:line / SDK) | Data type | Where it goes | Purpose | Optional or Required |
|---|---|---|---|---|
| `app/connection/add.tsx:217` → `src/lib/waitlist.ts:submitWaitlistSignup` (POST `https://opencode.agentlabs.cc/api/beta-signup`) | Email Address | First-party beta-signup API → **Brevo** (email marketing/CRM) | "OpenCode Connect" waitlist signup (account/contact for a future hosted product) | **Optional** — user only types an email into a dismissible waitlist card and taps submit |
| `app/connection/add.tsx:227` → `src/lib/waitlist.ts:buildWaitlistMailtoUrl` (mailto fallback) | Email Address | `mailto:support@agentlabs.cc` (user's own mail client) | Fallback for the same waitlist when the API is unreachable | Optional — user-initiated send from their own mail app; not a developer network collection |

### Paths that do NOT transmit email (ruled out)
- **Sentry** (`src/lib/sentry.ts`): `sendDefaultPii: false`; no `Sentry.setUser`/email anywhere; `beforeSend` → `scrubEvent` and `redactObject` strip `url/host/hostname/authorization/token/password/username/...`; console breadcrumbs dropped. **No email, no PII.** Gated behind first-launch consent (`TelemetryConsentModal`).
- **PostHog** (`src/lib/analytics.ts`): self-instantiated, no autocapture, **no `identify()`/`email`/`setUser`**; random anonymous distinct ID + coarse event props only. Gated behind the same consent.
- **Connection `username`** (`src/lib/auth.ts`): a Basic-auth username for the user's **own** self-hosted OpenCode server (defaults to `"opencode"`). Sent only to the host the user typed, never to a developer/third-party endpoint. Not an email, not developer-collected PII.
- **No** RevenueCat / `Purchases.logIn`, **no** Firebase Auth / `signInWith*`, **no** Amplitude/Segment, **no** other `fetch`/POST body containing an email.

**What Play most likely detected on 142:** the waitlist `POST … {email} → Brevo` (the only real off-device email), which was undeclared at the time. (Not Sentry/PostHog auto-capture — those carry no email.)

---

## 3. Exact Play Console Data safety answers — Email Address

Data type: **Personal info → Email address**

- **Collected?** **Yes** (the app sends it off-device to the developer's server / Brevo).
- **Shared?** **Yes** — shared with **Brevo** (Sendinblue), a third-party CRM/email processor. (Play "shared" = transferred to a third party.)
- **Processed ephemerally?** **No** — Brevo persists it on a contact list.
- **Required or optional?** **Optional** — the user chooses to enter an email in the waitlist card; the app is fully usable without it.
- **Purposes** (check exactly these Play boxes):
- ✅ **Account management** (waitlist / future-product account)
- ✅ **Developer communications** (product/waitlist updates via Brevo)
- ⬜ App functionality — *not* required for the app to work
- ⬜ Analytics · ⬜ Fraud prevention/security/compliance · ⬜ Advertising/marketing · ⬜ Personalization
- *(If Brevo is used to send promotional emails, additionally check **Advertising or marketing**. Only check it if that is actually true.)*
- **Encrypted in transit?** **Yes** — HTTPS (`https://opencode.agentlabs.cc/...`).
- **Deletion mechanism?** **Yes** — user can request deletion by emailing `support@agentlabs.cc`; must be documented in the privacy policy (already added in §3c / third-party-services of `distribution/privacy-policy.md` via PR #146).

---

## 4. Recommendation — declare vs remove (per source)

| Source | Recommendation | Why |
|---|---|---|
| **Waitlist → Brevo** (`waitlist.ts`) | **DECLARE** (Email = Collected + Shared/Brevo, Optional, Account management + Developer communications) | This is an intentional, user-initiated, optional feature with a real business purpose. Declaring it is correct and low-risk. PR #146 already wrote the matching privacy-policy + listing copy; the fix is simply to make the **Play Console Data safety form** match §3 above and resubmit. |
| Sentry / PostHog / auth username | **No declaration needed for Email** | No email leaves the device via these; already handled correctly. |

**Headline recommendation: DECLARE the email (do not remove the waitlist).** The single real egress is an optional, opt-in waitlist that forwards email to Brevo for account/communications — a legitimate, disclosed use. Removing it would delete a working growth feature for no compliance benefit. Fill the Play Console Data safety form for **Email Address** exactly as in §3 (Collected: yes; Shared: yes → Brevo; Optional; Purposes: Account management + Developer communications; Encrypted in transit: yes; deletion via support@agentlabs.cc) and resubmit.

*Optional hardening (future PR, not made here):* if the waitlist is not a priority, an alternative is to **remove** the API POST and keep only the `mailto:` fallback (`buildWaitlistMailtoUrl`) — a user-initiated send from their own mail client is not a developer network collection, which would let you answer Email = **Not collected**. This is a product decision, not a compliance requirement; declaring is the cleaner, recommended path.

---

## Appendix — how the audit was run

```
git fetch origin --tags --prune
git grep -n -i -E "email|setUser|identify|posthog|amplitude|analytics|sentry|revenuecat|Purchases.logIn|segment" origin/main -- 'src/**' 'app/**' '*.ts' '*.tsx' '*.js'
git show origin/main:src/lib/waitlist.ts # confirmed POST → opencode.agentlabs.cc/api/beta-signup → Brevo
git show origin/main:src/lib/sentry.ts # sendDefaultPii:false, scrubEvent, no setUser/email
git show origin/main:src/lib/analytics.ts # PostHog, no identify/email, anonymous ID only
git show origin/main:src/lib/auth.ts # username = self-hosted server Basic-auth, not email
```