|
Some checks are pending
* Bump to v1.0.10 Two user-facing changes since 1.0.9, both already on main: - Crash reporting in the FOSS build is opt-in and off by default, with a first-run consent sheet and a switch in Settings > Support (#546, #735). The google/Play distribution is deliberately unchanged. - The security-check interval gains `Never`, which stops the periodic work AND the opportunistic run TunnelForegroundService enqueues on connect -- the only two callers of the crt.sh / Certspotter fetchers (#740, #741). changelogs/11.txt already existed (written by #735) and covered only the first. Rewritten to cover both, 493 bytes -- under the 500-char cap fdroid lint enforces. Verified against the built APK rather than asserted: an assembleRelease reports versionCode 11 in output-metadata.json, and the release.yml gate replayed against it passes with 405 non-whitespace bytes, so #607's whitespace loophole is not what is carrying it. The `Never` option makes a scheduled outbound request optional rather than absent, so two claims that read as absolute needed adjusting: - fastlane full_description.txt gains a Certificate monitoring bullet. The existing "Private by design" bullet is left as-is, deliberately. - docs/user/privacy.md dropped "no background upload" -- a scheduled CT query contradicts it. Replaced with the narrower true claim (no analytics, no telemetry, nothing about you uploaded) plus a paragraph naming crt.sh / Certspotter and the Settings > Security control. README.md and the welcome string are untouched: the README claim is about user data and stays true, and the welcome string is a tagline, not a claim. Two changes to fdroid/com.rousecontext.yml beyond the version fields: - An app-level AntiFeatures: TetheredNet block. BuildConfig.RELAY_HOST is fixed at build time and F-Droid builds bare, so the shipped APK talks to relay.rousecontext.com only. Declared rather than left for a reviewer to find. Placement and indentation checked against a live fdroiddata recipe (app.myzel394.locus.yml), not inferred. - The `Description:` block is removed. The submitted copy (fdroiddata!42096) has no Description -- the maintainer asked for HTML, and fastlane's full_description.txt supplies it. Keeping a second copy of the prose in the template is how the listing drifts from the app, which is the shape of #737. A comment records where the text now lives. Stale `v1.0.8` references in the Builds comments (tags that exist now) refreshed to v1.0.10 while in there. No tag is pushed. Tagging triggers release.yml and publishes signed artifacts; that is the owner's separate step after this merges. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YYSPuh9bZEJkrzvnEpY1QK * Close the second exhaustive-network claim in privacy.md The brief named one line to fix, but "The only network calls are to the relay ... and to Firebase Cloud Messaging" is the same claim in a different section, and it is false in exactly the same way: it enumerates outbound destinations exhaustively and omits crt.sh / Certspotter. Fixing the first line and leaving this one would have left the doc contradicting itself two sections apart. Now non-exhaustive and conditional ("while certificate monitoring is switched on"), pointing back at the paragraph that names the hosts rather than repeating them. Scoped to the CT claim only. This section also says FCM where the FOSS build uses UnifiedPush -- a real, separate divergence that predates this release and is not what this bump changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YYSPuh9bZEJkrzvnEpY1QK * Pin the version in screenshot fixtures so goldens survive a release The 1.0.10 bump turned four goldens red: settingsCrashReports{On,Off}{Light,Dark} (12 failures = 4 goldens x 3 retries). SettingsState.versionName defaults to BuildConfig.VERSION_NAME, SettingsScreen renders it in the About row, and those four captures scroll past it. "Version 1.0.9" became "Version 1.0.10" and the pixels moved. Measured, not inferred: the golden-vs-actual diff is a single 320x33 band at the version row in all four, and nothing else. This is the first bump since those goldens existed -- #735 merged the day before -- which is why 1.0.9 did not hit it. Re-recording alone would have been the wrong fix. It clears the board and re-arms the trap: a golden that embeds the version number fails once per release, forever, and whoever hits it next gets no signal that it is expected. So the version is pinned to a synthetic literal instead, the same way the test JVM pins user.timezone=UTC (#633) so goldens do not depend on the recorder's environment. BuildConfig.VERSION_NAME is environment in exactly that sense. Pinned at all eight SettingsState fixtures in the screenshots package, not just the four that move today, so a capture that later scrolls further is already safe. The crash-report fixture moves to a file-level `internal fun` so the guard can assert against the real fixture rather than a copy of it. The default itself is deliberately NOT changed. Production never assigns versionName -- the default is the only thing that puts a version on the About row in the shipped app -- so changing it would blank a real screen to fix a test. A third assertion holds that default in place. ScreenshotVersionPinTest carries the guard, and it is not vacuous. Negative control: dropping `versionName = SCREENSHOT_VERSION_NAME` from the fixture fails it with "expected 0.0.0-screenshot but was 1.0.10" and a message naming the goldens at risk. It also asserts the pin is never equal to BuildConfig.VERSION_NAME, which is the assertion that survives the tempting wrong fix -- setting the pin to the current release looks like a pin, passes that day, and silently restores the breakage. Proof the pin works, not just that today is green: with versionName temporarily set to 99.99.99-pinproof, :app:verifyRoborazziDebug passes and check-roborazzi-coverage.sh reports 139 distinct goldens compared, so the green is a real comparison rather than the compared-zero state #628 describes. Only four PNGs are rewritten, each differing from its committed version by that one band and nothing else. Golden counts are unchanged: 139 app, 40 notifications. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YYSPuh9bZEJkrzvnEpY1QK --------- Co-authored-by: Monkopedia <monkopedia@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| .claude | ||
| .github | ||
| .signing | ||
| api | ||
| app | ||
| config/detekt | ||
| core | ||
| deploy | ||
| device-tests | ||
| docs | ||
| e2e | ||
| fastlane/metadata/android/en-US | ||
| fdroid | ||
| gradle | ||
| integrations | ||
| notifications | ||
| relay | ||
| scripts | ||
| work | ||
| .editorconfig | ||
| .gitignore | ||
| BLOCKERS.md | ||
| build.gradle.kts | ||
| glama.json | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| last_session | ||
| LICENSE | ||
| README.md | ||
| settings.gradle.kts | ||
Rouse Context
Your phone has context that AI doesn't — your health data, your notifications, your app usage. Rouse Context makes that context available to AI assistants on demand, without ever syncing your data to the cloud.
It's an Android app that turns your phone into an MCP server. AI clients like Claude connect to a URL, your phone wakes up, and a direct encrypted session is established. The AI asks for what it needs, your phone responds, and then it goes back to sleep. Your data never leaves your device except through that live session.
Status: v1.0 released. Available on GitHub Releases. For Google Play beta access, email
beta@rousecontext.com. Works with any MCP-compatible client.
Getting Started
- Install — download the APK from GitHub Releases and sideload it (you may need to enable "Install from unknown sources" in your Android settings). Or email
beta@rousecontext.comfor Google Play beta access. Two builds ship per release — a Firebase-free FOSS APK (-foss.apk) and a Google APK (-google.apk); seedocs/distribution.mdfor which to pick and the F-Droid story. - Onboard — open the app, tap Get Started, grant notification permissions. The app registers with the relay and provisions your device's TLS certificates (~10 seconds).
- Enable an integration — tap Add Integration on the home screen. Pick one (e.g. Health Connect), grant the requested permissions, and you'll get a URL like
https://brave-health.abc123.rousecontext.com/mcp. - Connect your AI client — paste that URL into Claude (Settings → Connectors → Add custom connector) or any MCP client. The first connection triggers an OAuth approval on your phone — tap Approve.
- Use it — ask the AI about your health data, send yourself a notification, check your screen time. Every tool call is logged in the app's Audit tab.
How It Works
AI Client ──TLS──> Relay (SNI passthrough) ──mux WebSocket (mTLS)──> Your Phone
- You enable an integration (e.g. Health Connect) and get a URL like
https://brave-health.abc123.rousecontext.com/mcp(the integration is identified by the hostname prefix; the path is always/mcp) - Add that URL to Claude or any MCP client
- When the client connects, the relay wakes your phone via FCM push
- Your phone connects back through a mTLS WebSocket, and the relay splices the two TLS streams together
- The AI client talks directly to your phone over end-to-end encrypted TLS — the relay never sees the plaintext
Sessions are ephemeral. The phone goes back to sleep when the client disconnects.
Integrations
Each integration is independently enabled and gets its own MCP endpoint with OAuth authorization (PKCE). 22 tools across 4 integrations:
Health Connect (3 tools)
Query Android Health Connect data across 36 record types in 7 categories (activity, body, sleep, vitals, nutrition, reproductive, mindfulness).
| Tool | Description |
|---|---|
list_record_types |
List Health Connect record types with permission status |
query_health_data |
Query records by type and time range |
get_health_summary |
Health summary across permitted types for a period |
Notifications (5 tools)
Read, search, and interact with device notifications.
| Tool | Description |
|---|---|
list_active_notifications |
List posted notifications |
search_notification_history |
Search notification history by text, package, or time range |
get_notification_stats |
Notification counts, top apps, and busiest hour for a period |
perform_notification_action |
Invoke an action button on an active notification |
dismiss_notification |
Dismiss an active notification by key |
Outreach (10 tools)
Let AI take actions on the device — launch apps, open links, send notifications, manage DND.
| Tool | Description |
|---|---|
launch_app |
Launch an installed app by package name |
open_link |
Open an http/https URL in the default app |
copy_to_clipboard |
Copy text to the clipboard |
send_notification |
Post a notification with optional action buttons and tap-to-open URL |
list_installed_apps |
List installed apps |
create_notification_channel |
Create a notification channel |
list_notification_channels |
List AI-created notification channels |
delete_notification_channel |
Delete an AI-created notification channel |
get_dnd_state |
Get Do Not Disturb state |
set_dnd_state |
Set Do Not Disturb state |
Usage Stats (4 tools)
App usage patterns and screen time analytics.
| Tool | Description |
|---|---|
get_usage_summary |
Screen time totals and top apps for a period |
get_app_usage |
Per-day usage for one app |
get_usage_events |
Raw app foreground/background events over a range |
compare_usage |
Compare screen time between two periods; biggest deltas first |
Architecture
Android App
| Module | Purpose |
|---|---|
:app |
Compose UI, Koin DI, navigation, theming |
:core:tunnel |
Mux protocol, WebSocket client, TLS accept, CertificateStore |
:core:mcp |
MCP session routing, HTTP server, OAuth (device code + auth code + PKCE), token management |
:core:bridge |
Bridges tunnel mux streams to MCP sessions via TLS |
:api |
McpIntegration interface, IntegrationStateStore |
:integrations |
MCP providers: Health Connect, outreach (calls/SMS/DND), usage stats, notification capture |
:notifications |
Cross-cutting notification infrastructure, audit persistence (Room) |
:work |
Foreground service, FCM receiver, WorkManager |
:core:testfixtures |
Shared fixtures for integration-tier tests that boot the real relay binary |
Relay Server (relay/)
Rust binary on a small VPS. Handles:
- TLS passthrough — routes AI client connections to devices via SNI, never terminates inner TLS
- Mux WebSocket — multiplexes multiple client sessions over one device connection
- FCM wakeup — sends push notifications to wake sleeping devices
- ACME certs — issues per-device TLS certs via Google Trust Services DNS-01 (Cloudflare API); Let's Encrypt also supported
- Bot protection — secret URL prefix validated before waking device, plus FCM throttle and IP rate limiting
Security
- End-to-end encrypted — TLS terminates on your phone. The relay does SNI passthrough only.
- No cloud sync — data is read from on-device sources and served live. Nothing stored remotely.
- Per-device ACME certs — per-device certs via Google Trust Services (DNS-01), private key in Android Keystore (hardware-backed).
- mTLS device auth — relay authenticates the device by client certificate before allowing connections.
- Secret URL prefix — each device URL includes a rotatable per-integration secret (
brave-health.abc123.rousecontext.com, wherebrave-healthis{adjective}-{integrationId}). Bots that discover the device subdomain can't wake it without the secret. - OAuth per-client — each AI client must be authorized via on-device approval before accessing tools.
- Audit trail — every tool invocation is logged locally with arguments, response, and duration.
Building
Android
./gradlew assembleDebug
Requires Android SDK (compileSdk 36, targetSdk 35, minSdk 24). Build requires JDK 21 (JAVA_HOME=/usr/lib/jvm/java-21-openjdk).
Coverage report
./gradlew koverHtmlReport
Aggregates line + branch coverage across every module's unit tests and the
:core:tunnel:integrationTest tier (real relay subprocess). HTML lands in
build/reports/kover/html/index.html. CI publishes the same report as a
test-coverage artifact plus a per-module summary on each PR.
Relay
cd relay
cargo build --release
End-to-end tests
The e2e tests drive a real device over adb via a (typically LAN-local) host
running adb. The adb.host system property is required and defaults to empty
— the :e2e:e2eTest task fails fast if unset. Device serial is optional and
only needed if multiple devices are connected to that host.
./gradlew :e2e:e2eTest \
-Dadb.host=<your-dev-host> \
-Dadb.serial=<your-device-serial>
The :device-tests module has a separate runner that builds an APK pointed at a
locally-running relay. It also requires a LAN IP reachable from the device:
./gradlew :device-tests:test -Dlan.ip=<your-lan-ip>
Without lan.ip, the device-tests skip cleanly via JUnit assumptions.
Status
v1.0 released. 22 tools across 4 integrations, cold-start wake in ~3 seconds, end-to-end encrypted. See docs/design/ for detailed design documents and privacy policy.
License
Licensed under the Apache License, Version 2.0. See LICENSE for details.