Version 3 Breaking Changes

May 18, 2026 ยท View on GitHub

This document is the canonical upgrade and migration guide for breaking changes introduced in version 3.0.

For a broader summary of version 3 improvements, architecture changes, and non-breaking additions, see Version 2.x to 3.0 Code Base Changes.

Breaking Changes

Output format flags changed

Pretty JSON moved to -o J

The noninteractive --output-format codes have changed so each human-oriented Ruby formatter has a distinct option.

FormatOld flagNew flag
Compact JSON-o j-o j
Pretty JSON-o k-o J
Puts-o p-o p
Pretty printnot available-o P
Amazing printdefault human output only-o a
Inspect-o i-o i
YAML-o y-o y

The -o p flag still means plain puts output. Scripts that depended on -o p for unquoted scalar values can keep using it:

state="$(wifi-wand -o p ci)"

Use -o J when you want indented JSON:

wifi-wand -o J info

Use -o P when you want Ruby pretty print output:

wifi-wand -o P info

Amazing Print color follows stdout

The -o a formatter now lets amazing_print decide whether to emit ANSI color instead of forcing plain text. It uses color when stdout is a terminal and plain text when output is piped or redirected. Pipe through tee if you want terminal-readable plain output while also saving or forwarding it:

wifi-wand -o a info | tee wifi-info.txt

Pretty output dependency changed

awesome_print replaced by amazing_print

wifi-wand now depends on amazing_print for human-readable object formatting. Ruby consumers that indirectly relied on wifi-wand to make awesome_print available must add their own direct awesome_print dependency or migrate to amazing_print.

Ruby require paths renamed

Library require paths now use wifi_wand

The Ruby library entry point and sub-require paths now use snake_case file names instead of the gem's hyphenated command name.

# Old
require 'wifi-wand'
require 'wifi-wand/models/ubuntu_model'
require 'wifi-wand/models/mac_os_model'

# New
require 'wifi_wand'
require 'wifi_wand/platforms/ubuntu/model'
require 'wifi_wand/platforms/mac/model'

The gem name and command names are unchanged: install wifi-wand and run the CLI as wifi-wand.

macOS helper runtime naming

Legacy helper require path removed

The legacy runtime helper file wifi-wand/mac_helper/mac_os_wifi_auth_helper is no longer shipped.

Code that previously did a direct require such as:

require 'wifi-wand/mac_helper/mac_os_wifi_auth_helper'

must now require the new primary runtime entry point instead:

require 'wifi_wand/platforms/mac/helper/bundle'

Migration

  • old require path: wifi-wand/mac_helper/mac_os_wifi_auth_helper
  • new require path: wifi_wand/platforms/mac/helper/bundle

The legacy constant name WifiWand::MacOsWifiAuthHelper no longer resolves. Load wifi_wand/platforms/mac/helper/bundle and use the supported runtime names directly:

  • WifiWand::Platforms::Mac::Helper::Bundle
  • WifiWand::Platforms::Mac::Helper::Client
  • WifiWand::Platforms::Mac::Helper::Installer

The older nested constants WifiWand::MacOsHelperBundle::Client and WifiWand::MacOsHelperBundle::Installer are also no longer provided.

Verbose API naming

verbose? and verbose= are now the only supported forms

The public verbose and verbose_mode aliases have been removed from the library-facing objects that exposed them.

Use:

  • verbose? to read the flag
  • verbose= to update the flag

This makes the API follow standard Ruby predicate naming instead of supporting multiple reader spellings for the same boolean state.

Error constructor keywords

Several WifiWand::Error subclasses that previously accepted multiple positional constructor arguments now require keyword arguments instead.

This change makes the call sites self-describing and removes ambiguous argument ordering from the error API.

Migration

# Old
raise WifiWand::NetworkConnectionError.new('MyNet', 'timed out')
raise WifiWand::WaitTimeoutError.new(:associated, 5)
raise WifiWand::CommandExecutor::OsCommandError.new(1, 'nmcli', 'boom')

# New
raise WifiWand::NetworkConnectionError.new(network_name: 'MyNet', reason: 'timed out')
raise WifiWand::WaitTimeoutError.new(action: :associated, timeout: 5)
raise WifiWand::CommandExecutor::OsCommandError.new(exitstatus: 1, command: 'nmcli', text: 'boom')

