Skip to content

Releases: hyperb1iss/unifly

Release v0.9.0

10 Apr 05:41

Choose a tag to compare

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 to https://api.ui.com, force system TLS defaults, disable WebSocket and session caching, and use longer polling intervals (60s refresh, 30s poll)
  • IntegrationClient detects cloud platform connections and surfaces ConsoleOffline / ConsoleAccessDenied errors with actionable guidance
  • Cloud connector routing uses /v1/connector/consoles/{host_id} prefix for Integration API tunneling
  • --host-id global flag and UNIFI_HOST_ID env var for CLI-level host targeting
  • cloud isp and cloud sdwan subcommands 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 (U key) 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 19 for 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

  • --theme promoted from TUI-only to a global flag shared by CLI and TUI — resolution order: --themeUNIFLY_THEME env → config defaults.themesilkcircuit-neon
  • nat policies update merges 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 a NonInteractiveRequiresYes error in CI/pipe contexts instead of blocking forever, guiding callers to pass --yes
  • config set normalizes auth_mode = "legacy" to "session" on write for backward compatibility
  • Error help text across AuthFailed, NoCredentials, Unsupported, ProfileNotFound, and NoConfig now references both config init (local) and config cloud-setup (Site Manager)

🐛 Bug Fixes

  • Platform detection rewritten — both /api/login and /api/auth/login are probed before deciding, fixing misclassification of recent standalone controllers that return 401 from the UniFi OS endpoint
  • NAT policy creationfilter_type corrected to use only NONE/ADDRESS_AND_PORT (controller rejects ADDRESS/PORT); interface fields now resolve Integration UUIDs to Session _id strings; rule_index auto-increments from existing rules instead of hardcoding 0
  • API-key 401 classificationSessionAuth enum distinguishes cookie vs API-key session clients; a rejected API key now returns InvalidApiKey instead 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.name instead of flat fields; WiFi experience uses wlan_band; channels redesigned from per-radio rows to country-level regulatory data; format_radio_band handles both stat/sta and wifiman band code families
  • VPN IPsec SA 404 — controllers with no VPN tunnels return 404 for stat/ipsec-sa; vpn status now catches this and shows an empty list
  • Client uplink MAC — resolution uses ap_mac for wireless and sw_mac for wired clients regardless of wireless info presence
  • Session events — API-key session clients treat 404 on stat/event as optional-missing; events watch gated on cookie-backed session auth

♻️ Refactoring

  • convert.rs (2,189 lines) decomposed into convert/ module directory with 13 per-entity submodules — no behavioral changes
  • Full legacysession rename across Rust modules, config keys, TUI labels, CLI error strings, and documentation — LegacyClientSessionClient, Error::LegacyApiError::SessionApi, ensure_legacy_accessensure_session_access, etc.
  • ensure_session_access promoted from a private function in events.rs to the shared util/access.rs module
  • CLI dispatch split into dispatch() and dispatch_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.txt and llms-full.txt generated from Zola content at build time via docs/scripts/gen-llms-txt.sh
  • Prettier configuration (.prettierrc, .prettierignore) integrated into justfile for consistent non-Rust file formatting
  • GitHub issue templates (bug report, feature request), PR template, CODE_OF_CONDUCT.md, and SECURITY.md added
  • 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, and skills/unifly/SKILL.md on release

💥 Breaking Changes

  • auth_mode = "legacy" renamed to "session" — Existing config files with auth_mode = "legacy" are accepted as a backward-compatible alias and normalized to "session" on next config set write. 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 --yes in non-interactive contexts (CI, pipes) will now fail with exit code 1 and a message pointing to the -y flag
  • Cloud profiles can be created with unifly config cloud-setup — existing local profiles are unaffected
  • The ...
Read more

Release v0.8.2

08 Apr 18:59

Choose a tag to compare

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 summaries
  • unifly settings get <key> — Retrieve a setting section with automatic sensitive field masking in table output; JSON output shows full values for programmatic access
  • unifly settings set <key> <field> <value> — Update individual fields with type-inferred value parsing; also supports --data '{...}' for bulk JSON merge
  • unifly settings export — Dump all settings as raw JSON for backup or inspection
  • Controller::has_live_event_access() — New API method that checks for cookie-backed session auth, used to gate WebSocket-dependent features

