whodis

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 10, 2026 License: MIT Imports: 23 Imported by: 0

README

whodis

CI Latest release

A modern WHOIS alternative that automatically uses RDAP where traditional WHOIS falls short.

The registry world is split between old-school WHOIS and modern RDAP. whodis hides that split: give it a domain, IP address, network, or ASN and it finds the right service automatically. No protocol trivia required.

Instead of dumping a wall of registry text, it can turn the answer into a compact terminal dashboard, a hierarchical tree, a 2002-style ASCII layout, or clean plain text. Registration facts, dates, DNS, contacts, and routing details each get the space they need.

$ whodis example.com
╭─ Registration ──────────────────────╮ ╭─ Contacts · 1 ───────────────────────╮
│ [CLIENT TRANSFER PROHIBITED]        │ │ REGISTRANT / TECHNICAL               │
│                                     │ │   Example Registry · id-1234         │
│ Name       example.com              │ ╰──────────────────────────────────────╯
│ Handle     2336799_DOMAIN_COM-VRSN  │
│ Registrar  Example Registrar Inc.   │ ╭─ Timeline · 3 ───────────────────────╮
╰─────────────────────────────────────╯ │ Registered  1995-08-14               │
                                        │ Expires     2026-08-13               │
╭─ DNS · 2 ───────────────────────────╮ │ Updated     2025-08-14               │
│ DNSSEC  signed delegation           │ ╰──────────────────────────────────────╯
│                                     │
│ • A.IANA-SERVERS.NET                │ ╭─ Source ─────────────────────────────╮
│ • B.IANA-SERVERS.NET                │ │ Protocol   RDAP                      │
╰─────────────────────────────────────╯ │ Authority  rdap.example.net          │
                                        │ Discovery  IANA bootstrap            │
                                        │ Notices    2 hidden · use --details  │
                                        ╰──────────────────────────────────────╯

The dashboard adapts to the terminal: wide windows use a multi-panel mosaic, while narrow windows stack the same semantic panels into one column. Contacts stay visible but are consolidated instead of repeated. Lengthy registry notices are summarized by count; add --details when you want their full text and links. The flag also expands notices in the tree and GeekBoys views; plain and machine-readable formats remain unchanged.

What you get

  • Automatic protocol selection — RDAP for registries that support it, WHOIS where it is still needed.
  • Four terminal views — switch between the responsive dashboard, a semantic tree, a retro ASCII layout, and plain text.
  • Persistent preferences — save your favorite view once and use it automatically in terminals and pipelines.
  • Script-friendly formats — output plain text, JSON, YAML, Markdown, or the raw registry response.
  • Direct file export — use --output result.json and Whodis infers the format from the extension.
  • More than domains — look up IPv4, IPv6, CIDR networks, and autonomous system numbers such as AS15169.
  • Cross-platform builds — the same CLI is designed for Linux, macOS, and Windows.

Install

Linux and macOS
curl -fsSL https://github.com/Alex9001/whodis/releases/latest/download/install.sh | sh

The installer detects your operating system and CPU, verifies the release checksum, and places whodis in /usr/local/bin. It asks for sudo only if that directory is not writable.

Windows

Run in PowerShell—no administrator window is required:

irm https://github.com/Alex9001/whodis/releases/latest/download/install.ps1 | iex

The installer puts Whodis under your local application-data directory and adds it to your user PATH without creating duplicate entries.

With Go

If you already have Go installed:

go install github.com/Alex9001/whodis/cmd/whodis@latest

Go installs the binary into GOBIN (normally ~/go/bin). Make sure that directory is on PATH.

Prebuilt archives for every supported platform and checksums.txt are also available on the latest GitHub Release.

The source-built AUR package is prepared but cannot be submitted until the maintainer's AUR account is registered. The exact one-time publication steps are preserved in AUR_HANDOFF.md; after publication Arch users will be able to install it with yay -S whodis or paru -S whodis.

Quick start

Once installed, use it from any directory:

whodis google.com

Choose your view

Use --format to change one lookup:

whodis example.com --format dashboard  # current responsive grid
whodis example.com --format tree
whodis example.com --format geekboys
whodis example.com --format plain

The tree uses the queried target as its root without repeating it inside the Registration panel:

example.com
├── Registration
│   ├── Status
│   │   └── CLIENT TRANSFER PROHIBITED
│   ├── Handle: 2336799_DOMAIN_COM-VRSN
│   └── Registrar: Example Registrar Inc.
├── DNS · 2
│   └── Nameservers
│       ├── A.IANA-SERVERS.NET
│       └── B.IANA-SERVERS.NET
└── Source
    ├── Protocol: RDAP
    └── Discovery: IANA bootstrap

The headerless geekboys view uses responsive ASCII-only geometry inspired by the 2002 GeekBoys community layout:

.--- Registration ---------------------+
| + CLIENT TRANSFER PROHIBITED +       |
|                                      |
| Name     : example.com               |
| Handle   : 2336799_DOMAIN_COM-VRSN   |
| Registrar: Example Registrar Inc.    |
+--------------------------------------'

Save a default view when you want the same choice in future terminals and stdout pipelines:

whodis config set format tree
whodis config get format      # tree
whodis config unset format    # restore automatic dashboard/plain selection
whodis config path            # show the platform-specific config file

Because config is a command name, query that exact target with whodis -- config.

Saved defaults may be dashboard, tree, geekboys, or plain. An explicit --format always wins. WHODIS_FORMAT provides a temporary environment override and can also select JSON, YAML, Markdown, or raw output. Named output files continue to take their format from the extension.

Whodis stores this preference in whodis/config.json below the operating system's user configuration directory: normally ~/.config on Linux, ~/Library/Application Support on macOS, and %AppData% on Windows. Use whodis config path for the exact location.

Common examples

# Domains use RDAP or WHOIS automatically
whodis example.com

# IP addresses and ASNs work the same way
whodis 8.8.8.8
whodis AS15169

# Print machine-readable data
whodis example.com --format json

# Try another terminal layout for one lookup
whodis example.com --format tree

# Make the ASCII view your default in terminals and pipelines
whodis config set format geekboys

# Expand registry notices in the structured terminal views
whodis example.com --details

# Export to a file; .yaml selects YAML automatically
whodis AS15169 --output google-asn.yaml

# Get clean, unstyled terminal text
whodis example.com --format plain

Command-line options

whodis <target> [options]
whodis config set format dashboard|tree|geekboys|plain
whodis config get format
whodis config unset format
whodis config path

-f, --format dashboard|tree|geekboys|plain|json|yaml|markdown|raw
-o, --output <file|->
    --protocol auto|rdap|whois
    --fallback unavailable|none|any-error
    --server <endpoint>
    --timeout <duration>
    --refresh-bootstrap
    --color auto|always|never
    --details
    --force
-h, --help
    --version

pretty, grid, and current are aliases for dashboard; retro and geek-boys are aliases for geekboys. When --format is omitted with --output, Whodis infers JSON, YAML, Markdown, tree, GeekBoys, or plain text from the filename. Existing files are protected unless --force is supplied.

How protocol selection works

Whodis fetches and caches IANA's RDAP bootstrap registries for domains, IPv4, IPv6, and ASNs. Domain routes use the longest matching registry suffix, IP routes use the longest matching network prefix, and ASN routes use IANA's number ranges. HTTPS endpoints are preferred, and secondary endpoints are tried before switching protocols.

When IANA does not list an RDAP service for a target, Whodis asks whois.iana.org for the authoritative WHOIS server and follows a bounded referral chain. The first registration lookup therefore goes to the protocol known for that target instead of blindly trying services in sequence.

The default --fallback unavailable mode tries the alternate protocol only when the selected service is unavailable or unusable. Authoritative not-found and rate-limit responses remain visible. Use --fallback any-error for wider diagnostic coverage or --fallback none for a strict single-protocol lookup.

Go API and future interfaces

The protocol engine is independent of terminal rendering. That keeps the same lookup and normalized result model reusable from a future Wails desktop interface or another Go application.

The public API is centered on:

import (
    "os"

    "github.com/Alex9001/whodis"
)

client := whodis.NewClient(whodis.ClientOptions{})
route, err := client.Route(ctx, "example.com", whodis.LookupOptions{})
result, err := client.Lookup(ctx, "example.com", whodis.LookupOptions{})
err = whodis.Render(os.Stdout, result, whodis.FormatTree, whodis.RenderOptions{})

LookupResult is a versioned model containing the query, routing decision, normalized registration data, notices, and registry sources. Native RDAP JSON and raw WHOIS text remain available through --format raw.

Additional protocols can implement ProtocolAdapter without coupling them to the CLI or a particular operating system.

Development

git clone https://github.com/Alex9001/whodis.git
cd whodis
go test ./...
go vet ./...
go run ./cmd/whodis example.com

Tests use local normalizer and renderer fixtures; public registries are not queried during the test suite. Live checks are intentionally manual because registry availability and query limits are external conditions.

Current limitations

The current release implements RDAP and standard port-43 WHOIS. RWhois, proprietary registration APIs, authenticated RDAP, web scraping, desktop GUI, and mobile apps are not implemented yet.

License

