devcli is an all-in-one terminal tool for developers, written in Go. One binary includes:
- a btop-style system dashboard
- a process killer
- a Podman/Docker container manager
- colored JVM thread dumps for Java, Scala, Kotlin and Clojure
- query consoles for MySQL, Postgres, SQLite, Cassandra, Redis, Loki, Grafana and Prometheus
Every console shares one editor: syntax highlighting, line numbers, auto-complete as you type, history, and results shown as tables or colored JSON.
It opens with an ASCII-art splash, finds your running database containers and asks whether to connect to them. Cmd-K searches everything and jumps there. Every data source also has a one-shot mode for scripts: devcli -sql --postgres "select 1".
The design is in design-doc.md.
main.goparses the flags. With no mode it opens the TUI. With a mode (-sql,-redis,-loki, …) it runs once, prints the result and exits.- One tview event loop owns the screen. Background workers collect data and hand it back through
QueueUpdateDraw, so only the UI goroutine changes what is drawn. - Container discovery: at start,
podman inspectruns on every running container. Each image name maps to a data store, with its port and credentials read from the container. A prompt then asks which containers each console should use. - Cmd-K palette: a native Go overlay. It fuzzy-matches tabs, commands, discovered containers, containers, JVMs, processes, the live completion words of every console (tables, keys, labels, metrics) and query history. Enter goes to the selected item.
- Dashboard: streams
top -l 0 -s 1for CPU and memory. It reads exact byte counters fromnetstat -ibandioregfor network and disk rates, and draws braille graphs and gradient meters. - Consoles: all eight share one
Consolecomponent and oneBackendinterface.- MySQL, Postgres and SQLite use
database/sql; Cassandra usesgocql. - Redis uses a hand-written RESP2 client.
- Loki, Grafana and Prometheus use plain
net/http.
- MySQL, Postgres and SQLite use
- One-shot output: the same renderers produce tview color tags, which a small converter turns into 24-bit ANSI on a terminal or plain text in a pipe. The ASCII banner goes to stderr, so stdout stays clean for
jq. - Grafana charts in the terminal:
chart <uid>sends each panel's own queries throughPOST /api/ds/queryand draws the returned frames as color-tagged text. Time series become braille line charts, and stat, gauge and log panels get their own views. Charts use the width of the results pane (redrawn on resize) or of the terminal in one-shot mode.
The SVG source uses the Caveat font, a wobble filter, pastel boxes and solid arrows.
-
ASCII splash: a gradient
DEVCLIbanner with a spinner while containers are scanned. Any key skips it. -
Container discovery + connect prompt: finds running Postgres, MySQL, Cassandra, Redis, Loki, Grafana and Prometheus containers, even with custom ports and passwords.
Spacetoggles,Enterconnects. You don't have to type connection strings for your own containers. -
Cmd-K search anything: jump to a tab, run a command, connect a container, open a JVM's thread dump, filter to a process, or insert a table, key, label or metric into its console. Esc clears the search first, a second Esc closes.
-
Cmd-/ shortcuts: grouped by area, each group with its own icon and color, laid out in columns that fit the screen and scroll inside the modal. Has a search box, a match count, and a message when nothing matches.
-
Cmd-1..9 / Cmd-0, F1..F12: jump to any tab, even while typing in an editor.
-
One-shot mode:
-sql --postgres|--mysql|--sqlite,-cassandra,-redis,-loki,-grafana,-prometheus,-ps,-containers,-threads [PID],-discover. Also-json,-target,-timeoutand-q, a colored--help, the query from stdin, and exit code 1 on errors. -
Dashboard (btop style): CPU history, memory, wired, compressed and swap, volumes, disk and network rates, top processes.
-
Processes: filter, sort, and SIGTERM/SIGKILL through a dialog where Cancel is selected by default. Before signaling, devcli checks the PID still belongs to the same command.
-
Containers: running containers first; stop, start, kill, remove, and a shell inside the container with the TUI suspended.
-
Threads: JVMs tagged Java, Scala, Kotlin or Clojure; state colors, a deadlock banner, and frame names that read like source code.
-
MySQL / Postgres / SQLite / Cassandra: multi-statement queries,
Enterruns once the statement ends with;. Completion knows live tables and columns. -
Redis: any command. JSON values pretty print, hashes render as objects, keys complete.
-
allin MySQL, Postgres, SQLite, Cassandra, Loki, Grafana and Prometheus: typealland press Enter (no;needed) to see everything the connected server offers:- the commands of that console;
- ready queries built from the server's own tables, labels, metrics, datasources and dashboards. They are printed full width, and
Cmd-Klists them soEnterloads one into the editor; - Postgres: server, databases, schemas, tables with estimated rows and size, columns, indexes, constraints, sequences, routines, triggers, extensions, roles, sessions and functions;
- MySQL: server, databases, tables with engine, rows and size, columns, indexes, constraints, routines, triggers, events, users, sessions and functions;
- SQLite: server, attached databases, tables and views, columns, indexes, foreign keys, triggers, every pragma and functions;
- Cassandra: cluster, peers, keyspaces, tables, columns, indexes, materialized views, user types, user functions, aggregates, virtual tables and CQL functions;
- Loki: labels with their values, streams, pipeline stages and functions;
- Grafana: health, datasources, folders, dashboards and alert rules;
- Prometheus: metrics with type and help, labels, targets and functions.
Typing a bare function name such as
avg_over_timeshows how to call it instead of an empty result or a parse error. -
Loki: LogQL with
labels,values,:rangeand:limit, and a colored log view. -
Grafana:
search,dashboard,datasources,alerts,query DS EXPR,get /api/.... -
Grafana charts:
chart UIDdraws every panel of a dashboard, andchart UID latency(a panel id or part of a title) draws one:- timeseries panels become braille line charts with a value axis, a time axis, one color per series, and a legend with last, min and max. Missing samples and nulls stay gaps;
- stat panels show the last value in big digits with its unit and a sparkline;
- gauge, bar gauge, bar chart and pie chart panels become heat-colored bars against the panel
max, or 100 for percent; - logs panels use the Loki log view, table panels a table;
- panels inside collapsed rows are included, hidden targets are skipped, and a target's datasource overrides the panel's;
- the time range comes from the dashboard, with about 300 points per chart; units such as
bytes,percent,percentunit,sandmsare formatted like Grafana.
query DS EXPRalso draws a line chart when the result is numeric.devcli -grafana "chart devcli-metrics"prints the charts in any terminal. -
Prometheus: PromQL instant or
:rangequeries,metrics,labels,values,targets,alerts,rules. Metric names complete. -
Java 25 sample:
sample/java25is a real multi-threaded app to thread dump, run withscripts/sample-java.sh start|dump|stop. -
Sample data:
scripts/sample-all.sh start|stopstarts and fills every data store, and runs the Java 25 sample and a Clojure JVM. -
macOS install:
scripts/install-macos.shbuilds and installs onedevcliinto~/.local/bin;scripts/uninstall-macos.shremoves it.
- Go 1.26: one binary; standard library for HTTP, JSON, flags, process execution and signals.
- tview + tcell: layout, tables, mouse, paste, the kitty keyboard protocol (which delivers
Cmdas a modifier), and a simulation screen for tests and screenshots. - go-sql-driver/mysql, lib/pq, mattn/go-sqlite3, gocql: the four database wire protocols.
- Hand-written code instead of libraries: the RESP2 client; the Loki, Grafana and Prometheus clients; lexers, fuzzy search, completion, the ordered JSON colorizer, the ANSI converter and container discovery.
- Podman + podman-compose: MySQL 9, Postgres 18, Cassandra 5, Redis 8, Loki 3.5, Grafana and Prometheus for local testing.
- Playwright (npx): only used to photograph the captured terminal screens for this README.
devcli has no HTTP server. Its contract is the command line, environment variables and keys.
devcli --help| Command | Effect |
|---|---|
devcli |
Open the TUI: splash, container discovery prompt, dashboard |
devcli -sql --postgres QUERY / -sql --mysql QUERY / --sqlite QUERY |
Run SQL (--sqllite also works) |
devcli -cassandra QUERY |
Run CQL (-cql also works) |
devcli -redis COMMAND |
Run Redis commands, one per line |
devcli -loki LOGQL |
Query Loki |
devcli -grafana COMMAND |
Grafana API commands |
devcli -prometheus PROMQL |
Query Prometheus |
devcli -ps [FILTER] / -containers / -threads [PID] |
Processes, containers, JVMs or one thread dump |
devcli -discover |
Running data containers, their connect targets and the flag to use |
-json -target URL -timeout 30s -no-color -q |
JSON output, target override, timeout, plain output, no banner |
-tab NAME -no-discover -capture DIR -version |
TUI start tab, skip discovery, HTML screen capture, version |
Every option works with one or two dashes, and options go before the query. When no query is given and stdin is not a terminal, the query is read from stdin. Exit codes: 0 ok, 1 query or connection error, 2 invalid flags.
devcli -sql --postgres "select kind, count(*) from events group by kind"
devcli -json --sqlite "select * from hosts" | jq '.[].meta.arch'
echo "HGETALL session:9f2c" | devcli -q -redis
devcli -loki '{app="api"} |= "error"'
devcli -prometheus 'sum by (job) (up)'
devcli -threads "$(pgrep -f DevcliJvm)"| Variable | Default |
|---|---|
DEVCLI_MYSQL |
mysql://root:[email protected]:3306/devcli |
DEVCLI_POSTGRES |
postgres://postgres:[email protected]:5432/devcli?sslmode=disable |
DEVCLI_SQLITE |
devcli.db |
DEVCLI_CASSANDRA |
127.0.0.1:9042/devcli (host[,host]:port/keyspace) |
DEVCLI_REDIS |
redis://127.0.0.1:6380/0 (redis://[user:pass@]host:port/db) |
DEVCLI_LOKI |
http://127.0.0.1:3100 |
DEVCLI_GRAFANA |
http://admin:[email protected]:3000; DEVCLI_GRAFANA_TOKEN sends a bearer token |
DEVCLI_PROMETHEUS |
http://127.0.0.1:9090 |
| Scope | Keys |
|---|---|
| Global | Cmd-K / Ctrl-K search, Cmd-/ / Ctrl-/ / ? shortcuts, Cmd-1..9 Cmd-0 / F1..F12 tabs, Ctrl-N / Ctrl-P next / previous, Ctrl-Q quit (also from dialogs) |
| Palette | type to search, ↑↓ move, Enter go, Esc clear then close |
| Connect prompt | ↑↓ move, Space toggle, a all, Enter / y connect, Esc / n skip |
| Editor | Ctrl-R run, Enter run or newline, Tab complete, ↑↓ popup or history, Ctrl-L clear, Ctrl-T table/JSON, Ctrl-E connection, F5 reload words, Esc cancel query |
| Processes | / filter, o sort, x SIGTERM, K SIGKILL, r refresh |
| Containers | Enter/e shell, s stop, S start, k kill, d remove, r refresh |
| Threads | Enter dump, Tab focus dump, / filter, s state filter, r refresh |
Cmd keys in macOS terminals. tcell turns on the kitty keyboard protocol, so a terminal that forwards Cmd sends it to devcli as a modifier. Most terminals keep Cmd-K for themselves (it clears the screen), so either use Ctrl-K or unbind the key. In Ghostty:
keybind = cmd+k=unbind
keybind = cmd+slash=unbind
In iTerm2 or Terminal.app, map Cmd-K to send the hex code 0x0b, which is Ctrl-K.
Backends talk to these server APIs:
| Backend | Calls |
|---|---|
| Prometheus | GET /api/v1/status/buildinfo, /api/v1/query, /api/v1/query_range, /api/v1/labels, /api/v1/label/<name>/values, /api/v1/targets, /api/v1/alerts, /api/v1/rules |
| Loki | GET /loki/api/v1/labels, /loki/api/v1/label/<name>/values, /loki/api/v1/query_range |
| Grafana | GET /api/health, /api/search, /api/dashboards/uid/<uid>, /api/datasources, /api/folders, /api/v1/provisioning/alert-rules, /api/annotations, POST /api/ds/query |
| Redis | RESP2 over TCP, AUTH / SELECT from the URL, SCAN for completion |
| SQL / CQL | information_schema.columns, sqlite_master + pragma_table_info, system_schema.columns |
| Discovery | podman ps -q + podman inspect (or docker): ImageName, Config.Env, NetworkSettings.Ports, State.Running |
| Structure | Purpose |
|---|---|
backend.Backend |
Connect, Execute, Words, RunsOnEnter, Language: everything a console or one-shot run needs from a data store |
backend.Result |
Columns/Rows tables, Logs log views, Value JSON, Text pre-rendered text, plus title, message, elapsed |
discover.Found |
Kind, container, image, host port and a ready connect URL built from the container's env |
ui.PaletteItem |
Kind, title, detail and the Run action; the palette only scores and draws them |
ui.ShortcutGroup |
Name, icon, color and rows; one table feeds the modal and its filter |
cli.Options |
The parsed mode, dialect, query and output options |
syntax.Language / syntax.Object |
Per-language lexing rules; ordered JSON objects |
sys.*, jvm.Dump |
Parsed command output and thread dumps, each with a pure parser covered by tests |
- One renderer, two outputs. The TUI and one-shot mode use the same table, JSON and log renderers. For one-shot output,
cli.Colorizeturns the tview tags into ANSI escapes or strips them. - Discovery asks first. Containers are only suggested, and nothing connects until you press Enter. Only one container of each kind can be checked, because a console holds one connection. If the scan finishes after you have started working, devcli shows a status-line hint instead of popping the dialog, and the containers stay available in
Cmd-K. - Dialogs never strand the UI. Every dialog (splash, connect prompt, palette, shortcuts, confirm) sits in a layer that swallows all mouse events, and keys always go to the top dialog. A click on a tab behind a dialog can no longer switch the tab underneath and leave a dialog that ignores Esc.
- Connection generations. Picking a container while a console is still connecting to its default bumps a counter. The late result of the first connect is then ignored, and the backend is never used by two goroutines at once.
- Shortcuts that work while typing.
Cmd/Ctrlshortcuts are handled before the editor. Plainq,?and digits only act outside editors, andCtrl-Qquits from anywhere. - Few libraries. Drivers only where a binary protocol needs one; everything else is hand-written.
- Safe destructive actions. Kill and remove use a dialog with Cancel selected by default, and a reused PID is refused.
Requirements: macOS, Go 1.26, Podman with podman-compose, JDK 25 (java, jcmd), clojure, sqlite3, curl.
./scripts/setup.sh
./scripts/sample-all.sh start
./scripts/ui.sh
./scripts/test-all.sh
./scripts/sample-all.sh stopInstall the binary on your PATH:
./scripts/install-macos.sh
devcli --help
./scripts/uninstall-macos.shEach step of install-macos.sh, and why:
- Runs
uninstall-macos.shfirst, so only one version is ever installed. - Rebuilds
bin/devcliand copies it into~/.local/bin, orDEVCLI_INSTALL_DIR. The copy goes through a temporary file and a rename, so macOS never runs a half-written, invalidly signed binary. - Checks that the installed binary runs.
- Adds the directory to
~/.zshrc(or~/.bash_profile) only when it is not already onPATH. - Warns when another
devclionPATHwould shadow it.
uninstall-macos.sh stops running devcli processes and removes the binary, and it is safe to run twice.
sample/java25/src is a multi-file Java 25 program: no build tool, launched with java sample/java25/src/DevcliJvm.java. It uses compact source files with void main(), import module, IO.println, ScopedValue, records, sealed interfaces and pattern-matching switch. Its threads are always doing something worth dumping:
| Threads | State in a dump | What they do |
|---|---|---|
order-producer, order-worker-1..4 |
TIMED_WAITING, WAITING (parking), RUNNABLE | a producer fills a BlockingQueue; workers take orders and hash paid orders inside a ScopedValue |
ledger-writer, inventory-writer |
BLOCKED, deadlocked | two synchronized monitors taken in opposite order |
payment-capture, payment-refund |
WAITING (parking), deadlocked | two ReentrantLocks taken in opposite order |
report-exporter, report-reader-1..3 |
RUNNABLE, BLOCKED | one thread holds a monitor for 1.5 s while readers pile up |
cpu-hasher |
RUNNABLE | burns about half a core computing SHA-256 |
cache-refresher, event-listener |
TIMED_WAITING, WAITING (on object monitor) | a scheduler refreshes a cache and notifyAlls a waiting listener |
HTTP-Dispatcher, stats-poller, HttpClient-* |
RUNNABLE | a JDK HttpServer on an ephemeral loopback port, polled with HttpClient every 250 ms |
ForkJoinPool-1-worker-* |
WAITING (parking) | carrier threads of 500 virtual threads |
./scripts/sample-java.sh start
./scripts/sample-java.sh dump
./scripts/sample-java.sh status
./scripts/sample-java.sh stopstart requires Java 25 or newer, and waits until the app has started its threads. dump prints the colored dump through devcli -threads, or jcmd when the binary is not built. The dump shows both deadlocks, and the parser flags all four threads, including the ReentrantLock pair.
sample-all.sh start starts the containers and waits until each one answers a real query. It then loads sample data (safe to run again):
| Store | Data |
|---|---|
| Postgres and MySQL | users with JSON profiles, orders, 200 events |
| Cassandra | devcli.users, devcli.events |
| Redis | JSON strings, a hash, list, sorted set, set and stream |
| SQLite | .run/devcli.db with hosts and 100 latency samples |
| Loki | api, worker and web log lines |
| Prometheus | scrapes itself, Loki and Grafana |
| Grafana | Loki and Prometheus datasources; devcli-logs (logs, errors per minute) and devcli-metrics (stat, gauge, timeseries and bar gauge panels over Prometheus) dashboards |
| JVMs | the Java 25 sample (scripts/sample-java.sh) and a Clojure loop |
test-all.sh runs these steps:
bash -non every script.go vet.- Race-enabled unit tests: lexers, fuzzy search, palette navigation,
Cmd-K/Cmd-digitrouting, shortcut filtering, the connect prompt, discovery parsing, the ANSI converter, flag parsing, one-shot output, the Redis protocol, all parsers,allon a real in-memory SQLite, Loki, Grafana and Prometheus against fake servers, Grafana panel queries and frame shaping, and chart rendering (axes, gaps, big digits, gauges, units, redraw on resize). - Integration tests against the running stack, including every one-shot mode,
allon Postgres, MySQL, SQLite and Cassandra with every ready query executed, and charts drawn from both provisioned Grafana dashboards.
These screens are real terminal cells rendered by devcli -capture against the local stack, photographed with npx playwright. Machine-specific values (processes, containers, CPU) change on every run.
./bin/devcli -capture .run/capture
for page in .run/capture/*.html; do
npx playwright screenshot --browser chromium --viewport-size '1560,990' "file://$PWD/$page" "printscreens/$(basename "${page%.html}").png"
doneThe gradient ASCII banner, version and tagline. The spinner line reports the container scan, here 7 data containers found. Any key skips the splash, and it closes by itself after a moment.
On the top row, the CPU panel draws a heat-colored braille history, with user/sys split, model, cores, uptime, load and process/thread counts. The middle row holds memory meters, volume usage with disk rates, and network graphs. The bottom panel lists top processes by CPU.
All processes sorted by CPU (▼ marks the sort column). The filter row accepts text or a PID, and the bottom panel shows the selected process. x / K open the confirm dialog.
Running containers first, with state dots, published ports and status. Enter opens a shell inside the selected container.
The JVM list shows the Java 25 sample and the Clojure loop, with language badges. The dump shows per-state counts and a red DEADLOCK banner with both deadlocks: the monitor pair and the ReentrantLock pair. Deadlocked threads are marked ☠, lock lines are orange, JDK frames dimmed and application frames bright.
A multi-line join with highlighted keywords, functions and operators. The result table colors numbers orange, JSON documents purple and NULL dim.
A jsonb query in JSON view (Ctrl-T): rows become ordered objects, the profile document is expanded, and short arrays stay on one line.
A join over hosts and metrics. The editor holds the next query, and the completion popup offers the live column meta and table metrics for met.
CQL over devcli.users. The set<text> column renders as a JSON array.
Three commands in one run: a JSON string pretty printed, HGETALL as an object, and a sorted set with scores.
Every env="dev" stream except cache hits, merged newest first. Levels are colored, labels are purple, and JSON lines are colorized inline.
chart devcli-metrics draws the provisioned Prometheus dashboard in the results pane: targets up and the average scrape duration as big-digit stats with sparklines, a percent gauge per job, and CPU and goroutine line charts with a legend. all lists the Grafana commands and ready commands such as chart devcli-logs and query prometheus up.
all lists the Prometheus commands first, then ready queries built from live metrics (also in Cmd-K, where Enter loads one): up, a counter rate, a gauge average and a histogram p95. Every metric with its type and help, the labels with their values, the scrape targets and the PromQL functions follow; scroll the results pane with PgUp/PgDn.
Shown after the splash. Each running data container is listed with its kind, name and connect URL, password masked. The first container of each kind is pre-checked; Enter connects the checked ones and Esc skips.
Searching post ranks the Postgres tab first, then the connect action for devcli-postgres, every Postgres container, a matching process, Prometheus metric names that can be inserted, and actions for the current console. Matched letters are underlined in cyan, and Enter goes to the selected item.
Every shortcut grouped by area, each group with its own icon and color, flowing in columns with a search box and a match count.
All scripts live in scripts/ and run from any directory of the repository.
| Script | What it does |
|---|---|
./scripts/setup.sh |
Checks tools, downloads Go modules, builds bin/devcli, pulls images |
./scripts/sample-all.sh start |
Starts every container, waits for each service, loads sample data, starts the sample JVMs |
./scripts/sample-all.sh stop |
Stops the sample JVMs and every container |
./scripts/sample-java.sh start|stop|status|dump |
Runs the Java 25 thread dump sample, reports it, dumps it, stops it |
./scripts/install-macos.sh |
Uninstalls any previous copy, rebuilds, installs devcli into ~/.local/bin |
./scripts/uninstall-macos.sh |
Removes the installed devcli |
./scripts/start-all.sh |
Checks the binary and runs sample-all.sh start |
./scripts/status.sh |
Shows every service port as UP or DOWN, plus the sample JVMs |
./scripts/test-all.sh |
Runs bash checks, vet, race-enabled unit tests and integration tests |
./scripts/ui.sh |
Opens the devcli TUI wired to the local stack |
./scripts/stop-all.sh |
Runs sample-all.sh stop |
./scripts/sql-console.sh |
Opens a native console: mysql, postgres, cassandra, redis, sqlite or prometheus |
Ports are declared in scripts/ports.env.
./scripts/setup.sh
./scripts/start-all.sh
./scripts/status.sh
./scripts/ui.sh
./scripts/stop-all.sh
















