Release notes

See what changed in each release, why it matters, and whether there are install, compatibility, or security notes before you update.

All releases on GitHub

v0.3.1

Aug 24, 2026

A bug-fix release. Everything here was found by using the desktop app on a real network rather than by reading the code, and three of the fixes are for faults that reported success while doing nothing.

Fixed
  • Pinging your own machine no longer reports 100% loss. On Windows, ping 127.0.0.1 — and the machine's own LAN address — timed out while the system ping answered immediately. Two causes stacked up: raw ICMP sockets need administrator rights, so an ordinary run fell back to TCP probes on ports 80/443/22 and concluded a host was down when nothing answered; and a Windows raw socket does not observe traffic to an address the host owns. Windows now sends echoes through the IP Helper API, which needs no privileges and reaches local addresses. Discover and sweep both start from a ping sweep, so both returned nothing for any range covering this host.
  • IPv6 hosts can be pinged. ping ::1 reported total loss because IPv6 had no ICMP path at all and fell through to the same TCP probe. Windows now uses Icmp6SendEcho2.
  • A fresh install no longer runs one probe at a time. Number(null) is 0 and passes an isFinite check, so reading an unset preference produced a stored zero rather than the default: max concurrent probes came out as 1 instead of 256, serialising every scan, discover and sweep. Traffic precision had the same fault, showing no decimals. Profiles already left at 1 by the broken build are repaired once on upgrade.
  • Discover reports devices the OS already knows about. Results now merge probe replies with the neighbour table, so a device that answers ARP but not ICMP is no longer missing. Each host records whether it was found by probe or by neighbour, since a stale neighbour entry can outlive the device.
  • netscli arp --clear no longer claims to have cleared the table when it has not. arp -d * on Windows prints "The requested operation requires elevation" and then exits 0, so checking the exit status alone reported success while nothing was touched. It now fails, with the reason, and a non-zero exit.
  • The settings dialog is centred on the window, and its Max Concurrent Probes control is themed rather than rendering in the WebView's default 3D-bevelled controls with duplicate spin arrows.
  • Menu items in the run options popover respond to clicks. An unrecognised popover was torn down by the global dismissal handler before its own item could fire, so the item did nothing, threw nothing and logged nothing.
Added
  • Clear ARP table, then discover. A chevron beside Run offers a cache-flushing variant on discover, sweep and the ARP tab — the tools where a stale neighbour entry changes the answer. Clearing needs administrator rights; when it fails the run is suppressed rather than quietly returning the stale entries it was meant to drop.
Changed
  • The app opens on Discover rather than Scan. The first useful question on an unfamiliar network is what is on it, and a scan needs a host you do not have yet.
  • Completion notifications only appear for tabs you are not looking at. On the visible tab the result arriving in the table already says the run finished. Failures still report unconditionally.
  • Dependency updates: pcap 2.4 → 2.5, astro, lucide-react, vitest, @axe-core/cli, and the vite/rollup group.

v0.3.0

Aug 20, 2026

The desktop workspace is redesigned, scan results carry richer status data, and interactive interfaces gain probe concurrency controls. Public docs are refreshed and packaging is fixed across release channels.

Added
  • Richer port scan results across every interface. Port scans now report additive status and detail fields (open, closed, filtered, error, latency, banners, HTTP metadata, TLS metadata, and raw previews where available) while keeping the older open, port, service, and error fields intact for compatibility.
  • GUI render automation. The desktop app now has Tauri/WebDriver render coverage for the diagnostic workspace, including tab layout, top menus, toolbar actions, filtering, row selection, detail panes, command preview, and status bar behavior.
  • User-configurable probe concurrency. The CLI and MCP server already accepted concurrency limits; the desktop app settings and TUI settings now expose the same control so users can reduce simultaneous probes on fragile networks or raise them within the core safety cap.
  • Address-family display preference in the desktop app. The status bar now prefers IPv4 by default and lets users choose IPv6-first display when that better matches their environment.