🐛 Bug Fixes

  • NAT filter_type rejectionbuild_filter() now only emits NONE and ADDRESS_AND_PORT values. The controller's v2 API rejects ADDRESS and PORT alone with a deserialization error; when only an address or port is supplied, the filter uses ADDRESS_AND_PORT and includes only the populated fields
  • NAT interface ID mismatch — New resolve_interface_id() queries rest/networkconf and matches on external_id to translate Integration UUIDs to Session _id hex strings required by the v2 NAT API. Legacy-style IDs pass through unchanged
  • NAT duplicate rule_indexnext_rule_index() queries existing NAT rules and uses max(rule_index) + 1 instead of hardcoding 0
  • Misleading 401 errors in API-key modeunauthorized_error() now uses the SessionAuth enum to return Error::InvalidApiKey for API-key clients and Error::SessionExpired for cookie clients, replacing a generic "session expired" message that confused API-key users
  • Missing Integration API yields raw 404lifecycle.rs connect paths now catch 404 from the Integration sites endpoint and surface CoreError::Unsupported with guidance to use --username/--password instead
  • Event endpoint 404 treated gracefully — In refresh.rs, a 404 from stat/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 watch gated on cookie auth — The events watch handler now calls ensure_live_event_access() instead of ensure_session_access(), preventing confusing WebSocket failures when only API-key session auth is available
  • CLI tracing corrupted pipe outputinit_tracing() in cli.rs now writes to stderr instead of stdout, so diagnostic log lines no longer break jq, json.loads(), and other downstream consumers of -o json / -o yaml output
  • Dispatch cognitive complexity lint — The 27-arm dispatch() match was split into dispatch() and dispatch_extended() to stay under Clippy's cognitive complexity threshold of 25

🔧 API & Internal Changes

  • Added SessionAuth enum to crates/unifly-api/src/session/client.rs with Cookie and ApiKey variants; SessionClient now carries this field and exposes it via auth()
  • SessionClient::with_client() now requires a SessionAuth parameter
  • New controller methods: get_all_site_settings(), get_site_setting(key), update_site_setting(key, body) in session_queries.rs
  • New has_live_event_access() on Controller in subscriptions.rs
  • Command dispatch split: dispatch() handles A–N commands, dispatch_extended() handles N–Z plus unreachable arms

✅ Testing

  • New unit tests for SessionAuth unauthorized error classification (cookie → SessionExpired, API key → InvalidApiKey)
  • New unit tests for has_live_event_access() vs has_session_access() with both auth types
  • Integration test api_key_mode_treats_missing_event_endpoint_as_empty — verifies 404 from stat/event results in empty events, not a failure
  • Integration test api_key_mode_reports_unsupported_when_integration_api_missing — verifies 404 from the Integration sites endpoint produces CoreError::Unsupported with actionable message
  • Existing session_client_test.rs updated with setup_api_key() helper and correct SessionAuth parameters
  • 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.md updated with settings command entry (now 27 commands)
  • skills/unifly/references/commands.md updated with settings gotchas section
  • Plugin manifests (.claude-plugin/, .cursor-plugin/) bumped to v0.8.2
  • Workspace version in Cargo.toml bumped to 0.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, or rule_index values, those workarounds can be removed
  • Users on older UniFi controllers without the Integration API will now get a clear error message suggesting --username/--password auth instead of a confusing 404
  • CI/scripting pipelines that parse unifly stdout can now rely on clean structured output; tracing diagnostics are written to stderr

Release v0.8.1

08 Apr 05:37

