From ab1d7db90a73137cf5e77d2b55f2db2e7294e00c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Fern=C3=A1ndez?= <9169332+GeiserX@users.noreply.github.com> Date: Fri, 7 Aug 2026 02:27:51 +0200 Subject: [PATCH 1/2] New app: CashPilot (enhanced) Self-hosted passive income orchestrator. GPL-3.0. Enhanced tile shows total earnings and the number of running services, read from GET /api/earnings/summary with a Bearer token. CashPilot reports 'not measured' and 'measured zero' as different states, so livestats() maps both of its unknown cases to an em dash rather than a figure: has_readings is false on a fresh install, and active_services is null when the count could not be taken. --- CashPilot/CashPilot.php | 58 +++++++++++++++++++++++++++++++++++ CashPilot/app.json | 10 ++++++ CashPilot/cashpilot.svg | 42 +++++++++++++++++++++++++ CashPilot/config.blade.php | 14 +++++++++ CashPilot/livestats.blade.php | 10 ++++++ 5 files changed, 134 insertions(+) create mode 100644 CashPilot/CashPilot.php create mode 100644 CashPilot/app.json create mode 100644 CashPilot/cashpilot.svg create mode 100644 CashPilot/config.blade.php create mode 100644 CashPilot/livestats.blade.php diff --git a/CashPilot/CashPilot.php b/CashPilot/CashPilot.php new file mode 100644 index 0000000000..7f61b660f2 --- /dev/null +++ b/CashPilot/CashPilot.php @@ -0,0 +1,58 @@ +url('api/earnings/summary'), $this->attrs()); + echo $test->status; + } + + public function livestats() + { + $status = 'inactive'; + $data = []; + + $res = parent::execute($this->url('api/earnings/summary'), $this->attrs()); + $details = json_decode($res->getBody()); + + if ($details) { + $status = 'active'; + + // CashPilot separates "nothing has been read yet" from "read, and it + // is zero". has_readings is false on a fresh install and on one whose + // collection has stopped, so a figure there would state a + // measurement that was never taken. + $data['earnings'] = (isset($details->has_readings) && $details->has_readings) + ? '$' . number_format((float) ($details->total_adjusted ?? 0), 2) + : '—'; + + // active_services is null when the count could not be taken. Showing + // 0 would read as "nothing is running" while containers are running. + $data['running'] = (isset($details->active_services) && $details->active_services !== null) + ? $details->active_services + : '—'; + } + + return parent::getLiveStats($status, $data); + } + + public function url($endpoint) + { + return parent::normaliseurl($this->config->url) . $endpoint; + } + + public function attrs() + { + return [ + 'headers' => [ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $this->config->access_token, + ], + ]; + } +} diff --git a/CashPilot/app.json b/CashPilot/app.json new file mode 100644 index 0000000000..b439e74f2b --- /dev/null +++ b/CashPilot/app.json @@ -0,0 +1,10 @@ +{ + "appid": "5b7c085bf60518c8e7261473688a98200e60847b", + "name": "CashPilot", + "website": "https://github.com/GeiserX/CashPilot", + "license": "GNU General Public License v3.0 only", + "description": "Self-hosted passive income orchestrator. Deploy, manage and monitor bandwidth-sharing, DePIN, storage and GPU compute containers from a single web dashboard, with earnings tracked across 49 services.", + "enhanced": true, + "tile_background": "dark", + "icon": "cashpilot.svg" +} diff --git a/CashPilot/cashpilot.svg b/CashPilot/cashpilot.svg new file mode 100644 index 0000000000..044782ca0d --- /dev/null +++ b/CashPilot/cashpilot.svg @@ -0,0 +1,42 @@ + diff --git a/CashPilot/config.blade.php b/CashPilot/config.blade.php new file mode 100644 index 0000000000..4f5a6c9c70 --- /dev/null +++ b/CashPilot/config.blade.php @@ -0,0 +1,14 @@ +