Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .ghost-portfolio.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
description: "Android companion app for CashPilot — monitors passive income apps running on your phone without root."
personalNote: "CashPilot manages Docker containers on servers, but some passive income apps only run on Android. This agent detects which apps are installed and active using three complementary APIs — no root needed — and reports back to the fleet dashboard."
keyFeatures:
- "Detects 11 passive income apps via NotificationListener, NetworkStats, and UsageStats"
- "Detects 10 passive income apps via NotificationListener, NetworkStats, and UsageStats"
- "Periodic heartbeats to CashPilot server for unified fleet monitoring"
- "No root required — uses standard Android APIs (API 26+)"
techStack: "Kotlin, Jetpack Compose, Ktor, Material 3, WorkManager"
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Removed

- **Uprock is no longer watched on Android.** Google Play Protect removes Uprock's own app from phones, so a Play-listed CashPilot that sends people to install it would put the app's Play review at risk. The app now watches 10 apps. CashPilot web and Desktop keep Uprock.

### Changed

- **Heartbeats run every 15 minutes on WorkManager instead of every 30 seconds from a foreground service.** The always-on special-use foreground service and its permanent notification are gone, and so is the restart-on-boot receiver: WorkManager keeps the schedule across restarts. The app also reports once when it is opened and once when the server URL or key changes. The last report and the last earnings figure are kept on disk, so the dashboard still shows them after Android stops the app between runs, and it now says "Last report 6 min ago · sent every 15 min" instead of implying live monitoring.
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# CLAUDE.md — CashPilot Android

## Overview
Android monitoring agent for CashPilot that tracks passive income apps (EarnApp, IPRoyal Pawns, MystNodes, Traffmonetizer, Bytelixir, ByteBenefit, Grass, Titan Network, Nodle Cash, Uprock, Wipter) without root, reporting their status to the CashPilot fleet dashboard via periodic heartbeats.
Android monitoring agent for CashPilot that tracks passive income apps (EarnApp, IPRoyal Pawns, MystNodes, Traffmonetizer, Bytelixir, ByteBenefit, Grass, Titan Network, Nodle Cash, Wipter) without root, reporting their status to the CashPilot fleet dashboard via periodic heartbeats.