CLI Command Matching

Partial command abbreviations removed

CLI commands no longer accept arbitrary intermediate-length abbreviations between the short form and long form.

Only these forms are now valid:

  • the exact short form, such as co
  • the exact long form, such as connect

Intermediate partial spellings such as con, conn, and connec are now treated as invalid commands and follow the normal invalid-command behavior.

Migration
  • old: wifi-wand conn MyNet
  • new: wifi-wand co MyNet or wifi-wand connect MyNet

Connectivity API

connected_to_internet? replaced by internet_connectivity_state

The old boolean-style connected_to_internet? API has been removed and replaced by internet_connectivity_state.

OldNew
true:reachable
false:unreachable
nil:indeterminate

This change makes uncertainty explicit. :indeterminate means TCP and DNS worked, but captive-portal checks could not determine whether the network is actually open Internet or intercepted.

The companion captive-portal API is now captive_portal_login_required, returning:

  • :yes
  • :no
  • :unknown
Migration
# Old
model.connected_to_internet? == true

# New
model.internet_connectivity_state == :reachable

Callers should replace boolean checks with explicit state comparisons and handle :indeterminate separately where appropriate.

CLI ci output now reports explicit state values

The ci command no longer represents connectivity as true / false.

  • Human-readable output: Internet connectivity: reachable
  • Plain output: reachable, unreachable, or indeterminate
  • JSON output: "reachable", "unreachable", or "indeterminate"

WiFi-off connectivity behavior changed

wifi-wand no longer assumes that WiFi being off means Internet connectivity is unavailable, because connectivity may come from another interface such as Ethernet.

  • The ci command can now report Internet connectivity even when WiFi is off.
  • DNS, TCP, and captive-portal signals are tracked separately in the broader connectivity flow.

Local IP Address Reporting

Local IPv4 address reporting uses ipv4_addresses

The local IPv4 field in info has been renamed from ip_address to ipv4_addresses. info["ipv4_addresses"] and BaseModel#ipv4_addresses now return arrays of IPv4 addresses instead of a single string or nil.

This lets wifi-wand report every IPv4 address assigned to the WiFi interface. Interfaces with no assigned IPv4 address now report an empty array. BaseModel#ip_address has been removed; callers must use BaseModel#ipv4_addresses.

Custom BaseModel subclasses must now implement _ipv4_addresses and _ipv6_addresses. Existing subclasses that implemented _ip_address must move their IPv4 implementation to _ipv4_addresses; _ip_address is no longer part of the subclass contract.

Migration
# Old
info['ip_address'] #=> '192.168.1.100'

# New
info['ipv4_addresses'] #=> ['192.168.1.100']

# Old, no IPv4 address assigned
info['ip_address'] #=> nil

# New, no IPv4 address assigned
info['ipv4_addresses'] #=> []

Callers that only need one address can use info['ipv4_addresses'].first. Callers that previously used nil checks should use info['ipv4_addresses'].any? when testing whether an IPv4 address is present. Callers that display or log the value should handle multiple addresses.

Local IPv6 addresses are now reported

The info command now also reports local IPv6 addresses assigned to the WiFi interface. Use the ipv6_addresses field, which always returns an array.

info['ipv6_addresses'] #=> ['fe80::1', '2001:db8::100']

# No IPv6 address assigned
info['ipv6_addresses'] #=> []

Callers that need both address families should read info['ipv4_addresses'] and info['ipv6_addresses'] separately instead of treating ip_address as a generic local-address field.

Public IP Reporting

Public IP info removed from info

This is a breaking change in both location and data shape.

The info command no longer returns public_ip. The entire info["public_ip"] container has been removed. Public IP lookup is now an explicit CLI feature exposed through public_ip and its short alias pi.

What changed
OldNew
info["public_ip"]wifi-wand public_ip / wifi-wand pi
nested public_ip object inside infodedicated command result
broader unauthenticated IPinfo payloadnarrower result with only address and country
Fields no longer provided

The old info["public_ip"] payload could include these fields, which are no longer returned by the new command:

  • hostname
  • city
  • region
  • loc
  • org
  • postal
  • timezone
  • readme

The new command supports only:

  • address
  • country
