Skip to content
cd ..

Building a Developer Friendly CLI with an Interactive Shell

// · 5 min read

A good CLI is the difference between a project developers actually try and one they bounce off in the first five minutes. When I built the agenticmail CLI, I wanted the experience of running agenticmail setup to feel as polished as any commercial dev tool, while still exposing the power that experienced users expect.

The subcommand structure

The CLI has six core subcommands:

setup: The guided first run experience. Walks you through system checks, Docker management, relay configuration, and account provisioning.

start: Boots the AgenticMail server as a background process and writes a PID file for tracking.

stop: Reads the PID file and gracefully shuts down the server.

status: Reports whether the server is running, which port it’s bound to, how many agents are active, and basic health stats.

shell: Drops you into an interactive REPL where you can issue API commands directly without writing curl requests.

chat: Opens a conversational interface where you can interact with agents in natural language.

Each subcommand is its own module, loaded lazily so the CLI stays fast even as new commands get added.

The gradient spinner

Terminal spinners are everywhere, but most look the same. I wanted something that felt alive. The agenticmail spinner cycles through 15 ANSI color codes to create a smooth gradient animation while long operations run. The colors shift frame by frame, creating a flowing rainbow effect that makes it obvious the process is still working.

The spinner also supports a progress bar mode. When an operation can report incremental progress (like downloading Docker images or migrating databases), the spinner switches from its rotation animation to a filling bar with percentage and ETA. Same gradient coloring, different visual.

These are small things, but they communicate care. If the CLI looks polished, developers trust that the underlying system is built with the same attention.

Secret input and keypress pickers

Two interaction patterns come up repeatedly in CLI tools: entering secrets and picking from a list.

For secrets, the CLI uses asterisk masking. Each character you type appears as a *, so you get visual confirmation that input is being captured without exposing the value on screen. This handles API keys, SMTP passwords, and bearer tokens during setup.

For selection prompts, the CLI switches the terminal to raw mode and listens for single keypress events. Arrow keys move a highlight through the options, Enter selects. No typing, no ambiguous partial matches. The picker renders inline, updates in place, and restores the terminal state when done.

Raw mode is tricky to get right. You need to handle edge cases like Ctrl+C (which should exit cleanly, not leave the terminal in a broken state), window resize events, and the differences between how terminals report escape sequences. The picker saves and restores terminal settings defensively.

The setup wizard

First run is where most developer tools fail. The setup wizard handles four phases:

System checks verify that Node.js is the right version, Docker is installed and running, required ports are available, and disk space is sufficient. Each check gets a pass/fail indicator with the gradient spinner running between them.

Docker management pulls the required images (Postgres, a mail server container), creates a Docker network, and starts the containers. The progress bar mode kicks in here, since Docker image pulls report layer progress.

Relay configuration walks through SMTP relay setup. Which provider (Gmail, Outlook, custom SMTP), credentials, connection test. The wizard sends a test email to verify the configuration works before saving it.

Account provisioning creates the first agent account, generates API keys, and writes the configuration to disk.

If any phase fails, the wizard explains what went wrong and offers to retry or skip. It doesn’t dump a stack trace and exit.

PID file server management

The start command spawns the server as a detached background process and writes its PID to a known location. The stop command reads that PID and sends SIGTERM. The status command checks whether the PID is still alive.

This is old school Unix process management, and it works reliably. The alternative would be integrating with systemd or launchd, but those are platform specific and add complexity for a dev tool that’s meant to run on any machine.

The server process also writes a lock file on startup. If you accidentally run start twice, the second invocation sees the lock, checks whether the PID is actually alive, and either reports that the server is already running or cleans up a stale lock from a previous crash.

The interactive shell

The shell subcommand is the power user feature. It opens a REPL that understands AgenticMail’s API surface. You can type commands like mail list or agent status and get formatted JSON output without constructing HTTP requests.

Tab completion knows every available command and its arguments. The shell maintains a history file so you can arrow through previous commands across sessions. Output gets syntax highlighted and pretty printed.

For quick debugging sessions, the shell is faster than switching to a browser or writing a script. You see something odd in the logs, drop into the shell, inspect the state, and get back to what you were doing.

Building a CLI is deceptively time consuming. But the agenticmail CLI is the first thing new users touch, and that first impression determines whether they keep going. The gradient spinner, the guided setup, the interactive shell: they’re all investments in making that first experience smooth.

Source Code

The CLI and the interactive shell are implemented as separate modules within the same package.

View the CLI source on GitHub

View the shell source on GitHub

// share

// subscribe

New posts and updates straight to your inbox. No noise.

cd ..