{"slug": "fable-5-opened-a-1800-line-pr-on-tabularis-in-30-minutes", "title": "Fable 5 Opened a 1,800-Line PR on Tabularis in 30 Minutes", "summary": "Anthropic's Fable 5 AI model autonomously generated a 1,810-line pull request for the Tabularis codebase in 30 minutes, adding a full command-line interface to the Tauri app. The AI refactored existing code into a shared module, wrote 37 unit tests, and fixed two pre-existing bugs without being asked. The result demonstrates AI's ability to handle complex, multi-file software engineering tasks with minimal human input.", "body_md": "# Fable 5 Opened a 1,800-Line PR on Tabularis in 30 Minutes\n\nDaily 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.\n\nWe 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.\n\nOne prompt. A coffee break. A draft PR waiting at the end of it.\n\nHere's what was in it.\n\n## The numbers\n\n| Lines added | 1,810 |\n| Lines removed | 319 |\n| Files touched | 16 |\n| New unit tests | 37 (full suite: 692 passing) |\n| Pre-existing bugs fixed | 2 — we didn't ask for either |\n| Wall-clock time | ~30 minutes |\n\nWe've spent longer naming a variable.\n\nVideo unavailable\n\n## What we expected\n\nA coherent set of `clap`\n\nsubcommands, addressed by connection id **or** name:\n\n| Command | What it does |\n|---|---|\n`tabularis connections` (`ls` ) |\nList saved connections — table or `--json` |\n`tabularis databases <conn>` |\nList databases on the server |\n`tabularis schemas <conn>` |\nList schemas |\n`tabularis tables <conn>` |\nList tables (`-d` , `--schema` , `--json` ) |\n`tabularis describe <conn> <table>` |\nColumns, indexes, foreign keys |\n`tabularis query <conn> [SQL]` (`q` ) |\nOne-shot query, stdin pipe, or interactive shell |\n`tabularis install-cli` |\nSymlink the binary into a `PATH` directory |\n\nThe detail we like most: `query`\n\npicks 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.\n\n```\n# one-shot query, pipe-friendly\ntabularis query my-db \"select id, name from customers\" --format csv > out.csv\n\n# pipe a statement in\necho \"select count(*) from orders\" | tabularis q my-db\n\n# no SQL on a TTY → drop into a proper SQL shell\ntabularis query my-db\n```\n\nThe interactive shell is backed by `rustyline`\n\n: line editing, persistent history (`cli_history.txt`\n\nin the app config dir), multi-line statements that fire on a terminating `;`\n\n, `Ctrl-C`\n\ndrops the current buffer, `Ctrl-D`\n\nexits. Plus psql-style meta commands:\n\n```\n\\l    list databases        \\f table|json|csv   output format\n\\dn   list schemas          \\limit N            row limit (0 = unlimited)\n\\dt   list tables           \\schema NAME        set schema\n\\d T  describe table        \\use DB             switch database\n\\q    quit                  \\?                  help\n```\n\nResult 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.\n\nIt even kept the GUI-launch fallback intact: macOS still passes junk like `-psn_*`\n\nto 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`\n\nerror kinds that fall through to the GUI versus the ones that don't.\n\n## What we did NOT expect\n\n**1. It found the refactor before writing a single feature.**\n\nThe app already ships an [MCP server](/wiki/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`\n\n, 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.\n\nThat's 242 lines deleted from `mcp/mod.rs`\n\nand 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.\n\nIt also didn't dump everything into one file. The old 52-line `cli.rs`\n\nbecame a real module:\n\n```\nsrc-tauri/src/cli/\n├── mod.rs       clap definitions, GUI-fallback logic     (182 lines)\n├── run.rs       command dispatch + execution             (338 lines)\n├── repl.rs      the interactive shell                    (282 lines)\n├── output.rs    table / JSON / CSV rendering             (138 lines)\n├── install.rs   install-cli symlink logic                 (97 lines)\n└── *_tests.rs   args, output, run, install               (442 lines)\n```\n\n**2. It fixed two real bugs that were already there.**\n\n`keychain_utils`\n\nwas logging with`println!`\n\n, dumping straight to**stdout**. 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 the`log`\n\ncrate.- Headless processes never called\n`sqlx::any::install_default_drivers()`\n\n, so the default connection-test path**panicked**. It installed the drivers in the shared`headless::register_drivers()`\n\n— which also quietly fixed the existing`--mcp`\n\nmode.\n\nNeither 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.\n\n**3. It handled multi-database connections properly.**\n\nMulti-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`\n\n, and the shell's `\\use <db>`\n\ndoesn'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>`\n\n). Under the hood it reuses the GUI's per-call database-override semantics (`DatabaseSelection::Single`\n\n) instead of inventing a parallel mechanism.\n\n**4. It tested its own work against a live database.**\n\nThe 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`\n\nsymlink logic — idempotency, refusal to clobber a foreign file, `--force`\n\n.\n\nThen it went further: it ran the new shell against a real MySQL connection, switched across the three demo databases with `\\use`\n\n, eyeballed the table/CSV/JSON output, and fixed the formatting it didn't like — before handing over the PR.\n\n**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?\n\nNo. 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:\n\n- On Windows release builds the binary has no attached console (\n`windows_subsystem = \"windows\"`\n\n), so CLI output is invisible there. Same constraint the`--mcp`\n\nmode always had. - Each shell statement runs on its own pooled connection, so session state —\n`SET`\n\n, transactions, temp tables — doesn't persist between statements. It even documents that caveat inside`\\?`\n\n.\n\nBut 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.\n\nTwo years ago this was Tab-complete. Today it's a coworker whose work we have to code-review.\n\n👉 **Read the full PR — every line, the real description:** [#313](https://github.com/TabularisDB/tabularis/pull/313)\n\n## Where this is going\n\nThis 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](/wiki/mcp-server) so agents like the one in this post can work against your databases, with [approval gates](/wiki/mcp-approval-gates), a [read-only mode](/wiki/mcp-readonly-mode), and a full [audit log](/wiki/ai-audit-log) so they do it on your terms. An [AI assistant](/wiki/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.\n\nAn agent wrote today's feature. The point is that tomorrow, your agents get a first-class, safe way to use it.", "url": "https://wpnews.pro/news/fable-5-opened-a-1800-line-pr-on-tabularis-in-30-minutes", "canonical_source": "https://tabularis.dev/blog/fable-5-opened-a-1800-line-pr-in-30-minutes", "published_at": "2026-06-10 10:00:00+00:00", "updated_at": "2026-06-25 17:01:22.887991+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models", "ai-agents", "developer-tools"], "entities": ["Anthropic", "Fable 5", "Tabularis", "Tauri", "clap", "rustyline", "MCP"], "alternates": {"html": "https://wpnews.pro/news/fable-5-opened-a-1800-line-pr-on-tabularis-in-30-minutes", "markdown": "https://wpnews.pro/news/fable-5-opened-a-1800-line-pr-on-tabularis-in-30-minutes.md", "text": "https://wpnews.pro/news/fable-5-opened-a-1800-line-pr-on-tabularis-in-30-minutes.txt", "jsonld": "https://wpnews.pro/news/fable-5-opened-a-1800-line-pr-on-tabularis-in-30-minutes.jsonld"}}