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 @@ +

{{ __('app.apps.config') }} ({{ __('app.optional') }}) @include('items.enable')

+
+
+ + {!! Form::text('config[override_url]', null, array('placeholder' => __('app.apps.override'), 'id' => 'override_url', 'class' => 'form-control')) !!} +
+
+ + {!! Form::text('config[access_token]', null, array('placeholder' => __('app.apps.apikey'), 'data-config' => 'access_token', 'class' => 'form-control config-item')) !!} +
+
+ +
+
diff --git a/CashPilot/livestats.blade.php b/CashPilot/livestats.blade.php new file mode 100644 index 0000000000..e1f2f0488a --- /dev/null +++ b/CashPilot/livestats.blade.php @@ -0,0 +1,10 @@ + From 4dc18d92d6a0e78792dee45bda0225b746c0c735 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 10:56:33 +0200 Subject: [PATCH 2/2] Use the transparent mark for the tile icon The first submission used a square icon with its own dark background plate. Their image guidance asks for transparent backgrounds, and a plate fights the tile colour Heimdall paints behind it. This is the same brand mark with no plate, and the disc now fills the frame edge to edge rather than sitting in 32px of padding. --- CashPilot/cashpilot.svg | 80 ++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/CashPilot/cashpilot.svg b/CashPilot/cashpilot.svg index 044782ca0d..17a7bb7967 100644 --- a/CashPilot/cashpilot.svg +++ b/CashPilot/cashpilot.svg @@ -1,42 +1,50 @@ - + + - - - - + + + + + - - - - - - - - - + + + + + + + - - - - - - - - - - - + + + + + + + + - - - - - - - - - -