MIT © 2026 Aleksandr Oreshkin. See LICENSE.

Documentation

Overview

Package whodis provides protocol-aware registration-data lookups.

Its public API intentionally has no terminal or GUI dependency, so the same Client can power the command-line tool and a future desktop application.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Render

func Render(writer io.Writer, result LookupResult, format Format, options RenderOptions) error

Render writes one successful result in the requested format.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is safe to reuse for multiple sequential or concurrent lookups.

func NewClient

func NewClient(options ClientOptions) *Client

NewClient creates a protocol-aware lookup client. It makes no network requests until Route or Lookup is called.

func (*Client) Lookup

func (c *Client) Lookup(ctx context.Context, input string, options LookupOptions) (LookupResult, error)

Lookup resolves one target and returns a stable, renderer-independent model.

func (*Client) Route

func (c *Client) Route(ctx context.Context, input string, options LookupOptions) (RouteDecision, error)

Route decides which known authority should receive a lookup without making a registration-data query. WHOIS route discovery may query IANA's referral service when the selected protocol is WHOIS.

type ClientOptions

type ClientOptions struct {
	Timeout        time.Duration
	CacheDirectory string
	Adapters       []ProtocolAdapter
}

ClientOptions configures a reusable lookup client.

type Entity

type Entity struct {
	Roles        []string `json:"roles,omitempty" yaml:"roles,omitempty"`
	Handle       string   `json:"handle,omitempty" yaml:"handle,omitempty"`
	Name         string   `json:"name,omitempty" yaml:"name,omitempty"`
	Organization string   `json:"organization,omitempty" yaml:"organization,omitempty"`
	Email        string   `json:"email,omitempty" yaml:"email,omitempty"`
	Phone        string   `json:"phone,omitempty" yaml:"phone,omitempty"`
}

Entity contains public contact or organization information. Fields that are redacted by a registry remain absent rather than being invented.

type ErrorKind

type ErrorKind string

ErrorKind allows command-line callers and future UIs to handle lookup failures without string matching.

const (
	ErrorInvalidInput ErrorKind = "invalid_input"
	ErrorNotFound     ErrorKind = "not_found"
	ErrorRateLimited  ErrorKind = "rate_limited"
	ErrorDiscovery    ErrorKind = "discovery"
	ErrorUnavailable  ErrorKind = "unavailable"
	ErrorProtocol     ErrorKind = "protocol"
)

type Event

type Event struct {
	Action string `json:"action" yaml:"action"`
	Date   string `json:"date" yaml:"date"`
}

Event is a dated registration event such as registration or expiration.

type FallbackMode

type FallbackMode string

FallbackMode controls whether Whodis tries the other protocol after its knowledge-based primary route fails.

const (
	FallbackUnavailable FallbackMode = "unavailable"
	FallbackNone        FallbackMode = "none"
	FallbackAnyError    FallbackMode = "any-error"
)

type Format

type Format string

Format is an output representation for a LookupResult.

const (
	FormatPretty   Format = "pretty"
	FormatTree     Format = "tree"
	FormatGeekBoys Format = "geekboys"
	FormatPlain    Format = "plain"
	FormatJSON     Format = "json"
	FormatYAML     Format = "yaml"
	FormatMarkdown Format = "markdown"
	FormatRaw      Format = "raw"
)

func ParseFormat

func ParseFormat(value string) (Format, error)

ParseFormat validates a CLI-facing output format. Common descriptions of the human-facing renderers are accepted as friendly aliases.

type Kind

type Kind string

Kind is the kind of registration object represented by a Target.

const (
	KindDomain Kind = "domain"
	KindIP     Kind = "ip"
	KindASN    Kind = "asn"
)

type LookupError

type LookupError struct {
	Kind    ErrorKind
	Message string
	Cause   error
}

LookupError wraps a failure with an actionable classification.

func (*LookupError) Error

func (e *LookupError) Error() string

func (*LookupError) Unwrap

func (e *LookupError) Unwrap() error

type LookupOptions

type LookupOptions struct {
	Protocol         Protocol
	Fallback         FallbackMode
	Server           string
	Timeout          time.Duration
	RefreshBootstrap bool
}

LookupOptions controls one lookup. A zero-value options struct uses the knowledge-based automatic protocol router and unavailable-only fallback.

type LookupResult

type LookupResult struct {
	SchemaVersion int            `json:"schema_version" yaml:"schema_version"`
	Query         Target         `json:"query" yaml:"query"`
	Route         RouteDecision  `json:"route" yaml:"route"`
	FallbackFrom  *RouteDecision `json:"fallback_from,omitempty" yaml:"fallback_from,omitempty"`
	RetrievedAt   time.Time      `json:"retrieved_at" yaml:"retrieved_at"`
	Object        Object         `json:"object" yaml:"object"`
	Sources       []Source       `json:"sources" yaml:"sources"`
}

