How the Interactive REPL Mode Works in UAD-ng CLI: Architecture and Commands
The UAD-ng CLI provides an interactive REPL (Read-Eval-Print Loop) built on the rustyline library that allows users to execute ADB commands, query package states, and manage Android applications through a persistent shell session with command history and tab completion.
The Universal Android Debloater Next Generation (UAD-ng) ships with a powerful command-line interface that includes an interactive REPL mode for real-time device management. This mode, implemented in the uad-cli crate, eliminates the need to repeatedly invoke binary commands by maintaining a persistent session with the Android Debug Bridge (ADB). Understanding how the interactive REPL mode works reveals a well-architected system that balances user experience with robust error handling and device safety.
REPL Initialization and Setup
The entry point for the interactive session is the repl_mode function in crates/uad-cli/src/repl.rs. When a user launches the REPL, the system performs several critical setup operations before accepting input.
First, the function prints a welcome banner and resolves the target device through get_target_device and the current user via get_user. It then loads the UAD package metadata using load_debloat_lists, which provides the debloating recommendations and safety classifications. Simultaneously, the REPL initializes a rustyline::DefaultEditor instance to handle line editing, history navigation, and tab completion. The editor attempts to load cached command history from ~/.cache/uad/cli_history.txt, restoring the user's previous session context if available.
// Simplified initialization flow from repl.rs
fn repl_mode(device: Option<String>, user: Option<String>) -> Result<(), Box<dyn std::error::Error>> {
println!("Universal Android Debloater - Interactive Mode");
let target_device = get_target_device(device)?;
let current_user = get_user(user)?;
let uad_lists = load_debloat_lists()?;
let mut rl = DefaultEditor::new()?;
let _ = rl.load_history("~/.cache/uad/cli_history.txt");
// Main loop begins here...
Ok(())
}
The Main Event Loop
Once initialized, the REPL enters an infinite loop defined in crates/uad-cli/src/repl.rs (lines 45-70). Each iteration presents the user with the uad> prompt and blocks on rl.readline(), waiting for input.
The loop handles three primary scenarios:
- Normal input: The line is dispatched to
handle_repl_linefor parsing and execution - Ctrl-C interruption: Captured as
ReadlineError::Interrupted, displays a cancellation message, and continues the loop - EOF (Ctrl-D): Captured as
ReadlineError::Eof, triggers graceful shutdown
This architecture ensures that transient errors or user interruptions do not terminate the session unexpectedly, maintaining the connection to the device throughout the interactive session.
Command Dispatch and Parsing
When the user submits a line, handle_repl_line (lines 84-112 in repl.rs) processes the input through a standardized pipeline. The function trims whitespace, adds the command to the rustyline history for persistence, and splits the input into whitespace-separated tokens.
The first token serves as the command discriminator, matched against the supported command set: help, exit, list, info, uninstall, enable, disable, device, and clear. Each matching branch forwards the remaining tokens to a specialized handler function that implements the concrete behavior. Unknown commands trigger a help message suggesting valid alternatives.
fn handle_repl_line(line: &str, ctx: &mut ReplContext) -> Result<(), Error> {
let line = line.trim();
ctx.editor.add_history_entry(line)?;
let mut parts = line.split_whitespace();
let cmd = parts.next().unwrap_or("");
match cmd {
"list" => handle_list_command(parts.collect(), ctx),
"info" => handle_info_command(parts.collect(), ctx),
"uninstall" | "enable" | "disable" => {
handle_state_change_command(cmd, parts.collect(), ctx)
}
"exit" | "quit" => std::process::exit(0),
_ => println!("Unknown command. Type 'help' for available commands."),
}
}
Core REPL Commands
Listing Packages with Filtering
The list command leverages handle_list_command to query the device and display filtered results. This function parses optional flags through ReplListArgs::parse, supporting --state (enabled, disabled, uninstalled, or all) and --search (substring matching) parameters.
Internally, the command constructs a PackageListContext and executes an ADB pm list call via the device interface. The raw results pass through display_package_list in crates/uad-cli/src/commands.rs (lines 56-84), which determines each package's runtime state, applies the user-specified filters, and renders the formatted output with UAD list annotations.
uad> list --state enabled --search camera
[🟢] com.android.camera - Camera app (UAD list: base)
[🟢] com.google.android.gms - Google Play services
Total: 42 package(s)
Inspecting Package Metadata
The info <package> command, implemented in handle_info_command, provides detailed intelligence about specific packages. This function retrieves the package name, looks up its entry in the UAD lists to display the removal recommendation (Safe, Unsafe, or Expert) and description, then queries the device for the current runtime state via get_package_state.
The aggregation of static metadata and dynamic state gives users complete visibility before making changes to system applications.
Changing Package States
The uninstall, enable, and disable commands share a unified implementation in handle_state_change_command. This design ensures consistent safety checks and error handling across all state transitions.
For each supplied package name, the function invokes process_package_change, which executes the following sequence:
- Retrieves the current state from the device to prevent redundant operations
- Displays a warning if the package is marked Unsafe in the UAD lists
- Constructs a
CorePackagedescriptor containing the target state and verification requirements - Generates the necessary ADB commands using
apply_pkg_state_commandsfromcrates/uad-core/src/sync.rs - Executes commands via
execute_with_fallbackincrates/uad-cli/src/commands.rs, which validates the final state and attempts recovery if the primary command fails
uad> uninstall com.android.camera
Uninstalling com.android.camera (current: Enabled)
⚠️ Package marked as Unsafe in UAD lists
✓ pm uninstall -k --user 0 com.android.camera
→ Verification passed
Helper Utilities and Error Handling
The REPL relies on several utility layers to maintain clean separation between interface and logic. The ReplListArgs::parse function handles POSIX-style flag parsing, converting --state enabled into typed StateFilter variants. For ADB interactions, execute_with_fallback implements a robust execution strategy that runs commands, captures output, verifies state changes, and triggers alternative approaches (such as user-specific uninstallation) when standard commands fail.
All file system operations, including history persistence, use standard Rust std::fs patterns with error handling that prevents session crashes due to missing cache directories or permission issues.
Session Cleanup and History Persistence
When the user issues exit or quit, or sends an EOF signal, the main loop terminates cleanly. Before returning control to the operating system, repl_mode saves the current command history back to ~/.cache/uad/cli_history.txt through the rustyline editor's save_history method. This ensures that command recall persists across terminal sessions, enhancing the workflow for repetitive debloating tasks.
Summary
- The UAD-ng REPL is implemented in
crates/uad-cli/src/repl.rsusing the rustyline library for line editing and history management - Session initialization resolves the target device, current user, and loads UAD debloat lists before accepting commands
- Command dispatch occurs through
handle_repl_line, which supports listing, inspecting, and modifying package states - State changes use unified logic in
handle_state_change_commandwith safety warnings for Unsafe packages and fallback execution strategies - History persistence automatically saves commands to
~/.cache/uad/cli_history.txtbetween sessions
Frequently Asked Questions
How do I start the interactive REPL mode in UAD-ng?
Launch the REPL by running uad repl from your terminal. The system will auto-detect the connected Android device and current user profile, then present the uad> prompt. You can also specify a device explicitly with uad repl --device <serial>.
What commands are available in the UAD-ng REPL?
The REPL supports nine primary commands: list (with --state and --search filters), info (package metadata), uninstall/enable/disable (state changes), device (show connection info), clear (clear screen), help (command reference), and exit/quit (close session).
How does the REPL handle command history?
The REPL maintains persistent history using the rustyline library, automatically loading previous commands from ~/.cache/uad/cli_history.txt at startup and saving new entries upon exit. Use the Up and Down arrow keys to navigate through previous commands during your session.
What happens if a package state change fails?
The execute_with_fallback function in commands.rs handles failures by first verifying the error type, then attempting alternative ADB strategies (such as targeting specific users or using different PM flags). If all attempts fail, the REPL displays a detailed error message but remains running, allowing you to retry or modify your approach without losing the session context.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →