Skip to main content

dasshboard

PyPI

A home screen for Ghostty. Every host in ~/.ssh/config — plus anything you add yourself — becomes a clickable tile. Activating one opens a new Ghostty tab connected to it, tinted with that host's colour.

The whole block is centred in both axes, and tiles keep a fixed width — a wide terminal gets whitespace, not vast tiles.

   ◆  DASSHBOARD                                                5 ssh · 1 local
   ━━━━━━━━━━━━━━──────────────────────────────────────────────────────────────

   ┏ 1 ━━━━━━━━━━━━━━━━━━━━━━━━ local ┓  ╭ 2 ───────────────────────────────╮
   ┃▌ ● MACBOOK-PRO                   ┃  │  ● alex                          │
   ┃    local shell                   ┃  │    v120bb18@alex.nhr.fau.de ⤳    │
   ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛  ╰──────────────────────────────────╯

   ⏎ open   t/w/c tab·win·here   / find   a add   e edit   x hide   s settings

Two kinds of tile:

  • ssh — opens a new Ghostty tab and connects.
  • local (tagged local) — takes over this tab. termhome restores the terminal and then execs the command, so the process is replaced rather than nested; quitting it returns you to the shell that started termhome.

Colour

The chrome is exactly two colours — a primary and an accent, #aaaaaa and #ff0000 out of the box — and the discipline is that the accent is never decoration. It marks the selection, the brand diamond, and errors; nothing else. Three tile states read as three border weights rather than three hues: thick accent when selected, primary when hovered, hairline when idle.

Both are yours to change, in s or in [theme]. Everything else is derived from the pair rather than listed separately, so a new primary restyles the whole UI coherently instead of leaving four hand-picked greys behind:

shade from used for
bright primary → white, 73% the one word that should stand out
muted primary × 0.62 secondary text
faint primary × 0.34 hairlines and idle borders
accent-dim accent mixed 25% toward primary, × 0.70 tags and hover

The ratios were reverse-engineered from the original hand-picked palette, so the defaults come out byte-identical to what they were before this became runtime — the_default_theme_reproduces_the_original_shades pins that. In the settings panel the arrows cycle presets and typing takes a hex; a colour is written the moment it parses, so the UI restyles as you type and never flickers through a half-finished value.

Hosts carry a third colour that is theirs alone, shown as the dot on the tile. That palette deliberately excludes red — a host that owned red would be indistinguishable from the cursor — and its eight entries are desaturated to sit against #aaaaaa without shouting over it.

A name hashes to a preferred slot, and a taken slot probes forward to the next free one, so no two tiles on screen share a colour. The trade-off is that a host's colour depends on what else is on screen: adding a host can shift a later one. Pin any host with color = "#rrggbb" and it never moves.

The palette is sized to the emoji, not the other way round. A Ghostty tab title is text, so a coloured circle is the only way a host's colour can reach the tab bar itself — the background tint colours the surface, not the strip you actually scan. There are nine circle emoji and red is spoken for, which leaves eight, one per palette slot. A richer palette would give two hosts the same circle and defeat the point. A hand-written hex gets the nearest circle by RGB distance, so color = "#2255cc" still shows 🔵.

One caveat: ⚫ is low-contrast on a dark tab bar. If a host lands on it and you'd rather it didn't, pin that host to another colour.

Keys

click / / space open the tile
19 open that numbered tile directly
arrows / hjkl move (j/k move by a row, so they respect the grid)
g / G first / last
/ find by name, target, or jump host; esc clears
a add a host
e edit the selected host — including ones from ~/.ssh/config
x hide the selected host (or unhide it, with show_hidden on)
d delete a host, or revert an ~/.ssh/config one (asks first)
t / w / c open in a new tab / new window / this surface, once
s settings — options, destination and both colours
r re-read both config files
q / esc quit into the shell underneath

Editing, in the TUI

Everything is editable without leaving the terminal UI — there is no $EDITOR handoff. a and e open the same form; only name is required, and the rest falls back to ssh's own resolution. On the color row, / cycle auto and the eight presets, or type a hex; the row previews the swatch and the exact circle the tab will carry. s opens settings and writes each toggle through immediately, so the file and the screen can never disagree.

Customising a host from ~/.ssh/config

