Need to track down that Raspberry Pi you swore was on the network five minutes ago? Pi-Spy is still your over-caffeinated sidekick, but now it speaks fluent multi-interface, mDNS, and Tailscale. It enumerates every up IPv4 interface, sweeps each CIDR, folds in tailnet intel, and lines up the candidates in a numbered K.A.R.I. list so you can lock onto the right board and SSH in with a single keystroke.
# run the launcher (manages a venv, sets PYTHONPATH, execs `python -m Pi-Spy`)
./pi-spy
# include offline devices (cached or currently sleeping)
./pi-spy --include-offline
# jump straight to a known host (substring match) and ssh
./pi-spy --connect atrium --action ssh
# focus on tailnet entries and print multi-line details
./pi-spy --tailnet-only --action details
# merge cached-but-offline devices back into the list
./pi-spy --use-cache --include-offline --action details(./bin/Pi-Spy still works if you prefer the original launcher.)
ip -o -4 addr showis parsed to enumerate every up, non-loopback IPv4 address and its CIDR.- Each interface CIDR is scanned with
nmap -sn; in--fastmode large networks are trimmed to the first 64 hosts. - If available,
avahi-browse -rt _ssh._tcpcollects mDNS announcements for_ssh._tcpendpoints. tailscale status --json(ortailscale status) injects tailnet peers when--tailscaleis enabled.- Optional
arp-scan --localnetruns (per interface) when--privilegedis provided and the command exists. - Every discovered IP is probed with a short TCP connect to port 22 to confirm SSH reachability.
- Records are deduplicated by MAC or IP, merged across sources, scored, and streamed into a K.A.R.I.-styled numbered list (online devices first by default):
[K.A.R.I.]: Pi-Spy with my little eye… 4 candidates across 2 interface(s). Filters: online only, tailnet-only.
1. [ 90] atrium / 100.79.139.57 (tailnet, ssh)
2. [ 80] freyja-nix / 100.103.68.25 (tailnet, ssh)
3. [ 50] jellyfin / 100.106.56.126 (tailnet, offline)
4. [ 10] 10.50.16.114 / 10.50.16.114 (lan, offline, cached)
Select device # (ENTER=cancel):
Choose a number and Pi-Spy immediately runs ssh user@best_ip, preferring tailnet endpoints when they’re reachable.
| Flag | Description |
|---|---|
--json |
Print the (filtered) device array as JSON and exit. |
--fast |
Limit nmap -sn to the first 64 hosts on large CIDRs (mDNS / tailnet still run). |
--tailscale / --no-tailscale |
Toggle tailnet discovery (auto-on when the CLI is present). |
--cache / --no-cache |
Read/write ~/.config/pi-spy/devices.json to track last_seen history. |
--privileged |
Allow arp-scan --localnet (requires root or CAP_NET_RAW). |
--ssh-timeout SECS |
Tune the SSH probe timeout (default 1.0). |
--action {ssh,print,details} |
Pick post-selection behavior (default ssh, or print when --json). |
--connect TOKEN |
Skip the menu and match a device by id/hostname substring. |
--user, --identity, --port |
Passed straight through to the ssh command (--user overrides the cached/prompted username). |
--tailnet-only, --lan-only |
Filter the candidate list before rendering/output. |
--include-offline |
Show offline devices (default view hides them). |
--use-cache |
Merge cached-but-offline devices back into the list (marked as cached). |
Interactive mode now defaults to an SSH connect (using tailnet IPs when confirmed reachable). If Pi-Spy hasn’t seen a username for the chosen device yet, it asks right after you pick it and remembers the answer for next time. Switch to --action print or --action details when you need structured data instead of a live shell, or set --connect <slug> to bypass the prompt entirely.
Cache entries live at ~/.config/pi-spy/devices.json by default (override with PI_SPY_CACHE). Each device record includes:
{
"id": "AA:BB:CC:DD:EE:FF",
"hostname": "mypi.local",
"tailscale_name": "mypi.ts.net",
"ip_local": ["10.0.0.50"],
"ip_tailscale": ["100.100.100.5"],
"mac": "AA:BB:CC:DD:EE:FF",
"vendor": "Raspberry Pi Foundation",
"score": 90,
"sources": ["nmap", "mdns", "tailscale", "ssh"],
"last_seen": "2024-05-01T12:00:00.000000+00:00"
}
--json mode prints an array of these objects; interactive mode uses the same data to populate the numbered list.
Pi-Spy remembers the SSH username you enter per device (stored in ~/.config/pi-spy/devices.json); supply --user USER to override it for a single run.
- No entries? Confirm the interface is
UP, make surenmapis installed, and try without--fast. - Need more accuracy on noisy networks? Re-run with
sudo ./pi-spy --privilegedsoarp-scancan grab MAC addresses. - Want to skip UI scripting? Stick to
--jsonand feed the array into your own tooling. - Client isolation / captive portals can hide hosts entirely; lean on the Tailscale feed if you have it.
Happy Pi hunting!