Daily Dev Experimentβ a short series where we hand a real task to an AI on the Tabularis codebase and report exactly what happened. No staged demos. Today's run got a little out of hand.
We asked Fable 5 (Anthropic's new model) to add a command-line interface to the app. That's less trivial than it sounds: this is a Tauri app β Rust backend, React frontend β so "a CLI" means reaching every saved connection (keychain passwords, SSH/K8s tunnels, plugin drivers) without booting the GUI or starting the Tauri runtime at all.
One prompt. A coffee break. A draft PR waiting at the end of it.
Here's what was in it.
The numbers #
| Lines added | 1,810 | | Lines removed | 319 | | Files touched | 16 | | New unit tests | 37 (full suite: 692 passing) | | Pre-existing bugs fixed | 2 β we didn't ask for either | | Wall-clock time | ~30 minutes |
We've spent longer naming a variable.
Video unavailable
What we expected #
A coherent set of clap
subcommands, addressed by connection id or name:
| Command | What it does |
|---|---|
tabularis connections (ls ) |
|
List saved connections β table or --json |
|
tabularis databases <conn> |
|
| List databases on the server | |
tabularis schemas <conn> |
|
| List schemas | |
tabularis tables <conn> |
|
List tables (-d , --schema , --json ) |
|
tabularis describe <conn> <table> |
|
| Columns, indexes, foreign keys | |
tabularis query <conn> [SQL] (q ) |
|
| One-shot query, stdin pipe, or interactive shell | |
tabularis install-cli |
|
Symlink the binary into a PATH directory |
The detail we like most: query
picks its mode from the invocation. SQL argument β one-shot. Piped stdin β executes the piped statement. Interactive TTY with no SQL β drops into a proper SQL shell.
tabularis query my-db "select id, name from customers" --format csv > out.csv
echo "select count(*) from orders" | tabularis q my-db
tabularis query my-db
The interactive shell is backed by rustyline
: line editing, persistent history (cli_history.txt
in the app config dir), multi-line statements that fire on a terminating ;
, Ctrl-C
drops the current buffer, Ctrl-D
exits. Plus psql-style meta commands:
\l list databases \f table|json|csv output format
\dn list schemas \limit N row limit (0 = unlimited)
\dt list tables \schema NAME set schema
\d T describe table \use DB switch database
\q quit \? help
Result data goes to stdout, logs go to stderr β so piping stays clean, and you get a non-zero exit code on failure. Exactly the kind of detail a human means to remember and usually forgets.
It even kept the GUI-launch fallback intact: macOS still passes junk like -psn_*
to the binary on launch, and that must keep booting the GUI β while a misspelled subcommand should surface clap's error instead of silently opening a window. It threaded that needle, and wrote tests asserting the exact clap
error kinds that fall through to the GUI versus the ones that don't.
What we did NOT expect #
1. It found the refactor before writing a single feature.
The app already ships an MCP server, and that server already knew how to resolve a saved connection β decrypt the keychain password, open the SSH/K8s tunnel, register the right plugin driver. Instead of duplicating all of that for the CLI, Fable 5 dug through the codebase, realized the logic was buried inside mcp/mod.rs
, and pulled it out into a shared headless.rs module that both the MCP server and the new CLI consume. The MCP server now just wraps the shared helpers with its JSON-RPC error type β zero behavior change on that side.
That's 242 lines deleted from mcp/mod.rs
and a 214-line module born. It's the refactor we would have done β unprompted, because it understood why duplicating connection resolution was the wrong move.
It also didn't dump everything into one file. The old 52-line cli.rs
became a real module:
src-tauri/src/cli/
βββ mod.rs clap definitions, GUI-fallback logic (182 lines)
βββ run.rs command dispatch + execution (338 lines)
βββ repl.rs the interactive shell (282 lines)
βββ output.rs table / JSON / CSV rendering (138 lines)
βββ install.rs install-cli symlink logic (97 lines)
βββ *_tests.rs args, output, run, install (442 lines)
2. It fixed two real bugs that were already there.
keychain_utils
was logging withprintln!
, dumping straight tostdout. Harmless in a GUI β silently corrupting any piped CSV/JSON the moment a CLI exists. And it was bypassing the in-app log buffer too. It rerouted everything through thelog
crate.- Headless processes never called
sqlx::any::install_default_drivers()
, so the default connection-test pathpanicked. It installed the drivers in the sharedheadless::register_drivers()
β which also quietly fixed the existing--mcp
mode.
Neither was in the prompt. It found them because it actually traced the execution path from "user pipes a query" to "bytes hit the terminal" and noticed what would break along the way.
3. It handled multi-database connections properly.
Multi-db connections used to resolve to their first database, which made the others unreachable outside the GUI. Every db-scoped command now takes -d/--database
, and the shell's \use <db>
doesn't just flip a variable β it validates the switch with a connection test before applying it, and the prompt shows where you are (Demo Β· MySQL:blog_demo>
). Under the hood it reuses the GUI's per-call database-override semantics (DatabaseSelection::Single
) instead of inventing a parallel mechanism.
4. It tested its own work against a live database.
The 37 unit tests aren't padding: clap parsing (including the GUI-fallback error kinds), table/CSV/JSON rendering (column alignment, control-character escaping, CSV quoting), limit and database-override semantics, and the install-cli
symlink logic β idempotency, refusal to clobber a foreign file, --force
.
Then it went further: it ran the new shell against a real MySQL connection, switched across the three demo databases with \use
, eyeballed the table/CSV/JSON output, and fixed the formatting it didn't like β before handing over the PR.
Finding Tabularis useful? Star it on GitHub β it takes a second and helps more developers discover the project. Star on GitHub## So... do we trust it?
No. It's a draft PR and we're reviewing every line before anything ships. There are real limitations β which, to its credit, it flagged itself in the PR description:
- On Windows release builds the binary has no attached console (
windows_subsystem = "windows"
), so CLI output is invisible there. Same constraint the--mcp
mode always had. - Each shell statement runs on its own pooled connection, so session state β
SET
, transactions, temp tables β doesn't persist between statements. It even documents that caveat inside\?
.
But here's the part that stuck with us: the review is genuinely worth doing. This isn't autocomplete spitting out a function body. It's an agent that read the architecture, found the seam we'd have found, refactored toward it, and cleaned up two messes on the way out β then wrote 37 tests and a PR description more thorough than most humans bother with.
Two years ago this was Tab-complete. Today it's a coworker whose work we have to code-review.
π Read the full PR β every line, the real description: #313
Where this is going #
This experiment isn't a side quest β it's the direction. We're building a database client that's native to this new workflow: a built-in MCP server so agents like the one in this post can work against your databases, with approval gates, a read-only mode, and a full audit log so they do it on your terms. An AI assistant that also runs on open-source models β locally via Ollama, with zero schema data leaving your machine. And now a CLI, born from that same headless core.
An agent wrote today's feature. The point is that tomorrow, your agents get a first-class, safe way to use it.