Migration
  • old: info["public_ip"]
  • new, both fields: wifi-wand public_ip or wifi-wand pi
  • new, address only: wifi-wand public_ip address or wifi-wand pi a
  • new, country only: wifi-wand public_ip country or wifi-wand pi c
Current result shape
{
  "address": "203.0.113.5",
  "country": "TH"
}

This change keeps info focused on local network state and makes external public-IP lookup explicit.

till Wait States

till wait-state vocabulary redesigned

The till command now uses an explicit, unambiguous vocabulary. The old state names conn, disc, on, and off have been removed.

Why this changed: conn checked full Internet reachability (internet_connectivity_state == :reachable), not WiFi association. That made it easy to confuse "joined the WiFi network" with "has Internet access". The new vocabulary separates those meanings.

New state names
StateMeaning
wifi_onWiFi hardware is powered on
wifi_offWiFi hardware is powered off
associatedWiFi is associated with an SSID (WiFi layer, not Internet)
disassociatedWiFi is not associated with any SSID
internet_onInternet connectivity state is reachable
internet_offInternet connectivity state is unreachable

internet_connectivity_state can also be :indeterminate when TCP and DNS succeed but captive-portal checks cannot determine whether the Internet is actually reachable. There is no dedicated till target for this state: internet_on matches only :reachable, and internet_off matches only :unreachable.

Migration table
Old usageNew usage
till ontill wifi_on
till offtill wifi_off
till conntill internet_on or till associated
till disctill internet_off or till disassociated

Using a removed name now raises an ArgumentError with a clear message listing the valid state names.

Internal behavior changes
  • Connection flows now wait for WiFi association (:associated) rather than Internet reachability.
  • Restore flows wait for WiFi power state (:wifi_on / :wifi_off) as appropriate.

Library Entry Point

WifiWand.create_model now strictly enforces Hash options

The WifiWand.create_model method (and the underlying BaseModel.create_model and WifiWand::Platforms::Selector.create_model_for_current_os) now strictly validates that the options argument is a Hash.

Previously, these methods were more permissive, which allowed passing objects like OpenStruct. They now raise an ArgumentError if a non-Hash is provided.

Migration

If you were passing an OpenStruct or another custom object, convert it to a Hash before calling create_model.

# Old
options = OpenStruct.new(verbose: true)
model = WifiWand.create_model(options)

# New
options = { verbose: true }
model = WifiWand.create_model(options)

CLI and Configuration Changes

Global --verbose now requires an explicit boolean value

The global verbose option now follows the same boolean parsing contract as the global --utc option. The old toggle-style forms no longer work.

Old usageNew usage
wifi-wand -v infowifi-wand -v true info
wifi-wand --verbose infowifi-wand --verbose true info
wifi-wand --no-verbose infowifi-wand --verbose false info
wifi-wand --no-v infowifi-wand -v false info

Accepted true values are true, t, yes, y, and +. Accepted false values are false, f, no, n, and -.

Inline forms are also accepted:

wifi-wand --verbose=true info
wifi-wand -vfalse info

When setting defaults through WIFIWAND_OPTS, include the boolean value:

export WIFIWAND_OPTS="--verbose true"

The log command's command-local --verbose / -v option now uses the same explicit boolean values.

  • WifiWand::Main#parse_command_line has been removed from the public API. If you parsed CLI arguments programmatically, instantiate WifiWand::CommandLineParser and call #parse instead.
  • cycle_network now toggles WiFi state twice regardless of starting state. Previously it unconditionally did off, then on.
  • Removed macOS Speedtest application launch support. The web site is still available via the ro spe command.
  • Removed the public open_application model API. This helper was a thin wrapper around platform launch commands, was not part of the CLI contract, and was not central to WiFi management. If similar behavior is needed again, it can return as a private OS-specific helper instead of a required public model method.
  • Removed fancy_print. Amazing Print is now a required gem, so there is no need for a separate fallback.
  • The -s / --shell command-line option has been replaced with a shell command.
  • All environment variables have been renamed to use the WIFIWAND_ prefix (for example WIFIWAND_VERBOSE and WIFIWAND_OPTS).
  • Removed the l / ls_avail_nets command; it is no longer operational.
  • The --hook option for the log subcommand has been removed. The hook execution feature was incomplete and never properly tested.