pub struct ReplCommand {
pub name: &'static str,
pub usage: &'static str,
pub about: &'static str,
}
const fn cmd(name: &'static str, usage: &'static str, about: &'static str) -> ReplCommand {
ReplCommand { name, usage, about }
}
pub struct Section {
pub title: &'static str,
pub commands: &'static [ReplCommand],
pub note: Option<fn() -> String>,
}
pub const SECTIONS: &[Section] = &[
Section {
title: "LOOKUP COMMANDS",
commands: &[
cmd(
"lookup",
"<domain>",
"Smart lookup (or just type a domain directly)",
),
cmd(
"info",
"<domain>",
"Comprehensive domain info (RDAP + WHOIS merged)",
),
cmd("whois", "<domain>", "Query WHOIS information"),
cmd("rdap", "<domain|ip|asn>", "Query RDAP registry data"),
],
note: None,
},
Section {
title: "DNS COMMANDS",
commands: &[
cmd(
"dig",
crate::dig_args::USAGE,
"Query DNS records like dig (-x <ip>: reverse)",
),
cmd("prop", "<domain> [type]", "Check DNS propagation globally"),
cmd(
"follow",
crate::dns_args::FOLLOW_USAGE,
"Monitor DNS records over time",
),
cmd(
"compare",
crate::dns_args::COMPARE_USAGE,
"Compare DNS records across nameservers",
),
cmd(
"delegation",
"<domain>",
"Check NS delegation health (parent vs zone, lameness)",
),
],
note: Some(|| format!("Record types: {}", *crate::VALID_RECORD_TYPES)),
},
Section {
title: "UTILITY COMMANDS",
commands: &[
cmd("reverse", "<ip>", "Reverse DNS lookup for an IP"),
cmd(
"avail",
"<domain>",
"Check domain registration availability",
),
cmd("dnssec", "<domain>", "Check DNSSEC configuration"),
cmd(
"tld",
"<tld>",
"Look up TLD info (WHOIS server, RDAP, registry)",
),
cmd(
"subdomains",
"<domain> [--resolve | --diff] [--record]",
"Enumerate subdomains via CT logs (classify, baseline diff)",
),
cmd(
"doctor",
"",
"Diagnose seer environment (config, DNS, WHOIS, RDAP)",
),
],
note: None,
},
Section {
title: "STATUS & SSL",
commands: &[
cmd(
"status",
"<domain>",
"Check HTTP, SSL, and domain expiration",
),
cmd("ssl", "<domain>", "Inspect SSL certificate chain and SANs"),
],
note: None,
},
Section {
title: "SECURITY",
commands: &[
cmd("caa", "<domain>", "Look up CAA (cert authority) policy"),
cmd(
"posture",
"<domain>",
"Email/DNS posture (SPF, DMARC, MTA-STS, BIMI, DANE)",
),
cmd(
"headers",
"<domain>",
"Audit HTTP security headers + cookie flags",
),
cmd(
"takeover",
"<domain> [--host <host>]...",
"Scan subdomains for takeover exposure",
),
cmd(
"confusables",
"<domain>",
"Find registered look-alike domains",
),
],
note: None,
},
Section {
title: "COMPARISON",
commands: &[cmd(
"diff",
"<domain1> <domain2>",
"Compare two domains side-by-side",
)],
note: None,
},
Section {
title: "MONITORING",
commands: &[
cmd(
"watch",
"[add|remove <domain>... | list]",
"Check watchlist / add / remove / list",
),
cmd("history", "[<domain> | --clear]", "View lookup history"),
cmd(
"drift",
"<domain> [--record]",
"Detect drift vs the last stored lookup",
),
],
note: None,
},
Section {
title: "BULK OPERATIONS",
commands: &[cmd(
"bulk",
"<operation> <file> [type] [-o output.csv] [--progress <mode>]",
"Run bulk operations from file (bulk -h for details)",
)],
note: Some(|| format!("Operations: {}", *crate::ops::BULK_OPS_SUMMARY)),
},
Section {
title: "SETTINGS",
commands: &[
cmd(
"set",
"output <human|json|yaml|markdown>",
"Change output format",
),
cmd(
"copy",
"[markdown|json|yaml]",
"Copy last result to clipboard (default: markdown)",
),
cmd("clear", "", "Clear screen"),
cmd("help", "", "Show this help"),
cmd("exit", "", "Exit the program"),
],
note: None,
},
];
pub const ALIASES: &[(&str, &str)] = &[
("dns", "dig"),
("propagation", "prop"),
("subs", "subdomains"),
("?", "help"),
("quit", "exit"),
("q", "exit"),
];
pub fn commands() -> impl Iterator<Item = &'static ReplCommand> {
SECTIONS.iter().flat_map(|section| section.commands)
}
pub fn canonical(word: &str) -> &str {
ALIASES
.iter()
.find(|(alias, _)| *alias == word)
.map_or(word, |(_, name)| name)
}
pub fn find(word: &str) -> Option<&'static ReplCommand> {
let name = canonical(word);
commands().find(|c| c.name == name)
}
pub fn usage(word: &str) -> String {
match find(word) {
Some(c) => format!("Usage: {} {}", c.name, c.usage)
.trim_end()
.to_string(),
None => format!("Usage: {}", word),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn names_and_aliases_are_unique_and_aliases_resolve() {
let mut seen = std::collections::HashSet::new();
for word in commands()
.map(|c| c.name)
.chain(ALIASES.iter().map(|(a, _)| *a))
{
assert!(seen.insert(word), "{word} is listed twice");
}
for (alias, name) in ALIASES {
assert!(find(alias).is_some_and(|c| c.name == *name), "{alias}");
}
}
#[test]
fn usage_names_the_canonical_command() {
assert_eq!(
usage("dns"),
"Usage: dig [@server] <name> [type...] [+short] [+trace]"
);
assert_eq!(usage("whois"), "Usage: whois <domain>");
}
}