console-probe CLI
The console-probe package is a generic embedded console scanner. It auto-detects prompts and error strings, parses help output for known commands and submenus, and brute-force probes for hidden commands. While built for the Winegard firmware, it works with any prompt-terminated serial console.
Installation
Section titled “Installation”uv sync # Installs both birdcage and console-probeCLI Usage
Section titled “CLI Usage”console-probe [connection] [discovery overrides] [probing] [output]Quick Examples
Section titled “Quick Examples”# Discover commands via help only (safe, no brute-force)console-probe --port /dev/ttyUSB2 --baud 115200 --discover-only --json /tmp/d.json
# Deep probe: discover + brute-force all submenusconsole-probe --port /dev/ttyUSB2 --baud 115200 --deep --wordlist scripts/wordlists/winegard.txt
# Probe a single submenuconsole-probe --port /dev/ttyUSB2 --baud 115200 --submenu mot
# Custom console (non-Winegard)console-probe --port /dev/ttyACM0 --baud 9600 --prompt "U-Boot>" --error "Unknown command"Connection Options
Section titled “Connection Options”| Option | Default | Description |
|---|---|---|
--port | /dev/ttyUSB0 | Serial port |
--baud | 115200 | Baud rate |
--line-ending | cr | Line ending to send (cr, lf, crlf) |
Discovery Overrides
Section titled “Discovery Overrides”| Option | Default | Description |
|---|---|---|
--prompt | (auto-detect) | Override auto-detected root prompt |
--error | (auto-detect) | Override auto-detected error string |
--help-cmd | ? | Command to request help |
--exit-cmd | q | Command to exit submenu |
Probing Options
Section titled “Probing Options”| Option | Default | Description |
|---|---|---|
--discover-only | false | Discover commands via help only (no brute-force) |
--deep | false | Probe all discovered submenus |
--submenu | (none) | Probe a single submenu by name |
--timeout | 0.5 | Per-command timeout in seconds |
--blocklist | reboot,stow,def,q,Q | Comma-separated commands to never send |
--wordlist | (none) | Extra candidate words file (repeatable) |
--bundled | (none) | Alias for —wordlist (repeatable) |
Output Options
Section titled “Output Options”| Option | Default | Description |
|---|---|---|
--json | (none) | Write results as JSON to FILE |
Operating Modes
Section titled “Operating Modes”Discover-Only Mode
Section titled “Discover-Only Mode”With --discover-only, the tool:
- Auto-detects the root prompt and error string
- Parses root-level help output for commands and submenu names
- Enters each submenu and queries
?(andmanfor paginated help) - Records all discovered commands with descriptions and parameters
- Writes a JSON report if
--jsonis specified
This mode is completely safe — it only sends help commands and submenu navigation.
Standard Probe Mode
Section titled “Standard Probe Mode”Without --discover-only:
- Runs auto-discovery (same as above)
- Generates candidate commands (built-in list + external wordlists)
- Sends each candidate to the root menu
- If
--deepor--submenu, repeats in each submenu - Reports any command that produces a non-error response
Deep Probe Mode
Section titled “Deep Probe Mode”With --deep, the tool probes all discovered submenus:
- Enter each submenu
- Query help (populates
submenu_help) - Brute-force probe all candidates
- Classify results as “known” (in help) or “undiscovered” (probe-only)
- Return to root before entering the next submenu
Auto-Discovery Sequence
Section titled “Auto-Discovery Sequence”The auto_discover() function runs automatically unless both --prompt and --error are provided:
-
Prompt detection: Send a bare line ending, read the response, extract the prompt token from the last line (matches pattern
\S+[>$#]) -
Error string detection: Send a garbage command (
__xyzzy_probe__), extract the error message from the response -
Help parsing: Send
?, parse the output for command names and submenu hints using multiple regex patterns:command - descriptionformatcommand description(double-space separated)- Angle-bracket references like
Enter <a3981> - Bare command names on their own line
-
Submenu registration: Build prompt list from discovered submenus (e.g.,
mot->MOT>)
Data Structures
Section titled “Data Structures”DeviceProfile
Section titled “DeviceProfile”Everything the tool knows (or detected) about the attached console.
@dataclassclass DeviceProfile: port: str = "/dev/ttyUSB0" baud: int = 115200 root_prompt: str = "" # e.g. "TRK>" prompts: list[str] = [] # all known prompts error_string: str = "" # e.g. "Invalid command." known_commands: set[str] = set() # from help output submenus: list[str] = [] # detected submenu names exit_cmd: str = "q" line_ending: str = "\r" submenu_help: dict[str, list[HelpEntry]] = {}HelpEntry
Section titled “HelpEntry”A single command parsed from firmware help output.
@dataclassclass HelpEntry: name: str # command name (lowercase) description: str # help description text params: str # parameter syntax, e.g. "[<motor> [angle]]"JSON Report Format
Section titled “JSON Report Format”The report uses format_version: 2 and includes:
{ "format_version": 2, "device": { "port": "/dev/ttyUSB2", "baud": 115200 }, "detected": { "root_prompt": "TRK>", "error_string": "Invalid command.", "known_commands": ["a3981", "adc", "dvb", "..."], "submenus": ["a3981", "adc", "dvb", "..."] }, "menus": { "MOT": { "prompt": "MOT>", "help_commands": [ { "cmd": "a", "description": "Go to angle [[[motor] [[+|-]angle]]]", "params": "[[[motor] [[+|-]angle]]]" } ], "probe_hits": [ {"cmd": "a", "response": "Angle[0] = 180.00 ..."} ], "undiscovered": [], "stats": { "help_count": 25, "probe_count": 22, "undiscovered_count": 0 } } }, "results": { "TRK": { "total_hits": 3, "known": 3, "unknown": 0, "hits": [...] } }}Report Sections
Section titled “Report Sections”| Section | Description |
|---|---|
device | Serial port and baud rate |
detected | Auto-discovered prompt, error string, known commands, submenus |
menus | Per-submenu structured data: help commands, probe hits, undiscovered commands, stats |
results | Legacy results section (backward compatible): total hits, known/unknown classification |
The menus section is populated when submenu_help is available (from --discover-only or --deep). The undiscovered array contains commands found by brute-force probing that do not appear in the help output.
Candidate Generation
Section titled “Candidate Generation”The built-in candidate list includes:
- All single lowercase and uppercase ASCII letters
- All single digits
- ~150 generic embedded debug commands (memory access, flash, boot/system, debug, shell/OS, network, service/factory, update)
- ~200 two-letter combinations
- External wordlist files (via
--wordlist)
Candidates are deduplicated and filtered through the blocklist before probing. The default blocklist prevents sending reboot, stow, def, q, and Q.
Prompt-Terminated Reading
Section titled “Prompt-Terminated Reading”The serial I/O module (serial_io.py) uses prompt-terminated reading rather than fixed-size buffers. It reads until:
- A known prompt string is found at the end of the response, or
- A
PROMPT_REregex match (\S+[>$#]) appears on the last line (only if no[brackets on that line, to avoid matching parameter syntax), or - The timeout expires
This approach handles variable-length responses and avoids the common bug of truncating long firmware output.
Parameter Placeholder Filtering
Section titled “Parameter Placeholder Filtering”The help parser maintains a set of known parameter placeholder names (command, value, index, motor, angle, etc.) and filters them out to avoid treating help syntax like help [<command>] as a command named “command”. It also checks whether a match falls inside [...] brackets.