kanri docs
kanri is a vim-modal system powertool for Linux — network, process, memory, disk, and hardware, watched and controlled from one TUI and one scriptable CLI.
- New here? Start with Install.
- Already running it? Keybindings and Configuration cover day-to-day use.
- Scripting it? CLI reference.
- Turning on WireGuard or packet capture control? Those need a one-time setup step you do yourself — see WireGuard and Packet capture.
Source lives at git.kanri.management/jaghack/kanri.
Install
kanri is a single static-ish binary, no runtime beyond what’s listed per method below. Pick your OS.
Status: the AUR package and apt repository below are packaging templates in this repo (
packaging/aur/,packaging/debian/) — not live yet, pending the first tagged release. Building from source (Nix flake or plaincargo) works today.
Arch Linux
yay -S kanri
(Coming soon — not yet published to the AUR. Until then, build from source below, or use the PKGBUILD directly:)
git clone https://git.kanri.management/jaghack/kanri.git
cd kanri/packaging/aur
makepkg -si
Ubuntu / Debian
curl -fsSL https://kanri.management/apt/key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/kanri.gpg
echo "deb [signed-by=/usr/share/keyrings/kanri.gpg] https://kanri.management/apt stable main" | sudo tee /etc/apt/sources.list.d/kanri.list
sudo apt update && sudo apt install kanri
(Coming soon — the apt repository isn’t hosted yet. Build a .deb
locally in the meantime: see packaging/debian/README.md in the repo.)
NixOS (flake)
{
inputs.kanri.url = "git+https://git.kanri.management/jaghack/kanri.git";
outputs = { self, nixpkgs, kanri, ... }: {
nixosConfigurations.yourhost = nixpkgs.lib.nixosSystem {
# ...
modules = [
{
environment.systemPackages = [ kanri.packages.x86_64-linux.default ];
}
];
};
};
}
Or try it without installing anything:
nix run git+https://git.kanri.management/jaghack/kanri.git
Build from source (any distro, works today)
Needs a Rust toolchain (edition 2024, so a reasonably recent one) and
libclang (one dependency’s build script needs it for bindgen).
git clone https://git.kanri.management/jaghack/kanri.git
cd kanri
cargo build --release
./target/release/kanri
Or with Nix, no system Rust install needed:
nix develop # or: nix-shell
cargo run
After installing
kanri works with zero config. Some features need one-time, deliberate setup you do yourself (kanri never grants privilege on its own) — see wireguard.md for WireGuard status/control and capture.md for packet capture. Everything else (network devices, processes, memory, disk, hardware, Tailscale) works out of the box.
See config.md for configuration and keybindings.md for the full keybinding reference.
Keybindings
All of kanri’s TUI input is vim-modal: Normal mode for navigation and
actions, Command mode (:) for typed commands, Search mode (/)
for live filtering. Every chord below is fully remappable — see
config.md.
Several chords are contextual: the same key does different things
depending which tab (and sub-view, for Network) is active. This is
deliberate — dd always means “delete/kill/forget the selected thing,”
just applied to whatever “the selected thing” is on the current screen,
rather than one bespoke keybind per subsystem.
Global
| Key | Action |
|---|---|
h j k l | Move left / down / up / right |
0 | Move to the start of the current line/list |
gg | Jump to the top |
G | Jump to the bottom |
gt | Next tab |
gT | Previous tab |
{n}gt | Jump to tab number n (1-indexed) |
: | Open the command line (same grammar as the CLI — see cli.md) |
/ | Open the search/filter line (contextual — see below) |
? | Toggle the help overlay (auto-generated from your live keymap) |
q | Quit — or close the help overlay first, if it’s open |
Ctrl+C | Quit unconditionally, regardless of mode |
Contextual
| Key | Meaning |
|---|---|
dd | Delete the selected thing — always goes through a y/N confirm first. Kills the selected process (Process tab), forgets the selected saved connection (Network → Devices). |
u | “Power up” the selected thing. Activates a saved connection (Network → Devices), brings a WireGuard interface up (Network → WireGuard). |
x | “Power down” the selected thing. Deactivates a connection (Network → Devices), brings a WireGuard interface down (Network → WireGuard). |
[ / ] | Cycle the Network tab’s sub-view: Devices → Diagnostics → Sockets → WireGuard → Tailscale → Bandwidth → Scan → Capture. |
Space | Context-dependent “do the thing”: run a subnet scan (Scan), start/stop a live capture (Capture), toggle Tailscale up/down (Tailscale), collapse/expand the selected drive (Disk). |
c | Open the WiFi-connect prompt — SSID, then a password (Network → Devices only; Enter with a blank password connects to an open network). |
o | Toggle showing small system/boot/recovery partitions alongside each drive’s main ones (Disk tab). |
/ | Live filter for whichever list is on screen: process command/user/PID, sockets, subnet scan results, per-process bandwidth, WireGuard peers, Tailscale peers, or live captured packets — each field it makes sense to match against, case-insensitive. Empty query clears the filter. |
Search filter fields, per view
| View | Matches against |
|---|---|
| Process | Command, username, PID |
| Network → Sockets | Protocol, state, local/peer address, process name, PID |
| Network → Scan | IP, hostname |
| Network → Bandwidth | Process name |
| Network → WireGuard | Interface, endpoint, allowed IPs |
| Network → Tailscale | Hostname, any Tailscale IP, OS |
| Network → Capture | Source, destination, protocol, dissected info |
Remapping
~/.config/kanri/config.toml:
[keymap.normal]
"j" = "move_down"
"dd" = "delete_selected"
Only the chords you mention change — everything else keeps its default. See config.md for the full list of action names you can bind.
Configuration
kanri works with zero config — every field below has a sensible
compiled-in default. Config lives at ~/.config/kanri/config.toml
(respects $XDG_CONFIG_HOME), or pass --config <path> to use a
different file. A missing config file is not an error.
Full example
# Show a Japanese companion label under every tab/section label.
# Off by default — a terminal powertool needs every column of width.
jp_labels = false
# How often live-ticking views (Overview, Process, Memory, Disk,
# Hardware) refresh, in milliseconds.
refresh_interval_ms = 1000
[theme]
# Overrides the default theme source (~/.config/quickshell/theme.json).
# Point this at any JSON file with the same shape to use kanri outside
# that theming pipeline. Every field in that file is optional — kanri
# falls back to a compiled-in dark palette for anything missing.
override_path = "/path/to/theme.json"
[thresholds]
# Color-coding cutoffs (0-100) shared by every gauge/table that shows a
# percentage-like value — CPU%, memory/disk usage, sensor temperatures.
warn_at = 70.0
crit_at = 90.0
[keymap.normal]
# Chord -> action name. Only the chords listed here change; every other
# chord keeps its compiled-in default. See "Action names" below for the
# full list of what you can bind. See keybindings.md for what each one
# does and which are contextual per-tab.
"j" = "move_down"
"k" = "move_up"
"dd" = "delete_selected"
theme
kanri reads the same live theme file a matugen-based rice pipeline
generates, and hot-reloads it on change — no restart needed. Every
field in that JSON is optional; kanri falls back to a built-in dark
palette (Catppuccin-Mocha-adjacent) for anything absent, including when
no theme file exists at all. Recognized keys (all optional): base,
surface, overlay, textPrimary, textSecondary, textMuted,
accentNetwork, accentSystem, success, warning, error — each a
"#rrggbb" hex string.
thresholds
Applies everywhere a value is shown as a percentage-like gauge or
colored number: below warn_at is “success” colored, between warn_at
and crit_at is “warning,” at or above crit_at is “error.”
keymap.normal
An overlay, not a replacement — set only the chords you want to change.
A chord can be any sequence of keys kanri’s input layer recognizes (a
single character like "j", or a short sequence like "gg"). An
unknown action name is logged and skipped at startup, not a fatal error.
Action names
The right-hand side of a keymap.normal entry — every name below is a
valid target:
move_down move_up move_left move_right
move_line_start jump_top jump_bottom
next_tab prev_tab
enter_command enter_search show_help quit
delete_selected up_selected down_selected
next_sub_view prev_sub_view trigger
connect_wifi toggle_extra
See keybindings.md for what each one does, including
the ones that are contextual per-tab (e.g. trigger starts a subnet
scan on the Scan sub-view, but toggles Tailscale on the Tailscale
sub-view).
CLI reference
Run kanri with no arguments for the TUI. Any subcommand runs it in CLI
mode instead: prints once, exits, scriptable. --json works on every
subcommand and switches to machine-readable output.
Every subcommand here is the exact same code path the TUI uses —
there is one implementation per action, not a separate CLI and TUI
version of each. The TUI’s : command line even parses with this same
grammar, so anything you can run from a shell you can also run without
leaving the TUI.
kanri [OPTIONS] [COMMAND]
Options:
--json Emit machine-readable JSON instead of human-readable text
--config <CONFIG> Path to an alternate config.toml (defaults to the XDG config dir)
net — network devices and connections (NetworkManager)
| Command | Does |
|---|---|
net list | List devices and saved connections |
net show <device> | Full detail for one device, by interface name |
net up <name> | Activate a saved connection profile |
net down <name> | Deactivate a saved connection profile, if active |
net disconnect <device> | Disconnect whatever’s active on a device |
net forget <name> | Permanently delete a saved connection profile |
net connect-wifi <ssid> [--password <pw>] | Create and activate a new WiFi connection |
net wg-up <interface> | Bring a WireGuard interface up via wg-quick — needs a matching sudo rule for that exact interface name (see wireguard.md) |
net wg-down <interface> | Bring a WireGuard interface down via wg-quick |
proc — processes and CPU usage
| Command | Does |
|---|---|
proc list [--sort cpu|mem|pid|name] [--filter <text>] | List running processes (default sort: cpu) |
proc kill <pid> [--signal term|kill] | Send a signal (default: term) |
proc renice <pid> <value> | Change nice value (-20 highest priority, 19 lowest) |
ts — Tailscale status and control
| Command | Does |
|---|---|
ts status | This device + tailnet peers |
ts up | Connect to the tailnet — no elevated privilege needed on a typical setup |
ts down | Disconnect. Doesn’t touch your normal internet route — Tailscale is an overlay network, not your default route |
ts exit-node [node] | Set the active exit node by hostname or IP; omit node to clear it. Routes all of this device’s traffic through the chosen node — treat this one with real caution, unlike a plain up/down toggle |
One-shot system info
| Command | Does |
|---|---|
overview | CPU, memory, network throughput, disks — one snapshot |
mem | Memory and swap usage |
disk | Disk usage by mount point |
hw | Host/CPU/kernel info and live sensor temperatures |
--json shape
Every command’s --json output is the same data the human-readable text
is built from — nothing is dropped or summarized differently between the
two. Field names are plain snake_case, matching the Rust struct they
come from. Pipe through jq as usual; kanri handles a closed pipe
(kanri disk --json | jq '.[0]') the same way df/free do, not with
a broken-pipe panic.
What’s TUI-only, for now
net show’s sub-views beyond the device/connection list — Diagnostics,
Sockets, subnet Scan, per-process Bandwidth, WireGuard status (write
control via wg-up/wg-down is scriptable, reading status isn’t yet),
and live packet Capture — don’t have CLI equivalents yet. They’re real,
working features, just reachable from the TUI only so far.
WireGuard
kanri talks to WireGuard through wg/wg-quick directly — not a
reimplementation, the real tools. Reading status (wg show) and
bringing an interface up/down (wg-quick up/down) both need root,
since wg-quick creates network interfaces and can touch routing
tables. kanri never has standing root itself; it shells out through
sudo -n (non-interactive — fails fast and cleanly if it isn’t
authorized, rather than hanging on a password prompt) for each call.
That means you configure the sudo access, scoped as narrowly as you want it. kanri ships no default grant.
Read-only status
# /etc/sudoers.d/kanri-wireguard
your_username ALL=(root) NOPASSWD: /path/to/wg show all dump
Resolve the real path first — command -v wg often returns a symlink
(e.g. /run/current-system/sw/bin/wg on NixOS), and some sudo
configurations match the literal command string rather than resolving
symlinks themselves:
readlink -f "$(command -v wg)"
Use that path in the sudoers rule.
Up/down control
Same idea, one rule per interface name you actually want kanri to control — resist the temptation to wildcard this:
your_username ALL=(root) NOPASSWD: /path/to/wg-quick up wg0
your_username ALL=(root) NOPASSWD: /path/to/wg-quick down wg0
A wildcard (wg-quick up *) technically works, but it means anything
that can run as your user can bring up or tear down any WireGuard
config on the box, not just ones you meant to expose to kanri. Scoping
per-interface costs one extra line per tunnel and closes that off
entirely.
wg show’s own private-key exposure
wg show all dump includes each interface’s private key in cleartext
in its output. If this machine has other, less-trusted local accounts,
scope the sudo rule to trusted users only — don’t hand out blanket wg
access to every account on a shared box.
NixOS example
security.sudo.extraRules = [
{
users = [ "your_username" ];
commands = [
{ command = "${pkgs.wireguard-tools}/bin/wg show all dump"; options = [ "NOPASSWD" ]; }
{ command = "${pkgs.wireguard-tools}/bin/wg-quick up wg0"; options = [ "NOPASSWD" ]; }
{ command = "${pkgs.wireguard-tools}/bin/wg-quick down wg0"; options = [ "NOPASSWD" ]; }
];
}
];
${pkgs.wireguard-tools}/bin/wg is already the real, fully-resolved
store path — no readlink -f dance needed on NixOS specifically.
Nothing configured?
kanri’s WireGuard sub-view (and net wg-up/net wg-down) still work
without any of this — wg show just reports no interfaces, and
sudo -n wg-quick up ... fails with a clear “a password is required”
style error instead of hanging.
Packet capture
The Capture sub-view (Network tab) shells out to tshark for real
protocol dissection — not raw frames, actual decoded source/destination/
protocol/info columns, the same as opening Wireshark itself. Capturing
packets needs CAP_NET_RAW/CAP_NET_ADMIN, which a normal user doesn’t
have by default. kanri doesn’t grant this itself; you configure it once,
the same way you would for Wireshark/tcpdump on any system.
The standard way
Most distros already have a mechanism for this via Wireshark’s own packaging:
# Debian/Ubuntu
sudo dpkg-reconfigure wireshark-common # answer "yes" to the non-superuser prompt
sudo usermod -aG wireshark $USER
# Arch
sudo setcap cap_net_raw,cap_net_admin=eip $(readlink -f $(command -v dumpcap))
sudo usermod -aG wireshark $USER # if you'd rather scope it to a group
Log out and back in (or reboot) afterward — group membership is fixed for your whole session at login, not per-process. A new terminal window inside an already-running desktop session does not pick up a newly-added group; only a fresh login does.
NixOS example
programs.wireshark = {
enable = true;
# dumpcap.enable defaults to true: wraps dumpcap with the capability,
# restricted to the `wireshark` group (not world-executable).
};
users.users.your_username.extraGroups = [ "wireshark" ];
Why kanri doesn’t just grant this itself
Packet capture can read cleartext credentials off the wire for anyone sharing the same network segment — materially more sensitive than, say, per-process bandwidth counts. That’s worth a deliberate, visible choice on your part, not something a TUI silently arranges the first time you press a key.
Nothing configured?
The Capture sub-view still works without any of this — tshark fails
fast (well under a second) with a clear permission error instead of
hanging, and kanri shows the whole error, not just its first line.