A terminal UI for backend developers to explore and operate backend stacks (Postgres, MongoDB, Redis, RabbitMQ, Kafka, Loki) from one place.
querypro is plugin-first: the Go core owns the TUI, plugin lifecycle,
connections and storage, and knows nothing about any datastore. Every backend
is a TypeScript plugin in plugins/ that runs as a subprocess and talks to
the core over gRPC, with streaming for live data (tails, subscriptions,
consumers).
brew install --cask imduchuyyy/tap/queryproOr download a tarball from the GitHub releases, extract it and run
querypro from the extracted directory.
- Go (version in
go.mod) to build - Node.js 24.2+ on
PATHat runtime (plugins run TypeScript directly) - Docker, only for
make demoand the integration tests
make build && ./bin/querypro # or: make run
make demo # start every backend in docker, seeded tabsctrl+t opens a connection, enter runs a query, ctrl+o opens a resource,
ctrl+x shows its actions, / opens the command palette, !name query runs
against another tab. Wide results stay readable: a table shows the columns
that fit, shift+←/→ (or a horizontal wheel) scrolls to the rest, and
clicking a row opens every column of it in a popup (c copies it). The
palette's Help lists every key.
| Plugin | URI | Queries |
|---|---|---|
| postgres | postgres://user:pass@host:5432/db?sslmode=require |
SQL, \d name, \dt, \dn, \l |
| mongodb | mongodb://host:27017/db |
mongo shell: db.x.find({}), use db, show collections |
| redis | redis://:pass@host:6379/0, rediss://… |
any command, SUBSCRIBE/PSUBSCRIBE/MONITOR stream live |
| rabbitmq | amqp://user:pass@host:5672/vhost?management=http://host:15672 |
queues, peek q, tap exchange #, publish, … |
| kafka | kafka://user:pass@h1:9092,h2:9092?ssl=true&mechanism=scram-sha-512 |
topics, describe t, tail t, produce t v, … |
| loki | https://user:pass@host?org=tenant |
LogQL, since 6h {app="x"}, tail {app="x"}, labels |
Unknown commands reply with the list of supported ones.
Saved connections are shared with the TUI:
querypro conn add local postgres postgres://user:pass@localhost/db
querypro conn ls
querypro query local 'SELECT * FROM users LIMIT 5'
querypro query cache SUBSCRIBE news # live streams run until ctrl+c
querypro conn rm localTables print aligned on stdout, the summary goes to stderr.
querypro mcp serves your saved connections to AI clients over stdio:
tools connections, resources, actions and query (marked
destructive, so clients ask before running it). Live streams are collected
for seconds (default 5, max 60) or 500 lines. URIs are never exposed.
claude mcp add querypro -- /path/to/querypro mcpOther clients: {"command": "/path/to/querypro", "args": ["mcp"]}.
Everything lives in $QUERYPRO_HOME, default os.UserConfigDir()/querypro
(~/Library/Application Support/querypro on macOS, ~/.config/querypro on
Linux). The directory is 0700 and every file 0600.
| File | Contents |
|---|---|
connections.json |
saved connections, including their URIs and secrets |
settings.json |
theme, sidebar, mouse |
history.json |
last 500 queries |
logs/<kind>.log |
plugin stderr, truncated past 10 MB |
Writes are atomic (temp file + rename). A corrupt file stops startup with its path instead of being overwritten.
Plugins are found in -plugins, $QUERYPRO_PLUGINS, plugins/ next to the
binary, or ./plugins.
make tools # pinned golangci-lint, buf, protoc-gen-go(-grpc)
make test # go test -race + plugin unit tests
make it # integration tests, needs `make services`
make lint # golangci-lint, tsc, buf lint
make proto # regenerate internal/plugin/pb from proto/
make dist # dist/querypro-<version>-<os>-<arch>.tar.gzA release is the binary plus plugins/ (with production node_modules) and
proto/ side by side; make dist builds it for the current GOOS/GOARCH.
A plugin is a directory in plugins/ with a plugin.json:
{
"kind": "postgres",
"code": "PG",
"color": "#5b9bd5",
"uri": "postgres://postgres:postgres@localhost:5432/postgres",
"placeholder": "SELECT * FROM users LIMIT 10;",
"command": ["node", "index.ts"]
}and an entry point that calls serve from plugins/sdk:
import { serve, table, type Session } from "../sdk/index.ts";
serve(async (uri): Promise<Session> => ({
server: "Example 1.0",
resources: async () => [{ kind: "table", name: "users" }],
actions: (r) => [{ name: "Preview", query: `SELECT * FROM ${r.name}` }],
query: async (q, signal) => table(["q"], [[q]]),
close: async () => {},
}));query returns table(...), text(...), or live(signal, summary, start)
for streams; dispatch maps small command languages. Any executable that
speaks the protocol below works; the SDK is just the TypeScript way.
The contract is proto/querypro/plugin/v1/plugin.proto.
- The core starts
commandin the plugin directory withQUERYPRO_SOCKET=<path>and a stdin pipe. - The plugin serves
PluginServiceon that unix socket and then writesquerypro-plugin 1\nto stdout. Anything else fails the start. Connect(uri)returns a session id used by every other call.Querystreams onetableortextevent for a finished result, or oneliveevent followed bylineevents until the core cancels.- A call on a session whose backend connection is gone fails with
UNAVAILABLE; the core then reconnects the tab. - The plugin exits when its stdin closes. If it dies, the core marks its tabs disconnected and respawns it on the next connect.