Choose a tag to compare

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/, LegacyClientSessionClient, Error::LegacyApiError::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 (alias s2s): CRUD for IPsec and OpenVPN site-to-site tunnels via Session API
  • vpn remote-access (alias ra): CRUD for L2TP/OpenVPN/WireGuard servers, plus suggest-port and download-config for OpenVPN
  • vpn clients: CRUD for configured VPN client profiles
  • vpn connections: list, get, and restart active VPN client connections
  • vpn peers (alias peer): CRUD for WireGuard peers scoped to a remote-access server, plus subnets audit
  • vpn magic-site-to-site (alias magic-s2s): read-only inspection of auto-configured tunnels
  • vpn settings: list, get, set, and patch for teleport, openvpn, magic-s2s, and peer-to-peer toggles
  • vpn status: live IPsec security association status from stat/ipsec-sa; returns empty list (not error) when no tunnels exist
  • vpn health: VPN subsystem health from the cached stat/health snapshot
  • vpn servers get <id> and vpn 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 from stat/rogueap with signal, channel, SSID, and observer AP MAC; supports --within <secs>, --limit, and --all flags
  • wifi channels: per-radio regulatory channel availability from stat/current-channel with country code and available HT/VHT modes
  • clients roams <client>: connection timeline from v2 system-log/client-connection/{mac} with timestamp, AP, SSID, signal, and band; accepts name, hostname, IP, or MAC with auto-resolution
  • clients wifi <client>: Wi-Fi experience metrics from v2 wifiman/{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.name structure instead of flat fields; added From/To/Channel columns
  • Fixed Wi-Fi experience parsing: wlan_band (not band), display_name for 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_band to handle both stat/sta codes (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) or Error::SessionExpired (session mode, jar present) instead of a generic "session expired" message that misled api-key users
  • Error::is_not_found() expanded: recognizes SessionApi 404s (HTTP 404 status prefix and api.err.NotFound message code), so vpn status returns 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 set normalizes 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, and skills/unifly/SKILL.md automatically on release — no more manual manifest bumps
  • ClawHub skill publishing: new publish-skill CI job runs clawhub sync after GitHub Release creation, reading version from SKILL.md frontmatter
  • Release workflow fix: switched version-files YAML scalar from | to |- to strip trailing newline that caused set -e to kill the script before git commit
  • Added prettier and prettier-check just recipes for formatting docs/ markdown and JSON
  • Satisfied newer clippy pedantic lints: map_or replaces map(..).unwrap_or(..), sort_by_key with Reverse replaces comparators, Duration::from_mins(1) replaces from_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.md symlinked 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 check gate, 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 (--within not --hours), and more

💥 Breaking Changes

  • Config: auth_mode = "legacy" renamed to auth_mode = "session" — Existing config files with auth_mode = "legacy" continue to work (accepted as backwards-compatible alias), but config set and config init now write "session". Update your configs or run unifly config init to regenerate.
  • Rust API types renamed — If you depend on unifly-api as a library: LegacyClientSessionClient, Error::LegacyApiError::SessionApi, legacy_prefix()session_prefix(), and all Legacy* model types → Session*. EntityId::Legacy is 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-api Rust types, update import paths from legacy:: to session:: and rename type references
  • VPN commands that previously only supported servers and tunnels now have 9 subcommand groups — review unifly vpn --help for the full surface
  • The wifi neighbors command defaults to 25 results; use --all or --limit to control output size

Release v0.8.0

05 Apr 01:15

Choose a tag to compare

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-file with JSON/JSONC payloads for masquerade, SNAT, and DNAT rules
  • unifly api <path> — Raw API passthrough with -m get|post and -d <json-body> flags. Returns unprocessed JSON from any controller endpoint
  • unifly dpi status — Query whether Deep Packet Inspection is enabled on the controller
  • unifly dpi enable / unifly dpi disable — Toggle DPI via the legacy site-settings endpoint
  • unifly clients reservations — List all DHCP reservations across networks, showing MAC, name, fixed IP, and network ID

🐛 API and Protocol Fixes

  • Client MAC resolution — Read macAddress from the top-level Integration API response field instead of the nested access object, which produced bogus UUID-format MACs
  • WiFi security config — Use securityConfiguration.type as the discriminator (not .mode), add saeConfiguration defaults 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; read networkId with id fallback on response
  • WiFi frequency precision — Round-trip f32 frequency values through string representation to prevent 2.4 from becoming 2.4000000953674316, which the API rejects
  • WiFi broadcast type guard — Only send bssTransitionEnabled for STANDARD broadcasts; IOT_OPTIMIZED rejects it as unknown
  • WiFi fast roaming — Add fastRoamingEnabled to the security config payload with --from-file alias support
  • WiFi detail endpointwifi get now fetches the detail endpoint instead of the overview, exposing hideName, bssTransitionEnabled, and bandSteeringEnabled
  • WiFi --from-file aliases — Add frequencies alias for frequencies_ghz and default for hide_ssid
  • Firewall ordering envelope — Deserialize the orderedFirewallPolicyIds wrapper correctly on GET and wrap the PUT body in the same envelope. Add --after-system flag for placing policies after system-defined rules
  • Firewall allowReturnTraffic — Default to true and expose as --allow-return-traffic on policy create and update, since the Integration API requires it non-null
  • Firewall port serialization — Parse port strings to u16 before building the portFilter JSON payload, since the API expects integers
  • Firewall action deserializationFirewallAction now 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.stop instead of .end
  • Network management type — Serialize the resolved NetworkManagement enum (GATEWAY, SWITCH, UNMANAGED) instead of hardcoded USER_DEFINED
  • Network DNS servers — Map dhcp.dns_servers to dnsServerIpAddressesOverride in the Integration API DHCP configuration. Add --dns flag to networks 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.message only deserializing "message" (added "msg" alias). Filter out WS bridge sync/state-dump messages. Push events_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 (a key) 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 ♥ Donate link that opens PayPal in the default browser. Hidden on Setup/Settings screens and during search. Toggled via show_donate in 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 --limit truncates list output
  • Firewall zones --from-file — JSON/JSONC file support for firewall zones create and update with 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 LegacyClient with X-API-KEY headers, 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_ng replaces the archived serde_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-tlsrustls)
  • tokio-tungstenite 0.26 → 0.29 (with ring crypto 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 ring crypto provider for rustls 0.23; use Connector::Plain for ws:// 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, and query/ submodules. Split integration response types, command payloads, and WebSocket runtime into focused files
  • unifly CLI: Split args.rs into 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-let guard 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.md added as symlink
  • SKILL.md restructured around operat...
Read more

Release v0.7.0

01 Apr 00:23

Choose a tag to compare

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_2FA cookie, and complete the two-step TOTP handshake in legacy/auth.rs
  • Validate TOTP format (exactly 6 digits) before sending to the controller
  • Return Error::TwoFactorRequired when 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, JWT exp parsing via base64 crate
  • Session validation probe to /api/s/{site}/self before trusting cached cookies
  • Skip logout on disconnect when cache is active (preserves session for next invocation)
  • New totp_env profile field lets you wire up TOTP providers: totp_env = "UNIFI_TOTP" with UNIFI_TOTP=$(op item get "UniFi" --otp) unifly ...

🔧 Firewall & Policy

  • New typed domain model: PolicyEndpoint, TrafficFilter, PortSpec, IpSpec with summary() for table display
  • Integration API types: SourceTrafficFilter, DestTrafficFilter, NetworkFilter, IpAddressFilter, PortFilter, PortItem with custom port value deserializer
  • TrafficFilterSpec enum (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 (a key): shows application name, version, author, and license. Inline shortcuts: d opens donate page, g opens 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_url helper. Hidden on Setup/Settings screens and when search is active. Toggled via show_donate setting, persisted under defaults.show_donate in config
  • Settings form section headers: fields grouped under Connection and Appearance sections using FormEntry enum that interleaves Section dividers with Field rows. Each SettingsField carries its own section() label and row_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 both LegacyEvent and UnifiEvent conversions
  • Blank events fixed: UnifiEvent.message now deserializes both "message" and "msg" via serde alias. WS bridge filters out non-EVT_* messages (sync/state-dump broadcasts). Data bridge pushes events_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 directories crate 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() where Disconnected state was read despite a successful connect()
  • 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_of and new clippy lints
  • Major dependency upgrades: reqwest 0.12→0.13, tokio-tungstenite 0.26→0.29, toml 0.8→1.1, dialoguer 0.11→0.12, indicatif 0.17→0.18, tachyonfx 0.23→0.25, bytesize 1→2, tabled 0.17→0.20, strum 0.26→0.28
  • WebSocket TLS fix: explicit ring crypto provider install for rustls 0.23, Connector::Plain for 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.yml with GitHub Sponsors and Ko-fi links
  • Added base64 and directories workspace dependencies for session caching

📝 Documentation

  • Comprehensive AI agent skill overhaul: added topology, countries, clients find commands; 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.toml instead 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 set UNIFI_TOTP to complete login. Configure totp_env in your profile for automated workflows.
  • Session caching is on by default. Use --no-cache to force a fresh login if you encounter stale session issues.
  • macOS users: move your config from ~/Library/Application Support/unifly/config.toml to ~/.config/unifly/config.toml.
  • Update your Rust toolchain to 1.94+ if building from source.

Release v0.6.0

27 Mar 03:54

Choose a tag to compare

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 --network specified.
  • 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 create and update:
    • --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> --logging for 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:

  1. Request timing: RequestStats fired at init() before controller connected—never retried. Now triggers after connection.
  2. Period keys shadowed: Keys 1-4 for period selection were unreachable due to global tab navigation consuming them. Changed to h/l for period cycling.
  3. Timestamp units: Sent timestamps in seconds but UniFi API expects milliseconds.
  4. Wrong DPI endpoint: Used stat/stadpi (per-station) instead of stat/sitedpi (site-wide aggregate).

Theme Integration

  • Opaline token-based theming replaces hardcoded SilkCircuit Neon color constants. All 20 builtin themes now work via --theme flag, UNIFLY_THEME env var, or defaults.theme in 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 | sh

Downloads 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-bin

Includes 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 find to entity table
  • Fixed flag names across all commands:
    • networks create: --vlan-id--vlan, --dhcp-mode--dhcp, --dhcp-end--dhcp-stop, --isolation--isolated
    • wifi create: --type--broadcast-type, --network-id--network, --frequency--frequencies
    • clients authorize: --duration--minutes, rate limits → --tx-limit-kbps/--rx-limit-kbps
    • hotspot create: --duration--minutes, added required --name flag
    • firewall/acl reorder: --policy-ids/--rule-ids--get/--set pattern
    • dns create: --type--record-type
    • traffic-lists create: --type--list-type
  • Environment variable fix: UNIFI_CONTROLLERUNIFI_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, IpSpec with human-readable summary() 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):

  • TrafficFilterSpec enum 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_port fields with typed filter system

Client Model Extensions

  • DHCP reservations: Added use_fixedip and fixed_ip fields, populated from Legacy user data during client merge phase
  • Fixed IP commands: SetClientFixedIp and RemoveClientFixedIp via Legacy API /rest/user endpoint
  • 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:

  1. Crypto provider: rustls 0.23 requires explicit crypto provider installation before ClientConfig::builder(). Added ring provider install in build_tls_connector().
  2. Plain ws:// handling: For non-TLS ws:// URLs, use Connector::Plain instead 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...
Read more

Release v0.1.1

25 Feb 05:22

Choose a tag to compare

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-status for quick health checks, /unifly-audit for security audits
  • Progressive disclosure — Command reference, workflow guides, and concept glossary
  • Installation via claude /plugin install hyperb1iss/unifly-skill or npx 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 transportHttpClient trait 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 variablesUNIFI_URL, UNIFI_API_KEY, UNIFI_PROFILE, UNIFI_SITE, UNIFI_THEME, UNIFI_INSECURE
  • Keyring storage — OS-level secret storage via keyring crate with sync-secret-service on Linux
  • File permissions — Config files created with 0o600 permissions on Unix

🖥️ CLI Features

Commands

  • acl — ACL rule management
  • admin — Administrator accounts
  • alarms — Alarm management
  • clients — Connected client listing and blocking
  • completions — Shell completions (bash, zsh, fish)
  • config — Profile management, keyring operations, interactive wizard
  • countries — Country code reference
  • devices — Device adoption, provisioning, upgrade, restart
  • dns — Local DNS record management
  • dpi — DPI application/category reference data
  • events — Event history and live streaming
  • firewall — Policy, zone, and rule management
  • hotspot — Voucher creation and management
  • networks — VLAN/subnet CRUD, DHCP configuration, IPv6 settings
  • radius — RADIUS profile listing
  • sites — Multi-site management
  • stats — Historical statistics queries (WAN bandwidth, client counts, DPI breakdown)
  • system — Controller info, backups, firmware queries
  • traffic-lists — Traffic matching list management
  • vpn — VPN server and tunnel listing
  • wans — WAN interface status
  • wifi — SSID broadcast management

Output Formats

  • table — Human-readable tables (default)
  • json — Pretty JSON
  • json-compact — Minified JSON
  • yaml — YAML output
  • plain — 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 uses SystemDefaults instead of silently accepting invalid certificates
  • Set 0o600 permissions 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-arm runner with lld linker and libdbus-1-dev

Build Configuration

  • Rust 1.86+ with Edition 2024
  • lld linker for faster builds on Linux
  • Nightly rustfmt for imports_granularity and group_imports support
  • Clippy pedantic lints with targeted #[allow] for TUI numeric casts
  • Workspace-level dependency management across 5 crates

Package Distribution

  • Homebrewbrew 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.iounifly-api and unifly-core published (binaries remain GitHub-only)

🐛 Bug Fixes

TUI Fixes

  • Fixed Stats screen which was completely non-functional:
    • RequestStats fired before controller connected — now retries
    • Period keys (1-4) shadowed by global tab navigation — now handled in screen-specific input
    • Timestamps se...
Read more