## Tech Stack
- Kotlin 2.1+
Expand Down Expand Up @@ -103,7 +103,7 @@ POST to `{serverUrl}/api/workers/heartbeat` with bearer auth (fleet API key via

## Known Apps

11 passive income apps with verified Android package names defined in `KnownApps.kt`. All verified via `adb shell pm list packages`.
10 passive income apps with verified Android package names defined in `KnownApps.kt`. All verified via `adb shell pm list packages`.

When adding new apps:
1. **Verify exact package name** via `adb shell pm list packages | grep -i <name>` — Play Store URLs are unreliable
Expand Down
6 changes: 3 additions & 3 deletions PUBLISHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ issue on its repository.
- **Closed testing first.** A personal developer account created after
2023-11-13 must run a closed test with at least 12 testers for 14 days before
production. Confirm the current numbers in Play Console.
- **3 of the 11 monitored apps are not on Play** (Traffmonetizer, ByteBenefit,
Uprock return 404 for their package ids). Linking to apps outside Play from a
Play listing invites questions.
- **2 of the 10 monitored apps are not on Play** (Traffmonetizer and ByteBenefit
return 404 for their package ids). Linking to apps outside Play from a Play
listing invites questions.
- **Android developer verification.** Google started requiring verified
developer identities for sideloaded apps on 2026-09-30 in Brazil, Indonesia,
Singapore and Thailand, and plans it globally in 2027. That affects F-Droid,
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ CashPilot Android is the Android companion app for [CashPilot](https://github.co

## Features

- Detects 11 passive income apps: EarnApp, IPRoyal Pawns, MystNodes, Traffmonetizer, Bytelixir, ByteBenefit, Grass, Titan Network, Nodle Cash, Uprock, Wipter
- Detects 10 passive income apps: EarnApp, IPRoyal Pawns, MystNodes, Traffmonetizer, Bytelixir, ByteBenefit, Grass, Titan Network, Nodle Cash, Wipter
- Instant detection from each app's ongoing notification (NotificationListenerService)
- Confirms activity from per-app network traffic and recent foreground use (NetworkStatsManager, UsageStatsManager)
- Periodic heartbeats to your server (`POST /api/workers/heartbeat`), so phones appear next to Docker workers in the fleet view
Expand Down
1 change: 0 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
<package android:name="io.getgrass.www" />
<package android:name="com.titan_network_vip.titan_app" />
<package android:name="io.nodle.cash" />
<package android:name="com.uprock.mining" />
<package android:name="com.wipter.app" />
</queries>

Expand Down
2 changes: 0 additions & 2 deletions app/src/main/java/com/cashpilot/android/model/MonitoredApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ object KnownApps {
MonitoredApp("titan", "com.titan_network_vip.titan_app", "Titan Network",
referralUrl = "https://edge.titannet.info/signup?inviteCode=2GKKJ495"),
MonitoredApp("nodle", "io.nodle.cash", "Nodle Cash"),
MonitoredApp("uprock", "com.uprock.mining", "Uprock",
referralUrl = "https://link.uprock.com/i/33e8492e"),
MonitoredApp("wipter", "com.wipter.app", "Wipter"),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,19 +149,19 @@ class AppPresentationTest {
// grid reshuffles under the user's thumb on every 30-second refresh.
val once = AppPresentation.sortForDashboard(
listOf(
info(AppState.STOPPED, "uprock", "Uprock"),
info(AppState.STOPPED, "wipter", "Wipter"),
info(AppState.STOPPED, "earnapp", "EarnApp"),
info(AppState.STOPPED, "grass", "Grass"),
),
)
val again = AppPresentation.sortForDashboard(
listOf(
info(AppState.STOPPED, "grass", "Grass"),
info(AppState.STOPPED, "uprock", "Uprock"),
info(AppState.STOPPED, "wipter", "Wipter"),
info(AppState.STOPPED, "earnapp", "EarnApp"),
),
)
assertEquals(listOf("earnapp", "grass", "uprock"), once.map { it.app.slug })
assertEquals(listOf("earnapp", "grass", "wipter"), once.map { it.app.slug })
assertEquals(once.map { it.app.slug }, again.map { it.app.slug })
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class EarningsPresentationTest {
// Some platforms unread is normal; the total covers the ones that were.
assertEquals(
Mode.FIGURE,
EarningsPresentation.mode(earnings(3.0, without = listOf("titan", "uprock")), serverConfigured = true),
EarningsPresentation.mode(earnings(3.0, without = listOf("titan", "wipter")), serverConfigured = true),
)
}

Expand Down
5 changes: 2 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Everything the app stores, where you set it, and what it does. There are no conf
| CashPilot Server URL | Setup step 1, or Settings, Server Connection | `server_url` | empty | Base URL of your CashPilot server. Heartbeats go to `<url>/api/workers/heartbeat`; a trailing `/` is ignored. Empty means standalone: no heartbeats, no earnings. |
| Fleet API Key | Setup step 1, or Settings, Server Connection | `api_key` | empty | The server's `CASHPILOT_API_KEY`. Used only until the phone has its own key (next section). |
| Per-worker key | Nothing to set: the server issues it | `worker_key` | empty | This phone's own key, received on its first heartbeat and used for every heartbeat after that. |
| Monitored apps | Settings, Monitored Apps | `enabled_slugs` | all 11 on | Which apps the dashboard and the heartbeat include. A switched-off app shows as **Disabled** and is left out of the heartbeat. |
| Monitored apps | Settings, Monitored Apps | `enabled_slugs` | all 10 on | Which apps the dashboard and the heartbeat include. A switched-off app shows as **Disabled** and is left out of the heartbeat. |
| Heartbeat interval | No control in the app | `heartbeat_interval` | 30 seconds | Stored but no longer used. Heartbeats run every 15 minutes, the shortest period Android's WorkManager allows. |
| Setup completed | Finishing or skipping the setup screen | `setup_completed` | false | Whether the setup screen is shown at launch. An install that already had a URL and a key before this setting existed counts as set up. |

Expand Down Expand Up @@ -48,7 +48,7 @@ The server side of this is on CashPilot's [fleet page](https://geiserx.github.io

## Monitored apps

The app knows these 11 apps. The slug is the name the CashPilot server uses for the service; the package name is what the phone looks for.
The app knows these 10 apps. The slug is the name the CashPilot server uses for the service; the package name is what the phone looks for.

| App | Slug | Package |
|---|---|---|
Expand All @@ -61,7 +61,6 @@ The app knows these 11 apps. The slug is the name the CashPilot server uses for
| Grass | `grass` | `io.getgrass.www` |
| Titan Network | `titan` | `com.titan_network_vip.titan_app` |
| Nodle Cash | `nodle` | `io.nodle.cash` |
| Uprock | `uprock` | `com.uprock.mining` |
| Wipter | `wipter` | `com.wipter.app` |

Adding an app takes a new release: the list is in the code, and Android 11 and later only let the app see packages declared in its manifest.
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The main button unlocks once both accesses are granted. It reads **Continue to D

## What a working start looks like

- The dashboard lists the 11 monitored apps. Installed ones show **Running**, **Stopped** or **Can't tell**; the others show **Not installed — tap to install**. The summary at the top counts them.
- The dashboard lists the 10 monitored apps. Installed ones show **Running**, **Stopped** or **Can't tell**; the others show **Not installed — tap to install**. The summary at the top counts them.
- With a server, the dashboard's summary shows a green dot and **Last report** with its age, for example **Last report 6 min ago · sent every 15 min**, and the phone appears on the server's fleet page as its maker and model plus a short device id, for example `Google Pixel 8 (a1b2c3d4)`. The first heartbeat also enrolls the phone: the server issues it its own key ([Configuration](configuration.md#the-fleet-key-and-enrollment)).
- Without a server, the summary shows **Not paired — running standalone**. That is a supported mode, not an error.

Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ hide:

---

**CashPilot Android** tells you which of your passive income apps are actually running on your phone, without root. It watches 11 apps (EarnApp, IPRoyal Pawns, MystNodes, Grass and more) through three Android APIs. A bandwidth app that has stopped earns nothing and usually shows nothing on the phone; here it goes to the top of the list in red. Paired with a [CashPilot](https://geiserx.github.io/CashPilot/) server, the phone joins your fleet next to the Docker workers and shows what its platforms earned. Start with [Getting started](getting-started.md), then [Usage](usage.md).
**CashPilot Android** tells you which of your passive income apps are actually running on your phone, without root. It watches 10 apps (EarnApp, IPRoyal Pawns, MystNodes, Grass and more) through three Android APIs. A bandwidth app that has stopped earns nothing and usually shows nothing on the phone; here it goes to the top of the list in red. Paired with a [CashPilot](https://geiserx.github.io/CashPilot/) server, the phone joins your fleet next to the Docker workers and shows what its platforms earned. Start with [Getting started](getting-started.md), then [Usage](usage.md).

<div class="grid cards" markdown>

Expand Down Expand Up @@ -71,7 +71,7 @@ hide:
- It does not start, stop or control the apps. It watches them; restarting a stopped one is up to you.
- It does not read notification content, only whether a monitored app has an ongoing notification.
- It does not collect earnings itself. The figures come from the CashPilot server, and without one the earnings card stays empty.
- It watches the 11 apps it knows. A new app needs a new release.
- It watches the 10 apps it knows. A new app needs a new release.

## Privacy

Expand Down
2 changes: 1 addition & 1 deletion fastlane/metadata/android/en-US/full_description.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
CashPilot shows which of your passive-income and DePIN apps are installed, running and active on this phone. It needs no root.

It watches EarnApp, IPRoyal Pawns, MystNodes, Traffmonetizer, Bytelixir, ByteBenefit, Grass, Titan Network, Nodle Cash, Uprock and Wipter. For each one it shows whether the app is installed, whether it is running, and when it was last active.
It watches EarnApp, IPRoyal Pawns, MystNodes, Traffmonetizer, Bytelixir, ByteBenefit, Grass, Titan Network, Nodle Cash and Wipter. For each one it shows whether the app is installed, whether it is running, and when it was last active.

How it knows:
- Notification access: most of these apps keep a notification up while they run, so CashPilot reads it to tell that the app is alive.
Expand Down
Loading