Releases: hyperb1iss/unifly
Release v0.9.0
Released: 2026-04-10
This release adds Site Manager cloud fleet management, a full WiFi TUI dashboard, demo mode for PII-safe screen recordings, and a comprehensive end-to-end test suite running against a real controller. The command count grows from 27 to 28, and the documentation site migrates from VitePress to Zola.
🌟 Highlights
☁️ Site Manager Cloud Fleet Commands
New cloud command group (hosts, sites, devices, isp, sdwan, switch) provides fleet-wide visibility through the Ubiquiti Site Manager API at api.ui.com. A new SiteManagerClient in unifly-api handles cursor-based pagination and cloud rate-limit responses with Retry-After parsing. The cloud switch <site> subcommand fuzzy-matches site names and persists the new target in the active cloud profile.
📡 WiFi TUI Dashboard
A dedicated WiFi screen (key 9) with four sub-tabs surfaces RF health and client experience data directly in the TUI. The Overview tab shows AP health scores, client counts, and a toggleable channel map. Clients lists wireless clients with signal strength and expandable WiFi experience detail. Neighbors displays rogue/nearby APs with band filtering and sortable columns. Roaming renders per-client roam event timelines showing AP transitions and signal changes.
🎭 Demo Mode for PII Sanitization
New Sanitizer in crates/unifly/src/sanitizer.rs intercepts all entity payloads in the TUI data bridge before rendering, replacing personal names with cute deterministic aliases (Starling, Moonbeam, Cosmo...), WAN IPs with RFC 5737 documentation addresses (198.51.100.x), SSIDs with fun replacements ("The Promised LAN"), and optionally redacting MACs and ISP names. Activated via --demo, UNIFI_DEMO=1, or the [demo] config section.
✅ End-to-End Test Suite
A UniFi controller runs in Docker simulation mode during CI, and the CLI executes against it to verify session auth, device listing, output formatting, health subsystems, observability endpoints, destructive-command guards, and error paths. Over 30 e2e tests cover devices, stats, events, WiFi, VPN, NAT, settings, and client resolution errors.
🔧 Interactive Cloud Setup Wizard
unifly config cloud-setup validates a Site Manager API key, discovers accessible consoles and sites, and writes a ready-to-use cloud profile. The wizard prompts for API key entry (or detects UNIFI_API_KEY), lets users pick a console and site, and offers keyring, env var, or plaintext storage.
⚡ Lightweight Connect for One-Shot Commands
Controller::connect_lightweight() authenticates and sets up the session but defers the full data snapshot load. System commands (info, sysinfo, backup) now use this path, avoiding unnecessary network round-trips.
☁️ Cloud & Site Manager
- New
auth_mode = "cloud"end-to-end across CLI profiles, TUI onboarding, and settings screens — cloud profiles default the controller URL tohttps://api.ui.com, force system TLS defaults, disable WebSocket and session caching, and use longer polling intervals (60s refresh, 30s poll) IntegrationClientdetects cloud platform connections and surfacesConsoleOffline/ConsoleAccessDeniederrors with actionable guidance- Cloud connector routing uses
/v1/connector/consoles/{host_id}prefix for Integration API tunneling --host-idglobal flag andUNIFI_HOST_IDenv var for CLI-level host targetingcloud ispandcloud sdwansubcommands for ISP metrics and SD-WAN configuration visibility
🖥️ TUI Improvements
- Device detail view now populates Ports, Radios, and Clients tabs with real data parsed from both Integration and Session API responses — ports show max speed, connector type, and PoE standard; radios show band, channel, width, standard, channel utilization, and TX retry rates
- Firmware upgrade action (
Ukey) with confirmation dialog in device detail - Screen key handlers now take priority over global handlers so device-specific bindings work correctly
- Navigation range extended to keys
1–9for the new WiFi screen - Notification TTL is now severity-aware — warnings and errors persist longer than informational toasts
- Sponsor button replaces the old Donate/PayPal link with GitHub Sponsors URL
🔧 CLI & Config
--themepromoted from TUI-only to a global flag shared by CLI and TUI — resolution order:--theme→UNIFLY_THEMEenv →config defaults.theme→silkcircuit-neonnat policies updatemerges only changed fields into the existing rule via the Session v2 API, eliminating the delete-and-recreate workaround- Non-interactive stdin detection — destructive commands (
device remove,client forget,backup delete) now return aNonInteractiveRequiresYeserror in CI/pipe contexts instead of blocking forever, guiding callers to pass--yes config setnormalizesauth_mode = "legacy"to"session"on write for backward compatibility- Error help text across
AuthFailed,NoCredentials,Unsupported,ProfileNotFound, andNoConfignow references bothconfig init(local) andconfig cloud-setup(Site Manager)
🐛 Bug Fixes
- Platform detection rewritten — both
/api/loginand/api/auth/loginare probed before deciding, fixing misclassification of recent standalone controllers that return 401 from the UniFi OS endpoint - NAT policy creation —
filter_typecorrected to use onlyNONE/ADDRESS_AND_PORT(controller rejectsADDRESS/PORT); interface fields now resolve Integration UUIDs to Session_idstrings;rule_indexauto-increments from existing rules instead of hardcoding 0 - API-key 401 classification —
SessionAuthenum distinguishes cookie vs API-key session clients; a rejected API key now returnsInvalidApiKeyinstead of a misleading "session expired" message - macOS config path — uses
~/.config/unifly/(XDG) instead of~/Library/Application Support/unifly/with automatic migration on first run - Tracing to stderr — CLI tracing subscriber output redirected from stdout to stderr so
warn-level messages no longer corrupt JSON/YAML pipe output - Wi-Fi observability parsing — roam events extract from nested
parameters.DEVICE_FROM.nameinstead of flat fields; WiFi experience useswlan_band; channels redesigned from per-radio rows to country-level regulatory data;format_radio_bandhandles bothstat/staandwifimanband code families - VPN IPsec SA 404 — controllers with no VPN tunnels return 404 for
stat/ipsec-sa;vpn statusnow catches this and shows an empty list - Client uplink MAC — resolution uses
ap_macfor wireless andsw_macfor wired clients regardless of wireless info presence - Session events — API-key session clients treat 404 on
stat/eventas optional-missing;events watchgated on cookie-backed session auth
♻️ Refactoring
convert.rs(2,189 lines) decomposed intoconvert/module directory with 13 per-entity submodules — no behavioral changes- Full
legacy→sessionrename across Rust modules, config keys, TUI labels, CLI error strings, and documentation —LegacyClient→SessionClient,Error::LegacyApi→Error::SessionApi,ensure_legacy_access→ensure_session_access, etc. ensure_session_accesspromoted from a private function inevents.rsto the sharedutil/access.rsmodule- CLI dispatch split into
dispatch()anddispatch_extended()to stay under clippy's cognitive-complexity threshold
📝 Documentation
- Documentation site migrated from VitePress to Zola — eliminates the Node.js build dependency; new SilkCircuit theme with dark/light modes, glassmorphism nav, modular SCSS token system, Elasticlunr search, lazy Mermaid rendering with theme-aware re-renders, and responsive three-column layout
llms.txtandllms-full.txtgenerated from Zola content at build time viadocs/scripts/gen-llms-txt.sh- Prettier configuration (
.prettierrc,.prettierignore) integrated intojustfilefor consistent non-Rust file formatting - GitHub issue templates (bug report, feature request), PR template,
CODE_OF_CONDUCT.md, andSECURITY.mdadded - AGENTS.md and SKILL.md updated with 28-command inventory, cloud auth mode, WiFi screen, and corrected auth guidance
🔧 CI/CD
- End-to-end workflow triggers on push-to-main and pull requests (was manual/weekly-only)
- E2e pipeline overlaps Docker controller startup with Rust compilation for faster runs
- AUR package publishing automated in the release pipeline via
KSXGitHub/github-actions-deploy-aur - ClawHub skill publish job runs after GitHub Release creation
- Version-files automation patches
.claude-plugin/plugin.json,.cursor-plugin/plugin.json, andskills/unifly/SKILL.mdon release
💥 Breaking Changes
auth_mode = "legacy"renamed to"session"— Existing config files withauth_mode = "legacy"are accepted as a backward-compatible alias and normalized to"session"on nextconfig setwrite. No manual migration required, but new configs should use"session".- macOS config path changed — Config and cache now resolve via XDG (
~/.config/unifly/,~/.cache/unifly/) instead of~/Library/Application Support/unifly/. Automatic migration runs on first launch; if it fails, a message instructs you to move files manually.
📋 Upgrade Notes
- If you have
auth_mode = "legacy"in your config, it will continue working but will be rewritten to"session"on next config write - macOS users: config files are auto-migrated from
~/Library/Application Support/unifly/to~/.config/unifly/on first run - Scripts using destructive commands without
--yesin non-interactive contexts (CI, pipes) will now fail with exit code 1 and a message pointing to the-yflag - Cloud profiles can be created with
unifly config cloud-setup— existing local profiles are unaffected - The ...
Release v0.8.2
Release Notes v0.8.2
Released: 2026-04-08
This release focuses on correctness and resilience across NAT policy creation, session authentication handling, and CLI output piping. Three NAT creation bugs are fixed, API-key session clients now distinguish cookie vs key auth for accurate error reporting, and a new settings command exposes all 44 site configuration sections through the CLI.
🌟 Highlights
✨ New settings Command
Full site configuration management via unifly settings with list, get, set, and export subcommands. Covers all 44 setting sections (IPS, DPI, USG, Radio AI, management, etc.) with read-modify-write semantics, sensitive field masking in table output (x_-prefixed fields show as ***), smart value parsing (booleans, integers, floats, null, strings), and bulk JSON merge via --data.
🐛 NAT Policy Creation: Three Bugs Fixed
NAT rule creation via unifly nat create was broken in several ways: filter_type sent invalid values (ADDRESS/PORT) that the controller rejected, interface IDs used Integration UUIDs instead of Session _id strings, and rule_index was hardcoded to 0 causing duplicate-index errors. All three are fixed.
🔧 API-Key Session Auth Awareness
New SessionAuth enum (Cookie vs ApiKey) on SessionClient enables the system to distinguish between cookie-backed and API-key-backed session clients. This fixes misleading "session expired" errors when an API key is rejected and gates events watch to only run with cookie-backed sessions.
🐛 Integration API Detection for Older Controllers
When API-key auth fails because the controller lacks the Integration API (older or self-hosted UNA without Settings > Integrations), unifly now surfaces a clear Unsupported error with actionable guidance instead of a raw 404.
✨ New Features
unifly settings list— List all site setting sections with field counts, enabled status, and notable value summariesunifly settings get <key>— Retrieve a setting section with automatic sensitive field masking in table output; JSON output shows full values for programmatic accessunifly settings set <key> <field> <value>— Update individual fields with type-inferred value parsing; also supports--data '{...}'for bulk JSON mergeunifly settings export— Dump all settings as raw JSON for backup or inspectionController::has_live_event_access()— New API method that checks for cookie-backed session auth, used to gate WebSocket-dependent features
🐛 Bug Fixes
- NAT
filter_typerejection —build_filter()now only emitsNONEandADDRESS_AND_PORTvalues. The controller's v2 API rejectsADDRESSandPORTalone with a deserialization error; when only an address or port is supplied, the filter usesADDRESS_AND_PORTand includes only the populated fields - NAT interface ID mismatch — New
resolve_interface_id()queriesrest/networkconfand matches onexternal_idto translate Integration UUIDs to Session_idhex strings required by the v2 NAT API. Legacy-style IDs pass through unchanged - NAT duplicate
rule_index—next_rule_index()queries existing NAT rules and usesmax(rule_index) + 1instead of hardcoding 0 - Misleading 401 errors in API-key mode —
unauthorized_error()now uses theSessionAuthenum to returnError::InvalidApiKeyfor API-key clients andError::SessionExpiredfor cookie clients, replacing a generic "session expired" message that confused API-key users - Missing Integration API yields raw 404 —
lifecycle.rsconnect paths now catch 404 from the Integration sites endpoint and surfaceCoreError::Unsupportedwith guidance to use--username/--passwordinstead - Event endpoint 404 treated gracefully — In
refresh.rs, a 404 fromstat/event(common on API-key sessions where the endpoint varies by controller) is now demoted to debug-level logging and returns an empty event list instead of a warning events watchgated on cookie auth — Theevents watchhandler now callsensure_live_event_access()instead ofensure_session_access(), preventing confusing WebSocket failures when only API-key session auth is available- CLI tracing corrupted pipe output —
init_tracing()incli.rsnow writes tostderrinstead ofstdout, so diagnostic log lines no longer breakjq,json.loads(), and other downstream consumers of-o json/-o yamloutput - Dispatch cognitive complexity lint — The 27-arm
dispatch()match was split intodispatch()anddispatch_extended()to stay under Clippy's cognitive complexity threshold of 25
🔧 API & Internal Changes
- Added
SessionAuthenum tocrates/unifly-api/src/session/client.rswithCookieandApiKeyvariants;SessionClientnow carries this field and exposes it viaauth() SessionClient::with_client()now requires aSessionAuthparameter- New controller methods:
get_all_site_settings(),get_site_setting(key),update_site_setting(key, body)insession_queries.rs - New
has_live_event_access()onControllerinsubscriptions.rs - Command dispatch split:
dispatch()handles A–N commands,dispatch_extended()handles N–Z plus unreachable arms
✅ Testing
- New unit tests for
SessionAuthunauthorized error classification (cookie →SessionExpired, API key →InvalidApiKey) - New unit tests for
has_live_event_access()vshas_session_access()with both auth types - Integration test
api_key_mode_treats_missing_event_endpoint_as_empty— verifies 404 fromstat/eventresults in empty events, not a failure - Integration test
api_key_mode_reports_unsupported_when_integration_api_missing— verifies 404 from the Integration sites endpoint producesCoreError::Unsupportedwith actionable message - Existing
session_client_test.rsupdated withsetup_api_key()helper and correctSessionAuthparameters - Settings command unit tests: value parsing (booleans, integers, floats, null, strings), sensitive field masking, notable summary truncation, safe string truncation at 24 characters
📝 Documentation & Packaging
skills/unifly/SKILL.mdupdated withsettingscommand entry (now 27 commands)skills/unifly/references/commands.mdupdated with settings gotchas section- Plugin manifests (
.claude-plugin/,.cursor-plugin/) bumped to v0.8.2 - Workspace version in
Cargo.tomlbumped to0.8.2
🙏 Contributors
Thanks to @jorgeortiz85 for contributing the NAT policy creation fixes, API-key session auth improvements, and the stderr tracing fix.
⬆️ Upgrade Notes
- No breaking changes — All fixes are backward-compatible
- If you were working around NAT creation failures by manually constructing
filter_type,interface_id, orrule_indexvalues, those workarounds can be removed - Users on older UniFi controllers without the Integration API will now get a clear error message suggesting
--username/--passwordauth instead of a confusing 404 - CI/scripting pipelines that parse
uniflystdout can now rely on clean structured output; tracing diagnostics are written to stderr
Release v0.8.1
Released: 2026-04-08
This release delivers full VPN management with 9 subcommand groups and 33 operations, Wi-Fi observability commands for diagnosing roaming and RF environments, and a codebase-wide rename of "legacy" to "session" that corrects misleading terminology around the UniFi /api/* surface. Documentation receives a ground-up overhaul across the VitePress site, agent skill, and AGENTS.md.
🌟 Highlights
🔮 Full VPN Management Surface
The vpn command expands from 2 read-only subcommands to 9 subcommand groups with 33 operations. Site-to-site tunnels, remote-access servers, WireGuard peers, VPN client profiles, active connections, magic site-to-site configs, and VPN settings all support full CRUD via --from-file JSON payloads. New vpn status and vpn health commands query live IPsec SA state and VPN subsystem health from the Session API.
📡 Wi-Fi Observability Commands
Four new commands expose Session API Wi-Fi diagnostics: wifi neighbors lists rogue and neighboring APs with signal, channel, and observer MAC. wifi channels shows per-radio regulatory channel availability. clients roams <client> surfaces a client's connection timeline (connect/disconnect/roam events). clients wifi <client> displays per-client Wi-Fi experience metrics including signal/noise, channel, protocol, and nearest neighbors.
♻️ Legacy → Session Rename
The UniFi /api/* and /v2/api/* HTTP surface is not deprecated — Ubiquiti ships new functionality there regularly. Calling it "legacy" was misleading. The entire codebase now uses Session API consistently: legacy/ module → session/, LegacyClient → SessionClient, Error::LegacyApi → Error::SessionApi, config auth_mode = "legacy" → auth_mode = "session". EntityId::Legacy is kept — it names an ID format, not the API surface.
📝 Documentation Overhaul
The VitePress documentation site receives a complete content rewrite: new library API page, AI agent skill guide, and troubleshooting FAQ. Every existing page is expanded with mermaid diagrams, corrected API signatures, fixed auth mode claims, and accurate refresh intervals. AGENTS.md grows from a brief summary into comprehensive agent-facing documentation covering architecture, conventions, and code patterns.
🔮 VPN Management
vpn site-to-site(aliass2s): CRUD for IPsec and OpenVPN site-to-site tunnels via Session APIvpn remote-access(aliasra): CRUD for L2TP/OpenVPN/WireGuard servers, plussuggest-portanddownload-configfor OpenVPNvpn clients: CRUD for configured VPN client profilesvpn connections: list, get, and restart active VPN client connectionsvpn peers(aliaspeer): CRUD for WireGuard peers scoped to a remote-access server, plussubnetsauditvpn magic-site-to-site(aliasmagic-s2s): read-only inspection of auto-configured tunnelsvpn settings: list, get, set, and patch for teleport, openvpn, magic-s2s, and peer-to-peer togglesvpn status: live IPsec security association status fromstat/ipsec-sa; returns empty list (not error) when no tunnels existvpn health: VPN subsystem health from the cachedstat/healthsnapshotvpn servers get <id>andvpn tunnels get <id>: detail views with subnet, port, peer address, IKE version- Four example JSON payloads added:
vpn-site-to-site-ipsec.json,vpn-client-openvpn.json,vpn-remote-access-wireguard.json,vpn-wireguard-peer.json
📡 Wi-Fi & Client Observability
wifi neighbors: lists rogue/neighboring APs fromstat/rogueapwith signal, channel, SSID, and observer AP MAC; supports--within <secs>,--limit, and--allflagswifi channels: per-radio regulatory channel availability fromstat/current-channelwith country code and available HT/VHT modesclients roams <client>: connection timeline from v2system-log/client-connection/{mac}with timestamp, AP, SSID, signal, and band; accepts name, hostname, IP, or MAC with auto-resolutionclients wifi <client>: Wi-Fi experience metrics from v2wifiman/{ip}including experience score, signal/noise, channel, protocol, link rates, nearest neighbors, and uplink chain- Fixed roam event parsing to extract from nested
parameters.DEVICE_FROM.namestructure instead of flat fields; added From/To/Channel columns - Fixed Wi-Fi experience parsing:
wlan_band(notband),display_namefor uplinks, nested signal array for neighbors - Fixed channel model: redesigned from per-radio rows to country-level regulatory data with per-band channel lists
- Fixed
format_radio_bandto handle bothstat/stacodes (ng/na/6e) and wifiman codes (2.4g/5g/6g) - Channel country header suppressed in JSON/YAML output (only renders for table format)
🔧 API & Auth Improvements
- 401 error disambiguation: Session HTTP 401 responses now map to
Error::InvalidApiKey(api-key mode, no cookie jar) orError::SessionExpired(session mode, jar present) instead of a generic "session expired" message that misled api-key users Error::is_not_found()expanded: recognizesSessionApi404s (HTTP 404 status prefix andapi.err.NotFoundmessage code), sovpn statusreturns an empty list instead of failing when no IPsec tunnels exist- Backwards-compatible auth mode alias:
auth_mode = "legacy"in existing config files is silently accepted;config setnormalizes it to"session"on next write - Corrected auth mode documentation: API-key mode covers most CLI commands on UniFi OS (CRUD, device commands, stats, DHCP, admin,
events list); Hybrid is only needed for live WebSocket features (events watch)
🏗️ Build & CI
- Automated version-files patching: release workflow patches
.claude-plugin/plugin.json,.claude-plugin/marketplace.json,.cursor-plugin/plugin.json, andskills/unifly/SKILL.mdautomatically on release — no more manual manifest bumps - ClawHub skill publishing: new
publish-skillCI job runsclawhub syncafter GitHub Release creation, reading version from SKILL.md frontmatter - Release workflow fix: switched version-files YAML scalar from
|to|-to strip trailing newline that causedset -eto kill the script beforegit commit - Added
prettierandprettier-checkjust recipes for formatting docs/ markdown and JSON - Satisfied newer clippy pedantic lints:
map_orreplacesmap(..).unwrap_or(..),sort_by_keywithReversereplaces comparators,Duration::from_mins(1)replacesfrom_secs(60)
📝 Documentation
- New pages:
docs/reference/library.md(Rust API usage guide),docs/guide/agents.md(AI agent skill docs),docs/troubleshooting.md(comprehensive FAQ) - AGENTS.md: expanded from brief summary into full agent-facing documentation covering dual-API architecture, Controller lifecycle, CLI/TUI module layout, testing, release workflow, and sacred rules;
CLAUDE.mdsymlinked to it - CLI reference: expanded from bare command table to full subcommand documentation for all 26 commands with every subcommand, flags, API column (I/S/H/Mixed/Local), and gotchas
- VitePress site: deep content overhaul of all pages with mermaid diagrams, code-group tabs, emoji conventions, edit links, lastUpdated timestamps, and accurate counts (26 commands, 10 screens)
- CHANGELOG.md: rewritten with proper entries for all releases from v0.1.1 through v0.8.1
- CONTRIBUTING.md: rewritten with correct
just checkgate, testing section, and AGENTS.md cross-reference - ROADMAP.md: new file documenting known gaps, near-term priorities, and wish list
- Skill documentation: restructured around operational patterns with 4 VPN example payloads, expanded auth gate matrix, and automation recipes
- Fixed 13 accuracy issues found by cross-model review: corrected library code examples, credential claims, NAT routing, refresh intervals (30s → 10s), events flag (
--withinnot--hours), and more
💥 Breaking Changes
- Config:
auth_mode = "legacy"renamed toauth_mode = "session"— Existing config files withauth_mode = "legacy"continue to work (accepted as backwards-compatible alias), butconfig setandconfig initnow write"session". Update your configs or rununifly config initto regenerate. - Rust API types renamed — If you depend on
unifly-apias a library:LegacyClient→SessionClient,Error::LegacyApi→Error::SessionApi,legacy_prefix()→session_prefix(), and allLegacy*model types →Session*.EntityId::Legacyis unchanged.
🔑 Upgrade Notes
- Update any config files using
auth_mode = "legacy"— the alias works today but"session"is the canonical value going forward - If scripting against
unifly-apiRust types, update import paths fromlegacy::tosession::and rename type references - VPN commands that previously only supported
serversandtunnelsnow have 9 subcommand groups — reviewunifly vpn --helpfor the full surface - The
wifi neighborscommand defaults to 25 results; use--allor--limitto control output size
Release v0.8.0
Release Notes v0.8.0
Released: 2026-04-05
This release adds NAT policy management, a raw API passthrough command, MFA/TOTP authentication, persistent session caching, and Windows support. It also fixes over a dozen Integration API compatibility issues across WiFi, firewall, network, and client operations — and upgrades the entire dependency tree.
🌟 Highlights
✨ NAT Policy CRUD
Full create, list, get, and delete for masquerade, source NAT, and destination NAT rules. NAT operations use the v2 Legacy API endpoint (/v2/api/site/{site}/nat) since the Integration API does not expose NAT on Network Application 10.1.x. The TUI firewall screen gains a fourth NAT sub-tab with live policy display. CLI flags cover --name, --type, --src-address, --dst-address, --translated-address, --translated-port, protocol filtering, and --from-file for JSON payloads.
✨ Raw API Passthrough
New unifly api <path> command sends GET or POST requests to arbitrary controller endpoints, bypassing typed abstractions. Supports legacy, v2, and integration path styles with automatic platform prefix handling and CSRF token injection. Useful for exploring undocumented APIs or one-off queries without building dedicated commands.
🔒️ MFA/TOTP and Session Caching
Two-step MFA authentication for Legacy API login: unifly detects the HTTP 499 MFA challenge, injects a UBIC_2FA cookie, and retries with the TOTP code. Sessions are cached per profile+controller URL under $XDG_CACHE_HOME/unifly/ with JWT expiry extraction and a 60-second safety margin. The --no-cache flag forces a fresh login when needed.
🚀 Windows Support
New PowerShell installer (install.ps1) with automatic PATH setup and version detection. Config paths now resolve to %APPDATA%\unifly on Windows via the directories crate, with XDG fallback on Linux and macOS. CI builds target x86_64-pc-windows-msvc instead of -gnu, and a new windows-smoke job verifies every push.
⚡️ v2 API and Multi-Endpoint DPI Cascade
DPI statistics now try the v2 traffic-flow-latest-statistics endpoint first (Network Application 9+), then cascade through stat/sitedpi and stat/dpi for older firmware. Per-station filtering uses stat/stadpi when MAC addresses are provided. New dpi status, dpi enable, and dpi disable subcommands toggle Deep Packet Inspection from the CLI.
🐛 Integration API Compatibility Sweep
Over a dozen fixes to WiFi, firewall, network, and client payloads ensure unifly speaks the correct field names, types, and envelope structures the Integration API expects — resolving silent failures and rejected requests across create/update operations.
✨ New Commands
unifly nat policies list|get|create|delete— Full NAT policy management via the v2 Legacy API. Supports--from-filewith JSON/JSONC payloads for masquerade, SNAT, and DNAT rulesunifly api <path>— Raw API passthrough with-m get|postand-d <json-body>flags. Returns unprocessed JSON from any controller endpointunifly dpi status— Query whether Deep Packet Inspection is enabled on the controllerunifly dpi enable/unifly dpi disable— Toggle DPI via the legacy site-settings endpointunifly clients reservations— List all DHCP reservations across networks, showing MAC, name, fixed IP, and network ID
🐛 API and Protocol Fixes
- Client MAC resolution — Read
macAddressfrom the top-level Integration API response field instead of the nestedaccessobject, which produced bogus UUID-format MACs - WiFi security config — Use
securityConfiguration.typeas the discriminator (not.mode), addsaeConfigurationdefaults for WPA3 modes, and check both fields when deserializing for backward compatibility - WiFi network reference — Send
{"type": "SPECIFIC", "networkId": "..."}instead of{"id": "..."}when creating WiFi broadcasts; readnetworkIdwithidfallback on response - WiFi frequency precision — Round-trip
f32frequency values through string representation to prevent2.4from becoming2.4000000953674316, which the API rejects - WiFi broadcast type guard — Only send
bssTransitionEnabledforSTANDARDbroadcasts;IOT_OPTIMIZEDrejects it as unknown - WiFi fast roaming — Add
fastRoamingEnabledto the security config payload with--from-filealias support - WiFi detail endpoint —
wifi getnow fetches the detail endpoint instead of the overview, exposinghideName,bssTransitionEnabled, andbandSteeringEnabled - WiFi
--from-filealiases — Addfrequenciesalias forfrequencies_ghzand default forhide_ssid - Firewall ordering envelope — Deserialize the
orderedFirewallPolicyIdswrapper correctly on GET and wrap the PUT body in the same envelope. Add--after-systemflag for placing policies after system-defined rules - Firewall
allowReturnTraffic— Default totrueand expose as--allow-return-trafficon policy create and update, since the Integration API requires it non-null - Firewall port serialization — Parse port strings to
u16before building theportFilterJSON payload, since the API expects integers - Firewall action deserialization —
FirewallActionnow deserializes case-insensitively instead of requiring exact case - Network create defaults — Supply required non-null fields:
mdnsForwardingEnabled,cellularBackupEnabled,pingConflictDetectionEnabled,leaseTimeSeconds(86400),autoScaleEnabled - Network DHCP range — Use
ipAddressRange.stopinstead of.end - Network management type — Serialize the resolved
NetworkManagementenum (GATEWAY,SWITCH,UNMANAGED) instead of hardcodedUSER_DEFINED - Network DNS servers — Map
dhcp.dns_serverstodnsServerIpAddressesOverridein the Integration API DHCP configuration. Add--dnsflag tonetworks create - Event templates — Resolve
{key}placeholders in raw event messages using the event's extra JSON fields - Event display — Fix blank events caused by
UnifiEvent.messageonly deserializing"message"(added"msg"alias). Filter out WS bridge sync/state-dump messages. Pushevents_snapshot()on data bridge startup so initial events are not lost
🖥️ TUI Improvements
- NAT sub-tab on the Firewall screen showing enabled status, name, type, protocol, destination, and translation columns with themed selection highlighting
- About overlay (
akey) displaying application name, version, author, and license with inline shortcuts to open the donate page (d) and GitHub repository (g) - Donate button in the status bar — clickable
♥ Donatelink that opens PayPal in the default browser. Hidden on Setup/Settings screens and during search. Toggled viashow_donatein settings (persisted to config) - Settings section headers — Connection and Appearance groups separated by styled horizontal rules with bold labels
- Dashboard events — Single-column full-width event layout replacing the two-column format; percentage-based constraints for proportional scaling
- Data bridge now pushes initial snapshots for NAT policies, firewall zones, ACL rules, and events on startup
🔧 CLI Improvements
clients remove-ip --network <name>— Scope DHCP reservation removal to a single network when a device has reservations on multiple VLANs- Truncation hint — Print a stderr hint (
Showing N of M results. Use --all (-a) to see everything.) when--limittruncates list output - Firewall zones
--from-file— JSON/JSONC file support forfirewall zones createandupdatewith CLI-style field name aliases, comments, trailing commas, and case-insensitive action matching - API-key Legacy access — API-key auth mode now also creates a
LegacyClientwithX-API-KEYheaders, enabling access to DHCP reservations, client stats, events, and health data without requiring hybrid auth - TUI log path — Uses
std::env::temp_dir()instead of hardcoded/tmp/for cross-platform compatibility
⬆️ Dependencies
serde_yaml_ngreplaces the archivedserde_yml(known unsoundness). YAML output post-processes bare values containing colons to quote them for YAML 1.1 parser compatibility- reqwest 0.12 → 0.13 (
rustls-tls→rustls) - tokio-tungstenite 0.26 → 0.29 (with
ringcrypto provider install for rustls 0.23) - toml 0.8 → 1.1, dialoguer 0.11 → 0.12, indicatif 0.17 → 0.18
- tachyonfx 0.23 → 0.25, throbber-widgets-tui 0.10 → 0.11
- bytesize 1 → 2, tabled 0.17 → 0.20, strum 0.26 → 0.28
- WebSocket TLS fix — Install
ringcrypto provider for rustls 0.23; useConnector::Plainforws://URLs
♻️ Codebase Refactoring
Major module splitting across both crates to reduce file sizes and improve navigability:
- unifly-api: Split controller into
lifecycle.rs,refresh.rs,runtime.rs,subscriptions.rs,support.rs,legacy_queries.rs, andquery/submodules. Split integration response types, command payloads, and WebSocket runtime into focused files - unifly CLI: Split
args.rsinto per-entity submodules. Split client, device, firewall, ACL, and config command handlers into submodules - unifly TUI: Split dashboard render panels, firewall/clients/devices/networks/stats/settings/onboarding/topology screens into state, render, and input submodules. Extract shared screen state helpers, navigation handlers, and lifecycle handlers
- Replaced wildcard
use super::*imports with explicit, granular imports across all controller submodules - Normalized brace placement on multi-line
if-letguard chains across 13 files
📝 Documentation
- AGENTS.md expanded from brief skill summary into comprehensive agent-facing documentation covering architecture, workspace layout, CLI/TUI scaffolding patterns, error handling, and sacred rules.
CLAUDE.mdadded as symlink - SKILL.md restructured around operat...
Release v0.7.0
Release Notes v0.7.0
Released: 2026-04-01
This release adds MFA/TOTP authentication with persistent session caching, firewall traffic filter management, and DHCP reservations — alongside a deep codebase refactor that splits every large module into focused submodules. The TUI gains an About overlay, a donate button, sectioned settings form, and a rewritten bandwidth chart.
🌟 Highlights
🔒 MFA/TOTP Authentication & Session Caching
Two-factor authentication for legacy API login is now fully supported. When a UniFi controller returns an HTTP 499 MFA challenge, unifly injects the UBIC_2FA cookie and retries with the TOTP token. Session cookies are cached to disk under $XDG_CACHE_HOME/unifly/, keyed by profile + controller URL hash, with JWT-based expiry tracking and a 60-second safety margin. Subsequent CLI invocations skip the login handshake entirely — especially valuable for MFA-enabled controllers.
New flags: --totp, --no-cache. New env var: UNIFI_TOTP. New profile config: totp_env for integration with 1Password CLI and similar tools.
🔥 Firewall Traffic Filter Management
Full read/write support for the Integration API's polymorphic traffic filter system. firewall policies list and get now display structured source/destination filter summaries. Create and update policies with --src-network, --src-ip, --src-port, --dst-network, --dst-ip, --dst-port, --states, and --ip-version flags. The filter engine handles real-world API quirks: PORTS vs VALUE type names, numeric port values, IP_ADDRESSES vs SPECIFIC discriminators, and unknown variant fallbacks.
✨ DHCP Reservations & Firewall Patch Logging
New clients set-ip and clients remove-ip commands manage DHCP reservations via the Legacy API's /rest/user endpoint, with automatic network detection from IP subnet. firewall policies patch --logging toggles per-rule logging without a full policy update.
⚡ Bandwidth Chart Rewrite
The dashboard traffic chart is rebuilt using ratatui's Canvas widget with Octant markers for higher-resolution area fills. EMA smoothing (alpha 0.35) eliminates jarring sample-to-sample jumps, and the Y-axis scales against the P85 percentile over the most recent 48 samples instead of raw maximums. Sample rate increased from 1s to 250ms with a 120-sample window (30s visible span).
♻️ Deep Module Decomposition
Every oversized module in the codebase has been split into focused submodules. The controller is now lifecycle, refresh, runtime, subscriptions, support, commands/, payloads/, and query/. The websocket module splits into parser, runtime, and tls. The integration client is split by domain: clients, devices, firewall, networks, policy, reference, system, wifi. TUI screens decompose into input, render/, and state submodules. CLI args and commands follow the same pattern. This restructuring spans ~40K changed lines across 196 files.
🔒 Authentication & Sessions
- Detect HTTP 499 MFA challenge from UniFi SSO, inject
UBIC_2FAcookie, and complete the two-step TOTP handshake inlegacy/auth.rs - Validate TOTP format (exactly 6 digits) before sending to the controller
- Return
Error::TwoFactorRequiredwhen no token is provided, enabling callers to prompt interactively - Persistent session cache in
legacy/session_cache.rs: atomic file writes with 0600 permissions (Unix), Windows-safe rename, JWTexpparsing viabase64crate - Session validation probe to
/api/s/{site}/selfbefore trusting cached cookies - Skip logout on disconnect when cache is active (preserves session for next invocation)
- New
totp_envprofile field lets you wire up TOTP providers:totp_env = "UNIFI_TOTP"withUNIFI_TOTP=$(op item get "UniFi" --otp) unifly ...
🔧 Firewall & Policy
- New typed domain model:
PolicyEndpoint,TrafficFilter,PortSpec,IpSpecwithsummary()for table display - Integration API types:
SourceTrafficFilter,DestTrafficFilter,NetworkFilter,IpAddressFilter,PortFilter,PortItemwith custom port value deserializer TrafficFilterSpecenum (Network,IpAddress,Port) serialized into the Integration API's polymorphic JSON with proper type discriminators- Update handler reads existing policy, merges filter changes, and preserves unmodified fields
- Firewall policy and zone endpoints wrapped with
unwrap_or_empty()— 404 or not-configured errors no longer abort the full refresh cycle - Fixed firewall ordering alignment and ACL update correctness
🖥️ TUI Improvements
- About overlay (
akey): shows application name, version, author, and license. Inline shortcuts:dopens donate page,gopens GitHub repository. Renders on top of every screen - Donate button in status bar: clickable ♥ button at the right edge, opens PayPal link via cross-platform
open_urlhelper. Hidden on Setup/Settings screens and when search is active. Toggled viashow_donatesetting, persisted underdefaults.show_donatein config - Settings form section headers: fields grouped under Connection and Appearance sections using
FormEntryenum that interleavesSectiondividers withFieldrows. EachSettingsFieldcarries its ownsection()label androw_height() - Generic
handle_toggle_key()deduplicates Insecure and ShowDonate key handlers - Stabilized client detail target and device selection across refresh cycles
- Centralized command intent handling across TUI screens
🐛 Bug Fixes
- Event template resolution:
resolve_event_templates()substitutes{key}placeholders (e.g.,{user},{ssid}) with values from the event's extra JSON fields, applied to bothLegacyEventandUnifiEventconversions - Blank events fixed:
UnifiEvent.messagenow deserializes both"message"and"msg"via serde alias. WS bridge filters out non-EVT_*messages (sync/state-dump broadcasts). Data bridge pushesevents_snapshot()on startup so initial events are not lost - Dashboard events: single-column layout with full-width messages replaces the old two-column layout. Events page drops redundant Type column and milliseconds from timestamps
- XDG config path: replaced
directoriescrate with inline XDG logic so config always resolves to~/.config/unifly/config.toml(or$XDG_CONFIG_HOME/unifly/config.toml), not~/Library/Application Support/on macOS - Connection state race: fixed a pre-existing race in
Controller::execute()whereDisconnectedstate was read despite a successfulconnect() - Batch refresh snapshot rebuilds to deduplicate events and bound controller fanout
- Reject empty update requests and unavailable integration surfaces with clear errors
- Restore legacy event behavior: WebSocket startup for
events watch, fail-fast for legacy-only commands without legacy auth - Parse local list filters correctly in CLI commands
- Align integration API paths with OpenAPI spec
⬆️ Dependencies & Infrastructure
- MSRV bumped from 1.86 to 1.94 — unlocks
is_multiple_ofand new clippy lints - Major dependency upgrades:
reqwest0.12→0.13,tokio-tungstenite0.26→0.29,toml0.8→1.1,dialoguer0.11→0.12,indicatif0.17→0.18,tachyonfx0.23→0.25,bytesize1→2,tabled0.17→0.20,strum0.26→0.28 - WebSocket TLS fix: explicit
ringcrypto provider install for rustls 0.23,Connector::Plainfor non-TLS URLs - Replaced wildcard
use super::*imports with explicit imports across all controller submodules - Clippy fixes: owned-to-reference parameters, eta reduction,
map_or_else()chains,.expect()over.unwrap() - CI release notes model upgraded to
claude-opus-4-6 - Added
.github/FUNDING.ymlwith GitHub Sponsors and Ko-fi links - Added
base64anddirectoriesworkspace dependencies for session caching
📝 Documentation
- Comprehensive AI agent skill overhaul: added
topology,countries,clients findcommands; fixed all CLI flag names across SKILL.md, commands.md, and workflows.md - Fixed stale 5-crate architecture docs to reflect the actual 2-crate structure (
unifly-api+unifly) - Updated README: library section, topology command, missing aliases, opaline badge
- DHCP reservations and traffic filter documentation in skill references
Breaking Changes
- MSRV is now 1.94 — Rust 1.94 or newer is required to compile from source.
- Config path on macOS — The config file now resolves to
~/.config/unifly/config.tomlinstead of~/Library/Application Support/. If you have an existing config at the old path, move it to the new location.
Upgrade Notes
- If your UniFi controller has MFA enabled, use
--totp <CODE>or setUNIFI_TOTPto complete login. Configuretotp_envin your profile for automated workflows. - Session caching is on by default. Use
--no-cacheto force a fresh login if you encounter stale session issues. - macOS users: move your config from
~/Library/Application Support/unifly/config.tomlto~/.config/unifly/config.toml. - Update your Rust toolchain to 1.94+ if building from source.
Release v0.6.0
Release Notes v0.6.0
Released: 2026-03-27
This release marks the evolution from proof-of-concept to production-ready UniFi management toolkit. Four minor versions (v0.2.0 through v0.6.0) consolidate architecture, expand CLI capabilities, modernize dependencies, and refine the TUI experience. The focus is simplicity (single binary), usability (quick lookups, network visualization), and maintainability (Rust 1.94, modern dependencies).
Highlights
🚀 Single Binary Distribution
The TUI is now a subcommand (unifly tui) instead of a separate binary. This halves distribution complexity—one artifact for Homebrew, one for AUR, one install command. Feature flags still allow CLI-only builds (--no-default-features --features cli) for minimal deployments.
🌐 Network Topology Command
New unifly topology command renders a hierarchical tree view of your network: gateway → devices → clients, with VLAN labels, device types (AP/SW), signal strength, and connection state. Clients are grouped by uplink device MAC. Perfect for quick infrastructure visualization without opening the TUI.
🔍 Quick Client Lookup
unifly clients find <query> performs substring search across IP, name, hostname, and MAC (case-insensitive). No more scanning long client lists to find a specific device. Alias: unifly clients search.
🎨 SilkCircuit Themed CLI Output
All CLI table output now uses the opaline theme engine with semantic color methods: device names in neon cyan, IP addresses in coral, MAC addresses muted, health states in green/red/yellow. Respects --color flag and only applies to table format. The entire CLI now shares the same visual language as the TUI.
📊 Dashboard Bandwidth Chart Rewrite
Replaced the Chart+Dataset rendering with a Canvas widget using Octant markers for higher-resolution area fills. Added exponential moving average (EMA) smoothing (alpha 0.35) to eliminate jarring sample-to-sample jumps. Increased sample rate from 1s to 250ms intervals with a 120-sample window (30s visible span). Y-axis now scales using percentile-based reference (P85 over 48 recent samples) instead of raw maximum, preventing transient spikes from collapsing the chart scale.
⚡ Dependency Modernization
Bumped MSRV to Rust 1.94 and upgraded 10+ major dependencies: reqwest 0.12→0.13, tokio-tungstenite 0.26→0.29, toml 0.8→1.1, tabled 0.17→0.20. Fixed WebSocket TLS for rustls 0.23 (explicit crypto provider installation, plain ws:// URL handling).
🔧 CLI Enhancements
Commands
topology(alias:topo) — Network tree visualization showing gateway → devices → clients hierarchy with VLAN labels, device types, signal strength, and connection state. Clients grouped by uplink device MAC.clients find <query>— Quick client lookup by name, IP, hostname, or MAC. Case-insensitive substring search. Supports all output formats (-o json,-o yaml).clients set-ip <mac> <ip> [--network <name>]— Create DHCP static reservation. Auto-detects target network from IP subnet unless--networkspecified.clients remove-ip <mac>— Delete DHCP static reservation.
Firewall Traffic Filters
Full read/write support for the Integration API's polymorphic traffic filter system:
- Read: Parse structured source/destination endpoints with network, IP address, port, MAC, application, domain, and region filters. Handles API quirks (PORTS vs VALUE type names, numeric vs string port values).
- Write: New CLI flags for
firewall policies createandupdate:--src-network,--src-ip,--src-port--dst-network,--dst-ip,--dst-port--states(TCP state filters)--ip-version(IPv4/IPv6)
- Patch:
firewall policies patch <id> --loggingfor toggling rule logging without full update.
Colored Output
All 27 table row structs across 18 command handlers now use opaline's semantic color methods:
- Device/client names: neon cyan (
accent.secondary) - IP addresses: coral (
code.number) - MAC addresses: muted gray (
text.muted) - Health states: green/red/yellow (
state.success/error/warning) - Types/labels: dimmed (
text.dim)
Color respects --color flag and only applies to table output format.
🖥️ TUI Improvements
Dashboard Bandwidth Chart
- Canvas widget replaces Chart+Dataset for higher-resolution area fills and line edges using Octant markers
- EMA smoothing (alpha 0.35) eliminates jarring jumps in live traffic display. Each sample interpolates toward target value, snapping when delta drops below 1 byte/s.
- Percentile-based Y-axis uses P85 over 48 recent samples instead of raw visible maximum, preventing transient spikes from collapsing scale
- 250ms sample rate with 120-sample window (30s visible span) for smoother visual updates
- Manual Y-axis labels in dedicated gutter column for full layout control
Stats Screen Fixes
Fixed four critical bugs that made the Stats screen completely non-functional:
- Request timing:
RequestStatsfired atinit()before controller connected—never retried. Now triggers after connection. - Period keys shadowed: Keys 1-4 for period selection were unreachable due to global tab navigation consuming them. Changed to
h/lfor period cycling. - Timestamp units: Sent timestamps in seconds but UniFi API expects milliseconds.
- Wrong DPI endpoint: Used
stat/stadpi(per-station) instead ofstat/sitedpi(site-wide aggregate).
Theme Integration
- Opaline token-based theming replaces hardcoded SilkCircuit Neon color constants. All 20 builtin themes now work via
--themeflag,UNIFLY_THEMEenv var, ordefaults.themein config TOML. - Interactive theme selector in settings screen (press Enter on Theme field). Modal overlay with 20-theme picker, persists to config on selection.
- Derived tokens for TUI-specific colors (TX/RX fills, chart series) adapt automatically to any theme.
📦 Distribution & Installation
One-Line Installer
New platform-detecting installer script for Linux (amd64/arm64) and macOS (arm64):
curl -fsSL https://raw.githubusercontent.com/hyperb1iss/unifly/main/install.sh | shDownloads latest release binary and installs to /usr/local/bin (or ~/.local/bin if no sudo).
AUR Package
Arch Linux users can now install via AUR:
yay -S unifly-binIncludes aur/PKGBUILD and aur/update-aur.sh automation script.
Install Methods
All supported installation surfaces:
- One-line installer:
curl -fsSL https://... | sh - Homebrew:
brew install hyperb1iss/tap/unifly - AUR:
yay -S unifly-bin - Cargo:
cargo install --git https://github.com/hyperb1iss/unifly.git unifly - Binary: Download from GitHub Releases
🤖 AI Agent Skill
Major documentation overhaul with missing commands and critical flag fixes:
- Added missing commands:
topology,countries,clients findto entity table - Fixed flag names across all commands:
networks create:--vlan-id→--vlan,--dhcp-mode→--dhcp,--dhcp-end→--dhcp-stop,--isolation→--isolatedwifi create:--type→--broadcast-type,--network-id→--network,--frequency→--frequenciesclients authorize:--duration→--minutes, rate limits →--tx-limit-kbps/--rx-limit-kbpshotspot create:--duration→--minutes, added required--nameflagfirewall/acl reorder:--policy-ids/--rule-ids→--get/--setpatterndns create:--type→--record-typetraffic-lists create:--type→--list-type
- Environment variable fix:
UNIFI_CONTROLLER→UNIFI_URL(matches README) - Added full env var list:
UNIFI_API_KEY,UNIFI_URL,UNIFI_PROFILE,UNIFI_SITE,UNIFI_OUTPUT,UNIFI_INSECURE,UNIFI_TIMEOUT
🔨 API Layer (unifly-api)
Firewall Traffic Filter Support
Phase 1 (Read):
- Parse polymorphic traffic filter tree from Integration API into typed domain model
- New types:
SourceTrafficFilter,DestTrafficFilter,NetworkFilter,IpAddressFilter,PortFilter,PortItem - Custom port value deserializer handles numeric/string values from API
- Domain model:
PolicyEndpoint,TrafficFilter,PortSpec,IpSpecwith human-readablesummary()method for table display - Handles API quirks: PORTS vs VALUE port filter types, IP_ADDRESSES vs SPECIFIC ip filter types, PORT_NUMBER items with numeric values
- Unknown variant fallbacks for future API additions
Phase 2 (Write):
TrafficFilterSpecenum for write operations (Network, IpAddress, Port)- Controller serializes into Integration API's polymorphic JSON format with proper type discriminators
- Update handler reads existing policy, merges filter changes, preserves unmodified fields
- Replaces old flat
source_address/destination_address/destination_portfields with typed filter system
Client Model Extensions
- DHCP reservations: Added
use_fixedipandfixed_ipfields, populated from Legacy user data during client merge phase - Fixed IP commands:
SetClientFixedIpandRemoveClientFixedIpvia Legacy API/rest/userendpoint - Network auto-detection: Resolves target network from IP subnet CIDR matching
WebSocket TLS Fixes
Fixed two issues from rustls 0.23 and tungstenite 0.29 upgrade:
- Crypto provider: rustls 0.23 requires explicit crypto provider installation before
ClientConfig::builder(). Added ring provider install inbuild_tls_connector(). - Plain ws:// handling: For non-TLS
ws://URLs, useConnector::Plaininstead of trying to build a rustls config (previous code would panic).
Restores websocket handshake test to passing.
Store & Data Flow
- Batch refresh: Snapshot rebuilds now batch updates instead of clearing collections each cycle
- Integration availability checks: Explicit checks for snapshot-backed inte...
Release v0.1.1
Release Notes v0.1.1
Released: 2026-02-24
First public release of unifly — a complete command-line toolkit for managing Ubiquiti UniFi network controllers. Two binaries, one shared async engine, and a unified interface for all three UniFi API dialects (Integration REST, Legacy session-based, and WebSocket push).
🌟 Highlights
Dual API Engine with Hybrid Authentication
Built-in support for both the Integration API (REST with API keys) and Legacy API (session-based with cookie/CSRF tokens). Hybrid mode combines both for maximum coverage — CRUD via Integration, events/statistics/device commands via Legacy. The engine automatically negotiates the best authentication path based on available credentials.
Real-Time Terminal Dashboard
unifly-tui delivers an 8-screen btop-style dashboard with live data from your UniFi controller. Area-fill bandwidth charts with Braille line overlays, CPU/memory gauges, zoomable network topology, 10K-event rolling buffer with pause/scroll-back, and inline network editing. All screens update in real-time with configurable refresh intervals.
22 Resource Types, 15+ CLI Commands
Complete coverage of UniFi infrastructure: devices, clients, networks, WiFi (SSIDs), firewall policies/zones/ACLs, DNS records, VPN tunnels, hotspot vouchers, DPI statistics, RADIUS profiles, traffic lists, WAN interfaces, sites, and system operations. Each resource supports list, get, create, update, and delete where applicable.
Secure Credential Storage
OS keyring integration for API keys and passwords — nothing written to plaintext config files. Multi-profile support for managing multiple controllers with named profiles. Interactive setup wizard (unifly config init) with automatic site discovery and credential validation.
Comprehensive Documentation Site
Full VitePress documentation with Mermaid diagrams covering installation, authentication, CLI commands, TUI screens, architecture (crate map, data flow, CSRF token sequence), and development workflows. Deployed to GitHub Pages with SilkCircuit dual-palette theme.
🤖 Agent Platform
AI agent skill for Claude Code and skills.sh with autonomous network management capabilities:
- unifly-network-manager agent — Device management, network configuration, WiFi tuning, firewall auditing, monitoring, diagnostics
- Slash commands —
/unifly-statusfor quick health checks,/unifly-auditfor security audits - Progressive disclosure — Command reference, workflow guides, and concept glossary
- Installation via
claude /plugin install hyperb1iss/unifly-skillornpx add-skill hyperb1iss/unifly-skill
📦 Library Crates
unifly-api (Transport Layer)
HTTP client abstractions for both Integration and Legacy APIs:
- Integration API — REST client with automatic pagination, API key auth, OpenAPI types
- Legacy API — Session-based client with cookie/CSRF token management, event streaming, statistics
- WebSocket client — Real-time event push with TLS verification passthrough
- Unified transport —
HttpClienttrait with async-trait, configurable timeouts, retry logic
unifly-core (Business Logic)
Reactive data layer and domain models:
- Controller — Lifecycle management, command dispatch, multi-API coordination
- DataStore — Reactive collections with
watch()streams, automatic refresh, in-memory caching - Domain models — 22 strongly-typed resources with builder patterns, validation, ID wrappers
- Converters — Bidirectional mapping between API types and domain models with
#[serde(flatten)]for extra fields
unifly-config
Configuration management with profile support:
- TOML config — Named profiles, default site/output format, theme selection, TLS verification control
- Environment variables —
UNIFI_URL,UNIFI_API_KEY,UNIFI_PROFILE,UNIFI_SITE,UNIFI_THEME,UNIFI_INSECURE - Keyring storage — OS-level secret storage via
keyringcrate withsync-secret-serviceon Linux - File permissions — Config files created with
0o600permissions on Unix
🖥️ CLI Features
Commands
acl— ACL rule managementadmin— Administrator accountsalarms— Alarm managementclients— Connected client listing and blockingcompletions— Shell completions (bash, zsh, fish)config— Profile management, keyring operations, interactive wizardcountries— Country code referencedevices— Device adoption, provisioning, upgrade, restartdns— Local DNS record managementdpi— DPI application/category reference dataevents— Event history and live streamingfirewall— Policy, zone, and rule managementhotspot— Voucher creation and managementnetworks— VLAN/subnet CRUD, DHCP configuration, IPv6 settingsradius— RADIUS profile listingsites— Multi-site managementstats— Historical statistics queries (WAN bandwidth, client counts, DPI breakdown)system— Controller info, backups, firmware queriestraffic-lists— Traffic matching list managementvpn— VPN server and tunnel listingwans— WAN interface statuswifi— SSID broadcast management
Output Formats
table— Human-readable tables (default)json— Pretty JSONjson-compact— Minified JSONyaml— YAML outputplain— Plain text for scripting
Global Flags
-p, --profile <NAME> Named profile from config
-c, --controller <URL> Controller URL override
-s, --site <SITE> Site name or UUID
-o, --output <FORMAT> Output format
-k, --insecure Accept self-signed TLS certs
-v, --verbose Logging verbosity (-v, -vv, -vvv)
-q, --quiet Suppress non-error output
-y, --yes Skip confirmation prompts
--timeout <SECS> Request timeout
--color <MODE> Color control (auto/always/never)
🎨 TUI Screens
1. Dashboard
btop-style overview with 8 live panels:
- WAN Traffic — Area-fill chart with Braille line overlay, live TX/RX rates, peak tracking
- Gateway — Model, firmware, WAN IP, IPv6, DNS, ISP, latency
- Connectivity — Visual traffic bars for WAN uplink
- System Health — Subsystem status dots, CPU/MEM gauges, load averages
- Networks — VLAN topology with IPv6 prefix delegation indicators
- WiFi/APs — Access point table with experience scores
- Top Clients — Proportional traffic bars by hostname
- Recent Events — Compact 5-line event feed
2. Devices
3-column table with model, IP, CPU/MEM, TX/RX, uptime. Five-tab detail panel:
- Overview — Firmware, serial, adoption state, port config
- Performance — CPU/memory history, uptime graphs
- Radios — WiFi radio details (frequency, channel, power)
- Clients — Per-device client list
- Ports — Switch port status (PoE, speed, link state)
3. Clients
Connected client listing with hostname, IP, MAC, VLAN, signal bars, traffic. Filter by type:
- All / Wireless / Wired / VPN / Guest
4. Networks
VLAN topology with subnets, DHCP ranges, IPv6 mode, gateway type. Inline edit overlay (e key):
- Toggle isolation, internet access, mDNS forwarding, IPv6, DHCP
- Changes submit immediately to the Integration API
5. Firewall
Three-tab view:
- Policies — Traffic rule policies
- Zones — Firewall zones
- ACL Rules — Access control lists with visual rule reordering
6. Topology
Zoomable network tree with color-coded device types:
- Gateway → Switches → APs
- Uplink state visualization
7. Events
Live event stream with 10K rolling buffer:
- Pause (
Space), scroll back (↑/↓), severity filtering - Color-coded by event type
8. Stats
Historical charts with 1h/24h/7d/30d windows:
- WAN Bandwidth — Area-fill charts with TX/RX separation
- Client Counts — Connected device trends
- DPI Apps — Top 10 applications by traffic
- DPI Categories — Traffic breakdown by category
🔐 Security Improvements
- Fixed TLS default in
profile_to_controller_config()— now usesSystemDefaultsinstead of silently accepting invalid certificates - Set
0o600permissions on config file after write (Unix) - WebSocket connections now respect TLS verification settings passed via CLI
- Keyring integration prevents credential exposure in config files
- Config wizard validates credentials before saving
🔧 Infrastructure
CI/CD Pipelines
- CI workflow — Cargo check, test, clippy pedantic, fmt on PR/push
- Release workflow — Version bump, git-iris release notes, tag creation
- CI/CD workflow — Multi-platform builds (x86_64/aarch64 Linux, x86_64/aarch64 macOS, x86_64 Windows), crates.io publishing, GitHub Release with artifacts, Homebrew tap update
- Docs workflow — VitePress build and deploy to GitHub Pages
- ARM64 Linux support via
ubuntu-24.04-armrunner withlldlinker andlibdbus-1-dev
Build Configuration
- Rust 1.86+ with Edition 2024
lldlinker for faster builds on Linux- Nightly rustfmt for
imports_granularityandgroup_importssupport - Clippy pedantic lints with targeted
#[allow]for TUI numeric casts - Workspace-level dependency management across 5 crates
Package Distribution
- Homebrew —
brew install hyperb1iss/tap/unifly(installs both binaries) - Shell script installers — Linux and macOS via curl
- PowerShell installers — Windows via
irm+iex - Cargo install — From source via GitHub
- Crates.io —
unifly-apiandunifly-corepublished (binaries remain GitHub-only)
🐛 Bug Fixes
TUI Fixes
- Fixed Stats screen which was completely non-functional:
RequestStatsfired before controller connected — now retries- Period keys (1-4) shadowed by global tab navigation — now handled in screen-specific input
- Timestamps se...