~/.ssh/config is never written to — it's ssh's file, and rewriting it risks the options termhome doesn't model. Instead, editing one of its hosts creates a [[host]] block of the same name in config.toml, which merges on top of it:

   name     alex  from ~/.ssh/config
 ▌ hostname ▌  alex.nhr.fau.de  ~/.ssh/config
   user       v120bb18  ~/.ssh/config
   port       22
   jump       csnhr  ~/.ssh/config
   color    ● auto   🟤 tab

The name is the key joining the two files, so it's shown but locked, and focus skips it. Every blank field displays what it inherits and keeps tracking the ssh config; only what you actually fill in is written. So a colour override is just:

[[host]]
name = "alex"
color = "#c9a227"

That still launches as plain ssh alex. When you do override a connection field it rides in as -o HostName=… in front of the alias, which takes precedence over the config file without replacing the lookup — passing user@host instead would silently drop the alias's IdentityFile, ForwardX11 and everything else.

An override customises the existing tile rather than adding a second one, and d on such a host offers to revert it: the block goes, the tile stays.

Hiding a host

Not every Host in ~/.ssh/config is somewhere you open a shell. A bastion that only exists to be jumped through is still a host ssh needs and a tile you never want. x hides the selected one:

[[host]]
name = "csnhr"
hidden = true

That is the whole block — hiding pins nothing else, so the host keeps tracking ~/.ssh/config and keeps working as a ProxyJump target for everything that routes through it. Only the tile goes.

To get one back, turn on show_hidden in s: hidden hosts reappear dimmed and tagged hidden, and x puts them back. The form has the same toggle, so editing a hidden host can't silently unhide it.

--list always prints hidden hosts, marked, since it exists to show you what is actually configured.

Everything lands in ~/.config/dasshboard/config.toml, created with a commented template on first run:

[options]
include_ssh_config = true   # false shows only the hosts defined here
tint_tabs = true            # tint the new tab's background
tab_emoji = true            # coloured circle in the tab title
show_hidden = false         # reveal hidden hosts, to unhide them
open_in = "tab"             # "tab", "window" or "current"

[theme]
primary = "#aaaaaa"
accent = "#ff0000"

[[local]]                   # a command on this machine
label = "MACBOOK-PRO"
detail = "local shell"
command = "/bin/zsh"

[[host]]
name = "myserver"
hostname = "10.0.0.5"
user = "albe"
port = 22
jump = "bastion"
color = "#4f8ab0"
hidden = false
open_in = "window"          # overrides the global for this host

Edits rewrite whole blocks as text rather than re-serialising the document, so your comments and formatting survive: add_then_edit_then_delete_leaves_the_file_as_it_started asserts the file comes back byte-identical, and editing a host keeps it in position rather than moving it to the end. a/e/d only ever touch [[host]] blocks in this file. Local tiles are the one thing still edited by hand; e on one says so rather than failing quietly.

A TOML syntax error is reported in the status line and leaves the previous screen usable; a typo must never cost you the home screen.

How tabs are opened

Ghostty ≥ 1.2 ships an AppleScript dictionary, so no key-injection hacks:

tell application "Ghostty"
  set cfg to {command:"/bin/sh -c '...'", wait after command:true}
  new tab in front window with configuration cfg
end tell

Three things worth knowing, all found by probing the running app:

  • new tab fails with -1708 unless the optional in <window> parameter is passed explicitly. src/ghostty.rs always passes front window.
  • There is no GHOSTTY_SURFACE_ID in the environment, so a surface can't identify its own window. front window is correct anyway — we only spawn in response to input, which means we're focused.
  • The dictionary has no tab-colour property, so the colour reaches the tab two ways. The title gets the host's circle emoji (tab_emoji), which is the reliable one. The surface also emits OSC 11 to set its background (scaled to 13%, or text would be unreadable) and OSC 12 for the cursor at full strength (tint_tabs); whether Ghostty's tab bar picks that up is unverified here.

The spawned command is sh -c 'printf <title>; <tint>; exec ssh <args>'. Each argv element is quoted separately, so a value containing a space stays one argument to ssh. exec hands the process over so the tab dies with the connection, and wait after command:true keeps a failed connection on screen instead of flashing the tab shut.

