$ man ferrule
NAME
ferrule - mobile/desktop client for an ITFlow MSP backend
DESCRIPTION
A ferrule is the metal sleeve crimped onto the end of a
cable, pencil, or umbrella to keep the strands bound and
stop them fraying.
This Ferrule does the same job for an ITFlow instance: it
binds the loose ends (REST endpoints, agent CSRF flows,
PDF exports, guest links) into one client surface without
changing what's inside.
SEE ALSO
itflow(1), flutter(1), make-coffee-first(1)
$ ls /modules
clients/ contacts/ assets/ credentials/ tickets/
invoices/ quotes/ expenses/ products/ vendors/
locations/ networks/ certificates/ software/ documents/
domains/ dashboard/ timer/
$ stat invoices/
Read full list + detail + line items
PDF download via the OS share sheet
Guest copy or open the shareable client URL
Action Make Payment form (scrapes the agent modal for CSRF)
$ stat quotes/
Read full list + detail + line items
PDF download via the OS share sheet
Guest copy or open the shareable client URL
$ stat expenses/
Read full list + detail
Create receipt upload (camera or file: jpg/png/gif/webp/pdf)
$ stat everything-else/
Read list + detail screens with linked-record tap-through
(clients <-> contacts <-> assets <-> credentials <-> tickets ...)
Tap a client on an invoice, a contact on an asset, a credential on a ticket - linked IDs render as names and behave like hyperlinks, not magic numbers.
Two surfaces of the same backend, one Dio client:
+-----------------------------+
| Flutter app |
| Riverpod 3 + GoRouter |
+--------------+--------------+
|
+-------+-------+
| Dio + cookies |
+---+-------+---+
| |
/api/v1/* JSON | | /agent/* HTML + CSRF
v v
+--------------------------------+
| Your ITFlow |
| (PHP 8, MySQL, self-hosted) |
+--------------------------------+
- REST (
/api/v1/*): clean, paginated, scoped to an API key's client. Used wherever an endpoint exists. - Agent scrape (
/agent/*): everything REST doesn't cover. CSRF harvested from the rendered modal, session retry on lapse, multipart POSTs for things likeadd_expense.
When REST doesn't expose what the app needs, the fork below ships the endpoint and Ferrule eats it for breakfast.
API additions live in BoredManCodes/itflow:
| Endpoint | Purpose |
|---|---|
GET /api/v1/quote_items/read.php |
Line items for a quote, JOINed to quotes for client scope |
GET /api/v1/tickets/replies/read.php |
Conversation replies for a ticket |
POST /api/v1/tickets/replies/create.php |
Post a new reply to a ticket (Internal or Public-no-email; status updates supported) |
All additions follow the existing validate_api_key.php + LIKE '$client_id' scoping pattern and are designed to be upstreamable.
# 1. clone
git clone https://github.com/BoredManCodes/ferrule.git
cd ferrule
# 2. config (leave Sentry blank to disable)
cp secrets.example.json secrets.json
# 3. run
flutter pub get
flutter run --dart-define-from-file=secrets.jsonRelease builds:
flutter build appbundle --dart-define-from-file=secrets.json # Play / closed testing
flutter build apk --dart-define-from-file=secrets.json # sideload
flutter build windows --dart-define-from-file=secrets.json # desktop- Enter your ITFlow base URL and API key.
- (Optional) Add agent email + password to unlock scrape-only features (Make Payment, PDF downloads, expense create).
- (Optional) Turn on biometric unlock at launch. Off by default.
- Done. Everything caches to secure storage; you won't be asked again.
Asset lookup expects assetid_customer_installdate, e.g. 000037_bordertechsolutions_0126. That's the label format my business prints, so generic QR codes won't match out of the box.
Closed-testing ships through a self-contained PowerShell + Node pipeline in scripts/:
# 1. build + upload to Play alpha as a draft
.\scripts\release-internal.ps1 -Track alpha -NoPromote
# 2. Save + Send for review (Playwright drives Chromium)
node .\scripts\promote-closed-testing.mjs `
--track <track-id> `
--release-name 1.0.2 `
--version-code 6The promote script handles the part the Android Publisher API doesn't expose: Edit release -> Next -> Save on the review page, then Send for review on Publishing overview. One command from git push to "queued for Google review". Took an afternoon to write, saves twenty minutes per release.
Internal testing? Same release-internal.ps1 without -NoPromote. scripts/promote-via-browser.mjs clicks Save and publish for an immediate roll-out.
| Layer | Bits |
|---|---|
| Framework | Flutter, Dart 3 |
| State | Riverpod 3 (async caching, autoDispose families) |
| Routing | GoRouter (typed deep links) |
| HTTP | Dio + dio_cookie_manager, html parser |
| Storage | flutter_secure_storage (keychain / Keystore) |
| Auth | local_auth for biometrics |
| Telemetry | sentry_flutter, opt-in |
| Capture | mobile_scanner, image_picker, file_picker |
| Share | share_plus, path_provider |
$ traceroute your.data
1 your.itflow.instance (the only hop)
2 * * *
3 * * *
All network traffic goes to your ITFlow instance and nowhere else, except optional Sentry crash reports if you've configured a DSN. The privacy policy is bundled as PRIVACY.md and renders in-app at Settings -> Privacy policy so it works offline and survives a GitHub outage.
$ history | grep -i easter
There's at least one. The other one is reading the source. Pull requests welcome.
Apache 2.0 - see LICENSE and NOTICE.
Unofficial client. Not affiliated with or endorsed by the ITFlow project. ITFlow itself is an independent open-source project licensed under GPL-3.0; no ITFlow source code is included in or redistributed by this client.
guest@ferrule:~$ exit
Connection to ferrule closed. Have a good one.