Kinjo
Browse local DNS-SD services, filter them, and run custom actions from a terminal UI.
What's it for?
Avahi, the common Linux implementation of Bonjour / mDNS / DNS-SD, allows services to be published and discovered on a local network. This TUI lets users browse discovered services, filter and group them, and launch configured actions for a selected service.
Launch the TUI without arguments to browse the default local domain:
Browse another DNS-SD domain with the --domain (-d) flag:
For development without a running Avahi setup:
The app discovers services over mDNS/DNS-SD so no external CLI tools are
required. Two discovery backends are available, selectable with --backend:
mdns-sd(default): themdns-sd-discoverycrate. A single browser enumerates every service type on the link via the native DNS-SD meta-query.zeroconf: thezeroconf-tokiocrate, which talks to the system Avahi daemon on Linux. It browses one service type at a time, so a curated set of common types is swept in parallel when no--service-typeis given. This backend is behind the off-by-defaultzeroconfcargo feature (it needs the Avahi client headers to build, e.g.libavahi-client-devon Debian/Ubuntu):
When a --service-type is given, only that type is browsed. If mDNS discovery is
unavailable, it falls back to sample records so the UI remains usable.
Installation
Debian / Ubuntu
Download the latest .deb package from the project's
GitHub Releases page, then
install it with apt:
The package installs kinjo and the bundled system command files under
/etc/kinjo/commands.
For real local-network discovery, make sure Avahi is installed and running:
Cargo
Install from crates.io with Cargo:
On Debian or Ubuntu, install native build dependencies first:
Build From Source
Clone the repository and build locally:
Run from the source tree:
Install the built binary into Cargo's bin directory:
Smoke Test
You can verify the UI without a running Avahi daemon:
To browse real services on the default local domain:
How It Works
kinjo has five moving parts:
- Discovery finds DNS-SD service records on the network. Each record can carry fields such as service name, service type, domain, hostname, address, port, and TXT values.
- Filtering and view tabs organize those records in the UI. You can fuzzy-search the visible services, limit by service type, and switch the top-panel tab to view discovery by service, host, service type, or matching command.
- Actions decide what can be done with a selected service. Action command files
define match predicates such as "service type equals
_ssh._tcp" or "TXT field contains a URL". - Command templates turn service fields into executable commands. For example,
ssh {hostname}uses the selected service hostname, whilexdg-open http://{hostname}:{port}builds a URL from the selected instance. - Keybindings control the TUI. The defaults use Vim-style navigation, and every
built-in UI command can be rebound in
keybindings.toml.
The result is a small local service browser that behaves like a configurable launcher: discover services, narrow the list, choose a matching action, and run the command built from that service's fields.
Architecture
Internally those moving parts live in three deliberately decoupled modules, so the project is easy to extend and hack on. Each is designed to be swapped or reused independently:
- Discovery (
src/discovery/) — the producer of entries. An entry is a discovered record described entirely by its attributes (name, type, host, address, port, TXT, …). A backend implements theDiscoverytrait and emitsEntryvalues;Entryis the only contract the rest of the program depends on. The mDNS/Avahi backend is the default, with a built-in sample backend for--fake-discovery— but you could drop in a different DNS-SD source, a static file, or an SSDP/UPnP browser without touching anything else. - Plumber (
src/plumber/) — the rules engine. A serializable collection of command rules (the TOML command files) is matched against entries by their attributes; multiple rules can match one entry, and a matching rule can be executed. It depends only onEntry, never on the UI, and sits behind aRuleEnginetrait so an alternative matching strategy can be substituted. - UI (
src/ui/) — ties discovery and the rules engine together for a person at the terminal: CLI parsing, config and keymap loading, the application state machine, and rendering. It depends on the other two; they do not depend on it.
The dependency flow is one-directional — discovery ← plumber ← ui — wired
together in main.rs. The two trait seams (Discovery and RuleEngine) are the
intended extension points: implement a trait, swap it in at the composition root,
and experiment. See CONTRIBUTING.md for development setup.
UI
Default keys follow Vim-style conventions:
j/down: move downk/up: move upenter: show or run matching actions/: fuzzy text filtert: service type checklist filtertab/shift+tab(or←/→): switch view tabr/F5: refresh — restart service discovery from scratch?: helpq: quit
The top panel exposes four tabs — services, hosts, types, and commands. Each tab swaps the list and details panes to view discovery from that angle: individual services, hosts and the services they offer, discovered service types, or configured commands and the services they match. The list also supports fuzzy text search and service type filtering.
Keybindings are fully customizable: all built-in UI commands can be rebound with a keybindings config file. See docs/keybindings.md for the full keybinding reference and examples.
Configuration
Command files follow the XDG Base Directory Specification. User command files are loaded from:
If XDG_CONFIG_HOME is not set, the fallback path is:
Additional command directories can be provided with:
Validate and list the registered commands with:
To validate and list only the commands from a specific directory:
A running instance reloads its command files on SIGHUP (the conventional
reload signal), so edits apply without restarting the TUI:
Keybindings can be overridden at:
See docs/keybindings.md for examples and the complete list of bindable commands.
Command Files
Each command file defines one action and structured match predicates. Example SSH opener:
[]
= "ssh"
= "SSH into a service"
= ["ssh"]
[]
= "_ssh._tcp"
[]
= "SSH into the selected service"
= "ssh {hostname}"
= "execute"
Supported action modes:
fork: spawn the command and return to the TUI.execute: restore the terminal and replace the TUI process with the command.
Supported match predicates:
equalscontainsregex
Supported service fields:
nameservice_typeortypedomainhostnameaddressporttxt.<key>
The same fields can be used in action command interpolation, for example
{hostname}, {address}, and {port}.
Multiple configured actions can match the same service. In that case, the TUI
shows an action picker. If an action needs instance-specific fields such as
address or port and the selected row contains multiple instances, the TUI
asks which exact instance to use.
For the full command file format, examples, and overlay rules, see docs/actions.md.
Contributing
Development setup, required system packages, and local verification commands are documented in CONTRIBUTING.md.