Changed
  • Desktop app redesigned around a denser diagnostic workspace. NetsCLI Desktop moved from the earlier dashboard-style UI to a native-like shell with operation tabs, compact forms, sortable and filterable results, row details, CLI command previews, and status summaries. The GUI continues to use the same netscli-core operations as the CLI, TUI, and MCP server, so desktop behavior stays aligned with the rest of the tool.
  • Desktop app icon refreshed to match the current brand. The Tauri icon generator now renders the ANSI-style N using the same gradient direction as the website/favicon, and regenerates the Windows/Tauri icon assets from that source.
  • CLI and TUI scan output now reflects richer status data. Human output stays concise, but scanned ports can show closed, filtered, and error states with latency where available instead of only emphasizing open ports.
  • Windows install guidance now prefers Winget for the desktop app. Release notes and install docs call out Winget's manifest review and installer hash verification as the recommended Windows path, while direct GitHub Windows installers remain unsigned and may show Windows warnings until code signing is added later.
  • Website, docs, FAQ, 404 page, and changelog refreshed for the new release. The public site now uses a more consistent shell, unified code and table styling, clearer search behavior, release-note summaries, and a desktop-app screenshot captured from the real UI with representative demo data.
  • Linux/macOS install docs clarified. mDNS is documented as the default pure-Rust capability in published builds, while packet capture remains the optional workflow that depends on libpcap/Npcap support.
  • Website rebuilt for every screen width. The landing page and docs were swept across six widths and both themes, and the shell, navigation, contents list, colour and typography were reworked to hold up at all of them. The brand accent moved from a teal-green that read blue in small text to one that reads green at any size, and contrast improved with it. (#190–#209)
  • Search, head metadata and page titles rewritten so the site describes what it is rather than repeating adjectives. (#204)
  • Per-PR site previews on Cloudflare Pages, and GitHub Pages deploys are manual-only. (#165, #136)
  • netscli scan --json now reports every port, not just the open ones. Filtering to open ports made "all closed", "all filtered" and "every probe errored" the same empty array, so a script could not tell a clean scan from a host that refused every probe. Each entry carries open and status, so callers that want only open ports can filter for them.
  • The MCP server now scans only local networks by default. This is the one surface driven by a model rather than by the person at the keyboard, so the instruction to scan a third party can arrive from a web page or a file someone else wrote — and the packets leave from your machine and your IP. RFC1918, loopback, link-local and the carrier-grade NAT range overlay networks use are allowed; set NETSCLI_MCP_ALLOW_PUBLIC_TARGETS=1 to reach past them.
  • Scan results returned to a model are capped. The full probe response (raw) is no longer included and banners are truncated, both being bytes chosen by the scanned host.
  • Tool failures are returned as MCP isError results rather than JSON-RPC errors, so a failed scan no longer reads to a client as a broken server.
  • The desktop app's CSV export escapes spreadsheet formulas. A cell beginning = + - or @ is evaluated on open by Excel and LibreOffice, and exported cells carry banners and hostnames the scanned host chose. Values that parse as numbers are untouched, so a negative latency is still a number.
Changed (internal)
  • The release pipeline verifies before it commits to anything. The three crates are now published in one command, so cargo packages and compiles all of them before uploading any -- previously an upload of netscli-core could succeed and leave that version permanent on crates.io, which has no unpublish, while a later crate failed to package. CI runs the same command as a dry run on every push, so packaging is exercised long before a release rather than for the first time during one. Release Drafter also resolves its version from tags instead of from the last published release, which had it proposing v0.2.7 for a repo already tagged v0.3.0.
  • The Tauri render suite can run. It had never passed: every run ended at session creation, because msedgedriver looks for the debug port in a DevToolsActivePort file inside its own temporary profile while wry writes that file into Tauri's. The harness now starts the app itself and attaches to it, which skips the lookup entirely. It does not pass yet -- the remaining failures are assertions to triage -- but it drives the real app for the first time, and the throughput bug above is what it found.
  • GUI architecture split into maintainable ownership modules. App.tsx and the old single CSS file were decomposed into workspace state, tool presentation helpers, shell components, result/detail components, Tauri services, and layered style files. The UI behavior stays production-data driven; no mock/sample data is shipped in the app.
  • Core, CLI, TUI, MCP, and Tauri internals reduced from monolithic files into facades plus focused modules. The public Rust API, CLI syntax, MCP schema, Tauri command payloads, GUI data shape, and SQLite schema remain stable.
  • CI tightened for future changes. PR CI now includes GUI unit tests before the GUI build, and a separate Tauri render workflow can run manually, nightly, or on GUI/Tauri-related pull requests.
  • Packaging templates and release workflows audited. Release workflows use the pinned Rust toolchain, AUR templates include runtime dependencies and license installation, Winget/Scoop/Homebrew reference manifests were refreshed, and packaging validation commands were added to the release checklist.
  • CI gates report unconditionally, so branch protection can require them, and both required checks were closed against a job that fails without failing the gate. (#161, #187)
  • The end-to-end suite can now fail. Several scenarios were structurally incapable of it. (#199)
  • The Tauri render suite is schedule-only and no longer gates releases. (#178)
  • A dead-CSS budget runs in CI, holding the docs override stack at its current 126 provably shadowed declarations. (#207)
  • Node 22, jsdom 30, ESLint 10, react-hooks 7, and three Rust dependency bumps. (#187–#189)
  • Release pipeline hardened: tag validation on the AUR jobs, a checksum that could be contaminated by progress output, and the publish long tail. (#158, #200)
Fixed
  • MCP server handled one request at a time. The read loop awaited each handler before parsing the next line, so a slow scan blocked every other request on the connection, including cancellation. Handlers now run concurrently under a semaphore. (#169)
  • Reading the ARP table blocked a runtime worker. On Windows and macOS it shells out to arp and waits on the child process; three callers invoked it straight from async code. With MCP handlers capped at 16 concurrent, sixteen of these could stall every worker — including the one reading stdin, so no further request could even be parsed. Moved to a blocking thread. (#196)
  • Safety limits were enforced in Ops but not in the engines. The scan, sweep, discover and inspect engines are public API re-exported at the crate root, and called directly they applied no subnet, port or concurrency cap — 0.0.0.0/0 collected 4,294,967,294 addresses into a Vec before sending a packet. Every engine now enforces its own limits. (#198)
  • Port 0 was rejected only by the MCP surface. Now rejected everywhere. (#164)
  • Panic paths in the core and silent corruption in the OUI generator. (#172)
  • TUI mis-measured wide characters, so CJK and emoji in a remote-supplied hostname or banner pushed box borders out of alignment. (#173)
  • The desktop app described work it had not done. The command preview claimed five ports while three were scanned, truncated captures were presented as complete, and the open-port count drifted from the rows below it. (#197)
  • Packet Capture vanished on builds without capture support instead of explaining what was needed. (#193)
  • The desktop app was not keyboard operable, and the result grid had incorrect ARIA. (#171)
  • The website claimed packet capture in builds that do not ship it, and advertised a version that was never released. (#194, #208)
  • Safety limits that only one caller was applying. SweepEngine::sweep validates its port list instead of trusting the caller and silently returning "no open ports"; mDNS browse duration, ping -c and packet captures given a packet count but no duration all gained the core-side ceiling they were documented to have.
  • netscli trace no longer prints router-supplied hostnames unsanitised. Hop names come from PTR records controlled by whoever runs those routers, and this was the last plain-text output path without the terminal-safety pass every other one had.
  • Four ways an MCP client could wedge or kill the server: no overall request deadline, permits acquired after spawning rather than before, a single invalid UTF-8 byte on stdin terminating the process, and client disconnect cancelling nothing.
  • The concurrent packet-capture limit could be bypassed by calling the blocking capture tool, which never registered a job.
  • discover_network with no arguments failed on a host whose interface carries a /8, because the substituted default exceeded the /16 cap.
  • Workspace search jumped to the wrong row. The search dialog listed rows in backend order and the table renders them sorted and filtered, so the position it handed over meant a different row — which is every scan, since each tool has a default sort.
  • A malformed result bundle blanked the window. Import validated only that array-backed kinds got an array, so a bad entry threw during render with nothing to catch it, taking every other tab's state with it.
  • The desktop app stayed on "Detecting…" in silence when interface polling kept failing, leaving the capture form with no interfaces and no explanation.
  • AUR packages are published against a re-hashed asset. Both AUR jobs took the published .sha256 sidecar on trust rather than downloading the asset and hashing it, which is the circular check the release scripts exist to prevent; the other registries already did this correctly.
  • The Windows installer verifies Npcap before running it. install.ps1 downloaded the Npcap installer from an overridable URL and launched it elevated with nothing checked; it now verifies the Authenticode signature and signer, and refuses to run an unsigned or unexpected binary.
  • install.sh no longer claims success before installing libpcap. A user who asked for capture support could read "Installed successfully" and get a binary that cannot capture.
  • The desktop app's throughput reading no longer disappears on a VPN or tunnel interface. Traffic counters come from a different enumeration than the interface list, and the two disagree: on one Windows machine seven of twelve interfaces had no counterpart, including the Tailscale adapter that was up and was what the app selected by default. The status bar then dropped the whole reading -- numbers, unit and divider -- with nothing to explain it, permanently. Selection now prefers an interface whose throughput can actually be read, and where none can, the bar says "no traffic data" instead of showing nothing.

v0.2.6

May 6, 2026

Installed GUI builds now identify themselves correctly, and the Windows title-bar controls work. Also completes the CLI/TUI refactors that make future interface changes easier to review and test.

Fixed
  • GUI: in-app version display was stuck at 0.1.0. A stale APP_VERSION constant in App.tsx powered both the bottom-bar version readout and the About dialog, but it never got bumped alongside package.json, tauri.conf.json, or the workspace Cargo.tomls. Caught by a Winget moderator on microsoft/winget-pkgs#368471: the v0.2.4 build correctly reported 0.2.4 to the Windows registry (Tauri pulls ProductVersion from tauri.conf.json), but users saw 0.1.0 in the GUI itself. Wired APP_VERSION to read package.json at build time via Vite's define so the in-app display auto-syncs every release going forward.
  • GUI: title-bar buttons (close, minimize, maximize) didn't work on Windows. Tauri 2's deny-by-default permission system requires explicit core:window:allow-close/minimize/maximize/unmaximize/start-dragging grants; the app was missing its capabilities config entirely. Added src-tauri/capabilities/main.json. (#62)
Changed (internal)
  • Major refactor of TUI / CLI organization (#63–#67):
    • apps/netscli-cli/src/main.rs shrank from 1870 → 527 lines (-72%).
    • apps/netscli-cli/src/tui.rs (2226 lines) decomposed into a tui/ module with 8 focused files (state, events, widgets, palette, command_catalog, config, history, mod).
    • apps/netscli-gui/src/App.tsx shrank from 1480 → 931 lines (-37%) via per-tab views in views/*View.tsx.
    • formatter.rs renamed to tui_formatter.rs for naming consistency with tui_export.rs, tui_settings.rs.
    • All behavior-preserving; 25 tests pass on every PR's 3-OS matrix.
  • CI runner-minute spend cut by ~70% per PR by collapsing the release-build matrix to ubuntu-only on PRs (full 3-OS only on push to main) and adding paths-ignore for docs/site/packaging changes. (#61)

v0.2.5

May 5, 2026

Closes a DNS resolver security advisory. Windows subnet detection is fixed, so discovery and sweep find real LAN hosts again, and package publishing now covers GUI installers.

Security
  • hickory-resolver 0.24 → 0.26 closes RUSTSEC-2026-0119: CPU exhaustion during message encoding due to O(n²) name compression in hickory-proto. The DNS lookup tab and any inspect/discover that resolves hostnames are no longer reachable through the vulnerable encoding path. The 0.26 builder pattern (TokioResolver::builder_tokio()) replaces the deprecated TokioAsyncResolver::tokio constructor; see PR #55 for the source migration. The .cargo/audit.toml ignore added in #52 was removed once the bump landed.
Fixed
  • GUI: discover/sweep returned only a single host on Windows. Root cause: detect_default_ipv4_subnet iterated ipconfig::Adapter::prefixes() and grabbed the first IPv4 entry, but that list contains the host's own /32, broadcast /32, multicast /4, link-local /16, and the network /24. Windows reports the host /32 first, so the "subnet" was a single IP. New helper pick_ipv4_subnet_from_prefixes filters to network-shaped prefixes (length 1..=30, not multicast, not link-local) and truncates host bits, matching the Linux path. 5 unit tests added that run on every CI platform via cfg(any(windows, test)). (#59)
  • GUI: dashboard "Recent Scans" rendered with wrong colors / not as list rows. .history-item is a <button> (for keyboard accessibility) but the CSS didn't reset user-agent button styles. WebView2 on Windows applied Win32 chrome (color: ButtonText, centered text, content-fit width, system button font), breaking the inherit chain for child labels. Explicit reset added. (#59)
Changed
  • Dependencies (all transitive, no API surface impact):
    • crossterm 0.27 → 0.28 + tui-textarea 0.4 → 0.7 had to land together — tui-textarea 0.7 hardcodes crossterm = "0.28". (#58)
    • mdns-sd 0.13 → 0.19 — adapt to the new ScopedIp::to_ip_addr() accessor in netscli-core/src/mdns.rs. (#58)
    • clap 4.5.60 → 4.6.1 (#43), pcap 1.3 → 2.4 (#45), clap_mangen 0.2.33 → 0.3.0 (#46), dialoguer 0.11.0 → 0.12.0 (#48), dirs 5.0.1 → 6.0.0 (#51), tokio 1.52.1 → 1.52.2 + clap_complete 4.6.2 → 4.6.3 (#57).
  • ratatui 0.29 → 0.30 deferred: tui-textarea has no version yet that supports ratatui 0.30 (latest 0.7 still pins ratatui 0.29). Tracked via @dependabot ignore on the closed PR #49.
Added
  • Release pipeline GUI automation. publish.yml extended with 4 parallel jobs that publish the GUI bundles to Homebrew Cask, Scoop extras (netscli-gui.json), Winget (fstubner.netscli.gui), and AUR (netscli-gui-bin) on every tagged release. (#54, #53, #56)

v0.2.4

May 3, 2026

v0.2.3 built the GUI installers but never attached them. Publishing is repaired here, along with the AUR deploy action that was blocking Linux packages.

Fixed
  • GUI bundle path in release.yml's GUI matrix was rooted at apps/netscli-gui/src-tauri/target/${TARGET}/release/bundle/. Cargo workspaces actually use the workspace-root target/ directory regardless of which subcrate's directory cargo was invoked from, so Tauri's bundle output lives at target/${TARGET}/release/bundle/. v0.2.3 built the .deb / .dmg / .msi correctly but the collect step found an empty bundle dir and skipped everything; sigstore-sign then failed trying to sign nothing.
  • AUR deploy action (KSXGitHub/github-actions-deploy-aur) was pinned to @v2.7.0 (April 2024), which has a bash: --command: invalid option regression in its container entrypoint. Bumped to @v4.1.3 (current stable, same input shape).
Notes
  • CLI release shipped: 44 assets, sigstore-signed, on the v0.2.3 release page.
  • Homebrew, Scoop, Winget, and crates.io all updated to 0.2.3.
  • AUR is still on the previous version (failed to push).
  • 0 GUI installers attached to v0.2.3 release.

v0.2.3

May 3, 2026

GUI installer builds move again once the Tauri JavaScript and Rust versions agree, and the AUR packaging handoff is fixed. CLI packages were already usable from v0.2.2; the GUI artifacts needed these pipeline fixes.

Fixed
  • Tauri version skew broke all 4 GUI installer builds in v0.2.2's release matrix. The Cargo.toml constraint tauri = "2.0.0" resolved to tauri 2.9.5, but npm @tauri-apps/api: ^2 resolved to 2.10.1. Tauri's CLI rejects same-major different-minor as a version mismatch. Loosened the Rust constraint to tauri = "2" and ran npm update --save so both sides land on the same minor (currently 2.11.0). Verified with a local npm run tauri build producing NetsCLI_0.2.3_x64_en-US.msi cleanly.
  • AUR publish job in publish.yml failed on v0.2.2 with a confusing bash: --command: invalid option error from the deploy action's internals. Root cause: rendered PKGBUILD was written to /tmp/ PKGBUILD, but the KSXGitHub/github-actions-deploy-aur action runs in a Docker container that only mounts $GITHUB_WORKSPACE — files in /tmp are invisible inside the container. Render now writes to packaging/aur/PKGBUILD (workspace-relative) before handoff.
Notes
  • CLI binaries shipped successfully on v0.2.2 — cargo install, brew install netscli, and scoop install netscli all give v0.2.2.
  • v0.2.2 GitHub release has CLI assets but no GUI installers.
  • AUR netscli-bin was last bumped to v0.2.0; it'll catch up to v0.2.3 directly.

v0.2.2

May 3, 2026

This is a release-pipeline recovery build. It refreshes Cargo.lock so locked release builds can run reproducibly after dependency bumps, giving package-manager users a working replacement for the failed v0.2.1 artifacts.

Fixed
  • Cargo.lock was out of sync with Cargo.toml at the v0.2.1 tag — tokio 1.52.1 (bumped in #17) requires socket2 >= 0.6.3 transitively, but Dependabot only regenerated the direct-dep entries in the lock. CI's lint paths use cargo build (no --locked) so this slipped through; release.yml uses --locked to guarantee reproducible builds, and all 17 release builds for v0.2.1 failed at the lockfile check.
  • 0.2.2 regenerates the lockfile so socket2 0.6.3 is recorded alongside the existing 0.5.10. No application code changes.
Notes
  • Released to crates.io but the GitHub release page has no attached binaries (release.yml never produced any). cargo install netscli works because cargo regenerates the lockfile per-user; downloads from the GitHub release / package managers should use 0.2.2.
  • 0.2.1 is left in place as crates.io history rather than yanked.

v0.2.1

Apr 30, 2026

Desktop installers and signed release assets mean you no longer need a Rust toolchain to install. Adds concurrency tuning for networks that struggle with large parallel scans.

Added
  • Prebuilt desktop GUI installers attached to every release: .msi (Windows x86_64), .dmg (macOS aarch64 + x86_64), .deb and .AppImage (Linux x86_64). Each is sigstore-signed alongside the CLI binaries. macOS .dmg ships unsigned for now; right-click → Open to bypass Gatekeeper, or run xattr -dr com.apple.quarantine /Applications/NetsCLI.app.
  • --concurrency <N> (alias -j <N>) global CLI flag for tuning in-flight network operations. Default stays at 256; clamped to [1, 1024]. Useful on fragile home gateways that can't keep up with hundreds of simultaneous probes.
Changed
  • Bumped pnet_packet, pnet_transport, pnet_datalink, and pnet_sys from 0.34 to 0.35.
  • Bumped sysinfo from 0.30 to 0.38. New Networks::refresh(true) semantics drop hot-unplugged interfaces from the cached map rather than retaining stale RX/TX stats.
Notes
  • ipnetwork stayed at 0.20 because pnet_datalink 0.35 still pins it transitively; will revisit when upstream pnet relaxes the constraint.