Skip to content

Adding a New Tool

or1k edited this page Aug 6, 2026 · 1 revision

Adding a New Tool

This walks through wiring up a brand-new tool end to end, using the same shape as the existing ones (mysql_mgr is the reference example below — it's a mid-complexity tool: a config-backed list of connection profiles plus a second tab that does real work over one of them). Skim Architecture first if you haven't — this page assumes you know the App/Screen dispatch pattern.

1. The logic module: src/<tool>/

Create src/<tool>/mod.rs plus whatever split makes sense — the existing convention is config.rs for the on-disk profile/settings CRUD and client.rs (DB/HTTP tools) or exec.rs (SSH-driven tools) for the actual work. No ratatui/crossterm imports here — this module has to be usable from a screen or a CLI subcommand without caring which.

  • Persisted config → src/config.rs's config_file("<tool>.json"), a serde::{Serialize, Deserialize} struct, loaded/saved as plain serde_json. Look at mysql_mgr::config::{Config, Connection, load, save} for the shape.
  • Any password/token that needs to persist → encrypt it with crate::secret::encrypt/encrypt_optional before it goes in the struct that gets serialized, decrypt with decrypt/decrypt_optional when building an in-memory "with secrets" view for actual use. See Config and Secrets and mysql_mgr::config::ConnectionWithSecrets for the pattern (store _encrypted fields on disk, hand out a WithSecrets struct in memory only, never the reverse).
  • Talks to a remote host over SSH → build on crate::ssh_exec::SshSession (or the one-shot crate::ssh_exec::run_commands) rather than shelling out to a real ssh binary or rolling your own socket code. If it needs to reach something (a DB, an internal service) that's only reachable from the remote side, use crate::ssh_tunnel::open — see SSH and Remote Execution.

2. Register the module in src/main.rs

mod <tool>;

added next to the other mod lines (alphabetical order is the existing convention, not enforced by anything).

3. The screen: src/tui/<tool>_screen.rs

This is the only file that's allowed to import both crate::<tool>::* and ratatui/crossterm. It must expose exactly this surface — App in tui/mod.rs calls these by name, so getting the signatures right is what makes step 4 a one-line-per-method change:

pub struct <Tool>Screen { /* your UI state: Input fields, selected index,
                              which tab, an Option<FilePicker>/HostPicker
                              for any modal in progress, etc. */ }

impl <Tool>Screen {
    pub fn new() -> Self { /* load config, set defaults */ }

    pub fn tick(&mut self) { /* poll a background thread's mpsc::Receiver,
                                 advance a spinner — no-op is fine if the
                                 tool has nothing async */ }

    /// Return `true` to pop back to the Home screen (Esc from the top
    /// level); `false` to stay (Esc closes a modal / clears a field first).
    pub fn handle_key(&mut self, key: KeyEvent) -> bool { ... }

    pub fn handle_mouse(&mut self, me: MouseEvent, area: Rect) { ... }

    pub fn draw(&self, f: &mut Frame, area: Rect) { ... }
}

Build the UI out of the shared primitives in tui/widgets.rs rather than raw ratatui widgets wherever one fits — see TUI Conventions for the full list (Input, input_span, btn_span, tab_span, theme_block, draw_modal, draw_history/copy_history_to_clipboard) and the color functions (bg(), fg(), accent(), ...) — never hardcode a Color, always call the theme function, so F9 theme-cycling repaints your screen too. For mouse hit-testing, reuse tui/mouse.rs's helpers (table_row_hit, button_row_hit, label_row_hit, scroll_delta) instead of writing new coordinate math — see TUI Conventions for why table_row_hit in particular exists (it replicates ratatui's own scroll-to-keep-selection-visible math; naive y - top math selects the wrong row once a list has scrolled).

If the tool needs to pick an SSH host from ~/.ssh/config, a file from the filesystem, or a set of DB privileges, reuse host_picker::HostPicker, file_picker::FilePicker, or priv_picker::PrivPicker instead of building another modal from scratch — every existing DB tool (mysql_screen.rs, postgresql_screen.rs, clickhouse_screen.rs) composes all three.

4. Wire it into src/tui/mod.rs

Four small additions, each exactly mirroring what every other tool already has at that spot:

  1. mod <tool>_screen; at the top with the other screen modules.
  2. A new Screen::<Tool> variant.
  3. A new <tool>: Option<<tool>_screen::<Tool>Screen> field on App, plus <tool>: None in App::new().
  4. One arm each in App::enter (lazily construct on first visit), App::tick, App::take_pending_action (only if your screen can produce one — see Architecture's note on PendingAction, most tools don't), App::handle_mouse, App::handle_key, and App::draw. Every arm is a one-liner that forwards to the field you just added — copy the Screen::SslCert => { ... } arm in each match as a template, it's the newest tool and therefore the most representative of current style.

5. Add it to the Home menu (src/tui/home.rs)

  • One new HomeItem { title, desc, bin, screen: Screen::<Tool> } entry in DEFAULT_ITEMS. bin is cosmetic (shown in parens next to the title) — by convention it names what the tool would be called as a standalone binary/command, even though atk ships as one binary.
  • One arm each in screen_key (Screen → string, used for the persisted menu_order.json) and screen_from_key (the inverse). Pick a short, stable lowercase key — this string is what's already written to disk for every existing user's menu_order.json, so once it ships, don't rename it.

Note only tools 1–9 in the default order get a digit shortcut (Char(c) if c.is_ascii_digit() in HomeState::handle_key) — a 10th+ tool is reachable by arrow keys / mouse / Enter same as any other, just not by a single keypress. (This is already the situation for the SSL Certificate Manager, item 10.)

6. (Optional) a scriptable CLI subcommand

If the tool should also be usable non-interactively (atk <tool> ... in a script/cron job, no TUI), add a clap::Subcommand in main.rs — see CLI Subcommands for the full pattern, using the existing sshuser subcommand as the template. This is optional and most tools don't have one; only add it if there's an actual non-interactive use case, not by default.

7. Update README.md

Bump whatever "N tools" count is in the intro, add a section describing the tool (what it does, what it needs configured, any gotchas), and if you record a demo GIF, drop it next to the others and reference it the same way.

Checklist

  • src/<tool>/ — no ratatui/crossterm imports
  • mod <tool>; in main.rs
  • src/tui/<tool>_screen.rs implementing new/tick/handle_key/handle_mouse/draw
  • mod <tool>_screen;, Screen::<Tool>, Option<...> field + None default, and all six match arms in tui/mod.rs
  • HomeItem entry + screen_key/screen_from_key arms in tui/home.rs
  • Colors via widgets::{bg, fg, accent, ...}, never a hardcoded Color
  • Config (if any) via config::config_file, secrets via secret::{encrypt,decrypt}*
  • cargo build clean, manually exercised every code path once in a real terminal
  • README updated

Clone this wiki locally