domainlooker
Version:
A fast CLI and MCP server for inspecting domains: WHOIS/RDAP, DNS, SSL, ports, subdomains, with CSV/JSON export.
104 lines • 4.31 kB
JavaScript
import { Command } from 'commander';
import { DomainInspector } from './domain-inspector.js';
import { startStdioServer } from './mcp/server.js';
import { isDomainShaped } from './core/target-guard.js';
import { printError } from './ui/effects.js';
import { VERSION } from './version.js';
const RECORD_TYPES = ['A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT'];
const program = new Command();
program
.name('domainlooker')
.description('A fast CLI + MCP server for inspecting domains: WHOIS, DNS, SSL, ports, and subdomains.')
.version(VERSION)
.showHelpAfterError('(run with --help for a list of commands)');
program
.command('inspect', { isDefault: true })
.description('Run a full report (WHOIS, DNS, SSL, ports, advisories)')
.argument('<domains...>', 'One or more domains to inspect')
.option('-v, --verbose', 'Show underlying errors')
.option('-q, --quick', 'Skip the port scan')
.option('--subdomains', 'Also discover subdomains')
.option('-p, --parallel <number>', 'Domains to process in parallel', '3')
.option('--export-csv <file>', 'Write results to a CSV file')
.option('--export-json <file>', 'Write results to a JSON file')
.action(async (domains, options) => {
const invalid = domains.filter(d => !isDomainShaped(d));
if (invalid.length > 0) {
printError(`not a valid domain: ${invalid.map(d => `'${d}'`).join(', ')}`);
console.error("(run 'domainlooker --help' to see available commands)");
process.exitCode = 1;
return;
}
const inspector = new DomainInspector(options);
if (domains.length === 1) {
await inspector.inspect(domains[0]);
}
else {
await inspector.inspectMany(domains, parseInt(options.parallel, 10));
}
});
const singleAspectCommands = [
{ name: 'whois', description: 'Show registration (WHOIS) data', run: (i, d) => i.whoisReport(d) },
{ name: 'dns', description: 'Show DNS records', run: (i, d) => i.dnsReport(d) },
{ name: 'ssl', description: 'Show the SSL certificate', run: (i, d) => i.sslReport(d) },
{ name: 'ports', description: 'Scan for open ports', run: (i, d) => i.portsReport(d) },
{ name: 'subdomains', description: 'Discover subdomains', run: (i, d) => i.subdomainReport(d) },
];
for (const { name, description, run } of singleAspectCommands) {
program
.command(name)
.description(description)
.argument('<domain>', 'Domain to inspect')
.option('-v, --verbose', 'Show underlying errors')
.action(async (domain, options) => {
const inspector = new DomainInspector(options);
const found = await run(inspector, domain);
// Exit non-zero when nothing was found, so scripts can branch on it.
if (!found)
process.exitCode = 1;
});
}
program
.command('propagation')
.alias('propagate')
.description('Check DNS propagation across public resolvers worldwide')
.argument('<domain>', 'Domain to check')
.option('-t, --type <type>', `Record type (${RECORD_TYPES.join(', ')})`, 'A')
.option('--map <file>', 'Also write an interactive world-map HTML report')
.option('-v, --verbose', 'Show underlying errors')
.action(async (domain, options) => {
if (!isDomainShaped(domain)) {
printError(`not a valid domain: '${domain}'`);
process.exitCode = 1;
return;
}
const type = String(options.type).toUpperCase();
if (!RECORD_TYPES.includes(type)) {
printError(`invalid record type '${options.type}' (use one of ${RECORD_TYPES.join(', ')})`);
process.exitCode = 1;
return;
}
const inspector = new DomainInspector(options);
const found = await inspector.propagationReport(domain, type, options.map);
if (!found)
process.exitCode = 1;
});
program
.command('mcp')
.description('Run as an MCP server over stdio (for AI agents/clients)')
.action(async () => {
// stdout is reserved for the MCP protocol, so the process stays otherwise silent.
await startStdioServer();
});
async function main() {
try {
await program.parseAsync(process.argv);
}
catch (error) {
printError(String(error));
process.exitCode = 1;
}
}
main();
//# sourceMappingURL=index.js.map