Config parsing

src/ssh.rs handles Host (multiple aliases per line), HostName, User, Port, ProxyJump, Include (globs, ~, paths relative to ~/.ssh), and Key=value as well as Key value. Wildcard patterns (Host *) are skipped since they configure other hosts rather than naming one, and Match blocks end the current host so their conditional keys aren't misattributed.

Hosts from ~/.ssh/config are launched by alias alone, so ssh re-reads the same file and every option termhome doesn't model — IdentityFile, ForwardX11, IdentitiesOnly — still applies. Overrides preserve that by riding in as -o Key=Value before the alias rather than replacing it.

Launching

~/.zshrc has two marked blocks. See the comments there; the short version is that the dotfiles' hsl-login.sh already claimed the terminal-open slot and blocks until you quit herdr, so part 1 sets NO_HSL=1 before that source and part 2 runs termhome after it (where PATH is complete). herdr is un-autostarted, not disabled: type hsl, or press its tile.

The guards mirror hsl-login.sh's. The HERDR_* one is load-bearing: every herdr pane sources this rc, so without it each new pane would open a termhome inside itself.

Escape hatchesDASSHBOARD_SKIP=1 zsh -l for a bare shell, homescreen to bring the UI back up, or delete the two marked blocks (~/.zshrc.bak.termhome is the pre-install copy; removing them restores herdr autostart).

Install

uv tool install dasshboard      # or: pipx install dasshboard

The wheel is a Rust binary with a console-script entry point — there is no Python in it. maturin is the build backend and uv build drives it:

uv build          # -> dist/*.whl and dist/*.tar.gz
uv publish        # needs a PyPI token

The wheel is tagged macosx_11_0_arm64, because the whole thing talks to Ghostty over AppleScript. On any other platform pip will fall back to the sdist and build from source, which works if you have a Rust toolchain but still needs macOS and Ghostty to be useful.

Development

cargo build --release        # ~/.local/bin/dasshboard symlinks to the output
cargo test                   # parsing, escaping, colour assignment, layout
cargo test layout_dump -- --nocapture   # eyeball every screen at four sizes
./target/release/dasshboard --list      # tiles, colours and circles, no TUI
./target/release/dasshboard --open alex # spawn one tab, no TUI
./target/release/dasshboard --config    # print the config path

The binary is symlinked rather than copied, so cargo build --release takes effect immediately. Don't cargo clean without rebuilding.

Layout is verified headlessly with ratatui's TestBackend, which is how the grid gets checked without a terminal: every_tile_is_clickable_and_none_overlap asserts the hitboxes cover the visible list and never overlap, tiles_never_escape_the_viewport runs the same check down to 30×10, and content_is_centred measures the whitespace on all four sides.

The config writers take their target path as a parameter, so the round-trip tests run against a scratch file and never touch your real config.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dasshboard-0.1.0.tar.gz (622.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dasshboard-0.1.0-py3-none-macosx_11_0_arm64.whl (498.9 kB view details)

Uploaded Python 3macOS 11.0+ ARM64

File details

Details for the file dasshboard-0.1.0.tar.gz.

File metadata

  • Download URL: dasshboard-0.1.0.tar.gz
  • Upload date:
  • Size: 622.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for dasshboard-0.1.0.tar.gz
Algorithm Hash digest
SHA256 15aea8d5002b0f9354c47f3e29f4b91f8ed4faa9e75d190c3f7b00f7dec9e95e
MD5 88893833303487746da819bdfa49d85a
BLAKE2b-256 f8a34b30a0066ee2809316d905c69d2c24df13228796b8a6e75274c1888e67c4

See more details on using hashes here.

File details

Details for the file dasshboard-0.1.0-py3-none-macosx_11_0_arm64.whl.

File metadata

  • Download URL: dasshboard-0.1.0-py3-none-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 498.9 kB
  • Tags: Python 3, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for dasshboard-0.1.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 22751987a1d5fce67fa92c2aef3c09b47d9e5da834958b4e71a6fc0a4de4f28a
MD5 cf4f19da3b9b4a23748dcab553087c92
BLAKE2b-256 76558377e0fedf100cdf74d2861f884fc92342a7838e1d14a10b63f2cd4a79b4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page