app
Adom USB
Public Unreviewedby John Lauer
See every USB device across the entire Adom ecosystem — laptop, WSL2, cloud container, Azure VM, workcells — in one dashboard, and mount / unmount / reset / test / force-class / sniff any of them. USB/IP slingshot for the whole mesh.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
//! adom-usb: the USB device mesh dashboard.
//!
//! See every USB device across the Adom ecosystem — your laptop, its WSL2 /
//! Hydrogen Desktop, this cloud container, the Azure VM and its WSL2, and
//! workcells — in one table, and mount / unmount / reset / test / force-class /
//! sniff any of them. The dashboard is a Hydrogen webview backed by a tiny HTTP
//! server; every button is also a plain HTTP endpoint, so the AI drives it the
//! same way the human does.
mod app;
mod cli;
mod devices;
mod install;
mod plugins;
use clap::{CommandFactory, Parser, Subcommand};
use std::io::IsTerminal;
use std::process::ExitCode;
use std::sync::OnceLock;
pub const GREEN: &str = "\x1b[32m";
pub const RED: &str = "\x1b[31m";
pub const YELLOW: &str = "\x1b[33m";
pub const CYAN: &str = "\x1b[36m";
pub const BOLD: &str = "\x1b[1m";
pub const DIM: &str = "\x1b[2m";
pub const RESET: &str = "\x1b[0m";
static STDOUT_IS_TTY: OnceLock<bool> = OnceLock::new();
static STDERR_IS_TTY: OnceLock<bool> = OnceLock::new();
pub fn stdout_is_tty() -> bool { *STDOUT_IS_TTY.get_or_init(|| std::io::stdout().is_terminal()) }
pub fn stderr_is_tty() -> bool { *STDERR_IS_TTY.get_or_init(|| std::io::stderr().is_terminal()) }
pub fn strip_ansi(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c == '\x1b' && chars.peek() == Some(&'[') {
chars.next();
for c2 in chars.by_ref() { if c2.is_ascii_alphabetic() { break; } }
continue;
}
out.push(c);
}
out
}
pub fn ok(m: impl std::fmt::Display) {
if stdout_is_tty() { println!("{GREEN}{BOLD}OK:{RESET} {m}"); } else { println!("OK: {}", strip_ansi(&m.to_string())); }
}
pub fn err(m: impl std::fmt::Display) {
if stderr_is_tty() { eprintln!("{RED}{BOLD}ERROR:{RESET} {m}"); } else { eprintln!("ERROR: {}", strip_ansi(&m.to_string())); }
}
pub fn warn(m: impl std::fmt::Display) {
if stderr_is_tty() { eprintln!("{YELLOW}{BOLD}WARN:{RESET} {m}"); } else { eprintln!("WARN: {}", strip_ansi(&m.to_string())); }
}
pub fn hint(m: impl std::fmt::Display) {
if stderr_is_tty() { eprintln!("{DIM}Hint:{RESET} {m}"); } else { eprintln!("Hint: {}", strip_ansi(&m.to_string())); }
}
#[macro_export]
macro_rules! note {
($($arg:tt)*) => {{
let __s = format!($($arg)*);
if $crate::stdout_is_tty() { println!("{}", __s); } else { println!("{}", $crate::strip_ansi(&__s)); }
}};
}
#[derive(Parser)]
#[command(name = "adom-usb", version, about = "USB device mesh dashboard — see and route every USB device across the Adom ecosystem")]
pub struct Cli {
#[command(subcommand)]
cmd: Cmd,
}
#[derive(Subcommand)]
enum Cmd {
/// Open the Hydrogen webview dashboard (starts the HTTP server).
App(AppArgs),
/// Print the unified device inventory as JSON (no server needed).
List {
/// Seed the representative cross-ecosystem demo set on top of real lsusb.
#[arg(long)]
demo: bool,
},
/// Mount a device onto a target host (POSTs to a running `app` server).
Mount(MountArgs),
/// Unmount a device from wherever it is attached.
Unmount(UnmountArgs),
/// Flash a stub-firmware persona onto a device (e.g. RP2040 -> HID keyboard)
/// via a matching firmware device-test pack.
Flash(FlashArgs),
/// Check whether a running dashboard server is reachable.
Health {
#[arg(long, default_value_t = 8896)]
port: u16,
},
/// List installed sniffer/decoder plugins, or search the wiki for more.
Plugins {
#[command(subcommand)]
sub: PluginsCmd,
},
/// Install SKILL.md + bash completions.
Install,
/// Print shell completion script.
Completions { shell: clap_complete::Shell },
}
#[derive(clap::Args)]
pub struct AppArgs {
#[arg(long, default_value_t = 8896)] pub port: u16,
#[arg(long, default_value = "USB Devices")] pub tab_name: String,
#[arg(long)] pub no_tab: bool,
/// Seed a representative cross-ecosystem device set so the dashboard is
/// demonstrable before the live agents are wired.
#[arg(long, env = "ADOM_USB_DEMO")] pub demo: bool,
#[arg(long, env = "ADOM_USB_DEV")] pub dev: bool,
}
#[derive(Subcommand)]
pub enum PluginsCmd {
/// List plugins installed on this host.
List,
/// Search the Adom Wiki for publishable adom-usb plugins.
Search,
}
#[derive(clap::Args)]
pub struct MountArgs {
pub device_id: String,
pub target_id: String,
#[arg(long, default_value_t = 8896)] pub port: u16,
}
#[derive(clap::Args)]
pub struct UnmountArgs {
pub device_id: String,
#[arg(long, default_value_t = 8896)] pub port: u16,
}
#[derive(clap::Args)]
pub struct FlashArgs {
pub device_id: String,
/// Persona id from the device's firmware_personas (e.g. hid-keyboard, msc-disk).
pub persona: String,
#[arg(long, default_value_t = 8896)] pub port: u16,
}
fn main() -> ExitCode {
let args = Cli::parse();
let result = match args.cmd {
Cmd::App(a) => app::run(a),
Cmd::List { demo } => cli::run_list(demo),
Cmd::Mount(a) => cli::run_mount(a),
Cmd::Unmount(a) => cli::run_unmount(a),
Cmd::Flash(a) => cli::run_flash(a),
Cmd::Health { port } => cli::run_health(port),
Cmd::Plugins { sub } => cli::run_plugins(sub),
Cmd::Install => install::install(),
Cmd::Completions { shell } => {
let mut cmd = Cli::command();
clap_complete::generate(shell, &mut cmd, "adom-usb", &mut std::io::stdout());
Ok(())
}
};
match result { Ok(()) => ExitCode::SUCCESS, Err(e) => { err(&e); ExitCode::FAILURE } }
}