LookupResult is the serializable response returned by Client.Lookup.

type Notice

type Notice struct {
	Title       string   `json:"title,omitempty" yaml:"title,omitempty"`
	Description []string `json:"description,omitempty" yaml:"description,omitempty"`
	Links       []string `json:"links,omitempty" yaml:"links,omitempty"`
}

Notice is a registry-supplied legal or service notice.

type Object

type Object struct {
	Kind         Kind                `json:"kind" yaml:"kind"`
	Handle       string              `json:"handle,omitempty" yaml:"handle,omitempty"`
	Name         string              `json:"name,omitempty" yaml:"name,omitempty"`
	UnicodeName  string              `json:"unicode_name,omitempty" yaml:"unicode_name,omitempty"`
	Status       []string            `json:"status,omitempty" yaml:"status,omitempty"`
	Events       []Event             `json:"events,omitempty" yaml:"events,omitempty"`
	Nameservers  []string            `json:"nameservers,omitempty" yaml:"nameservers,omitempty"`
	Entities     []Entity            `json:"entities,omitempty" yaml:"entities,omitempty"`
	Registrar    string              `json:"registrar,omitempty" yaml:"registrar,omitempty"`
	Registry     string              `json:"registry,omitempty" yaml:"registry,omitempty"`
	DNSSEC       string              `json:"dnssec,omitempty" yaml:"dnssec,omitempty"`
	StartAddress string              `json:"start_address,omitempty" yaml:"start_address,omitempty"`
	EndAddress   string              `json:"end_address,omitempty" yaml:"end_address,omitempty"`
	CIDR         []string            `json:"cidr,omitempty" yaml:"cidr,omitempty"`
	Country      string              `json:"country,omitempty" yaml:"country,omitempty"`
	NetworkType  string              `json:"network_type,omitempty" yaml:"network_type,omitempty"`
	ASN          string              `json:"asn,omitempty" yaml:"asn,omitempty"`
	ASNName      string              `json:"asn_name,omitempty" yaml:"asn_name,omitempty"`
	ASNType      string              `json:"asn_type,omitempty" yaml:"asn_type,omitempty"`
	Notices      []Notice            `json:"notices,omitempty" yaml:"notices,omitempty"`
	Extras       map[string][]string `json:"extras,omitempty" yaml:"extras,omitempty"`
}

Object is Whodis's stable normalized registration-data model. Extras keeps protocol or registry-specific values that do not fit the common fields.

type Protocol

type Protocol string

Protocol is a registration-data transport.

const (
	ProtocolAuto  Protocol = "auto"
	ProtocolRDAP  Protocol = "rdap"
	ProtocolWHOIS Protocol = "whois"
)

type ProtocolAdapter

type ProtocolAdapter interface {
	Protocol() Protocol
	Lookup(ctx context.Context, target Target, route RouteDecision) (Object, []Source, error)
}

ProtocolAdapter is the extension point for registration-data protocols. Implementations are supplied to NewClient rather than loaded through Go's platform-limited plugin mechanism.

type RenderOptions

type RenderOptions struct {
	Color   string
	Width   int
	Details bool
}

RenderOptions changes presentation only; it never changes lookup behavior.

type RouteDecision

type RouteDecision struct {
	Protocol        Protocol `json:"protocol" yaml:"protocol"`
	Endpoint        string   `json:"endpoint" yaml:"endpoint"`
	Alternates      []string `json:"alternates,omitempty" yaml:"alternates,omitempty"`
	DiscoverySource string   `json:"discovery_source" yaml:"discovery_source"`
	Reason          string   `json:"reason" yaml:"reason"`
}

RouteDecision records why an authority and protocol were selected.

type Source

type Source struct {
	Protocol  Protocol `json:"protocol" yaml:"protocol"`
	Endpoint  string   `json:"endpoint" yaml:"endpoint"`
	Authority string   `json:"authority,omitempty" yaml:"authority,omitempty"`
	Raw       string   `json:"-" yaml:"-"`
}

Source is one registry response used to construct a result.

type Target

type Target struct {
	Original  string `json:"original" yaml:"original"`
	Canonical string `json:"canonical" yaml:"canonical"`
	Kind      Kind   `json:"kind" yaml:"kind"`
}

Target is a validated, canonical lookup input.

func ParseTarget

func ParseTarget(input string) (Target, error)

ParseTarget classifies and canonicalizes one user supplied lookup target.

Directories

Path Synopsis
cmd
whodis command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL