When we started building Bunny Database, we needed a shell-like experience. Naturally, we reached for libsql-shell-go, the official Go-based shell from the libSQL project. It's a solid tool, and it served us well in early development.
But as we began building the upcoming bunny.net CLI in TypeScript, maintaining a separate Go binary for just the shell felt like carrying two toolchains for one job. We wanted a shell that could be embedded directly into our CLI, share the same authentication flow, and ship as a single binary. So we rewrote it from scratch in TypeScript.
The result is
@bunny.net/database-shell
— a standalone, interactive SQL shell for libSQL databases that also powers
bunny db shell
inside the upcoming bunny.net CLI.
The interactive Bunny Database shell is currently in public preview. While fully functional, we recommend exercising caution with production workloads as we continue refining the experience.
Connect and query
You will need Node.js 18 or later installed and an existing database to continue. If you don't have one, create one.
Navigate to Dashboard > Edge Platform > Database > [Select Database] > Access to find your database URL and generate an access token.
Once you’re ready, execute the
npx
command to connect to your database:
You get a readline-powered REPL with multi-line SQL support, persistent command history, and query timing:
Type .help
for commands, .quit to exit.
→ SELECT
id, name, email FROM users
WHERE created_at
> '2025-01-01';
┌────┬───────────┬─────────────────────┐
│
id │ name │ email │
├────┼───────────┼─────────────────────┤
│ 1 │ Alice │ a****e@example.com │
│ 2 │ Bob │ b****b@example.com │
└────┴───────────┴─────────────────────┘
2 rows (4ms)
Dot-commands for database introspection
If you've used SQLite or libSQL’s shell, these will feel familiar. Dot-commands give you quick access to your schema without writing
SELECT
queries against
sqlite_master:
.tableslist all tables.schema [table]shows CREATE statements.describe tablecolumn names, types, nullability, defaults, and primary keys.indexes [table]list indexes with their target columns.fk tableshow foreign key relationships.erdisplay a text-based entity-relationship overview of your entire schema, which is useful for getting to grips in an unfamiliar database without jumping through.describecalls
users
id INTEGER PK
email TEXT
created_at TEXT
└─ orders.user_id
orders
id INTEGER PK
user_id INTEGER FK →
users.id
total REAL
created_at TEXT
For data operations:
.count tablerow count.size tablestorage size estimate.dump [table]export as SQL INSERT statements.read file.sqlexecute a SQL file
Bunny Database charges against a read quota based on rows scanned. Commands that scan full tables
(.count,
.size,
.dump)
warn you before running and ask for confirmation. So an accidental query on a large table doesn’t burn through your quota unexpectedly.
Saved views
Dot-commands are great for introspection, but some queries you run over and over. Saved views let you name and persist any query so you can re-run it without retyping.
id) as orders, sum(o.total) as revenue
FROM users u JOIN orders o ON o.user_id = u.
id
GROUP BY u.
id ORDER BY revenue DESC LIMIT 10;
┌──────────┬────────┬─────────┐
│ name │ orders │ revenue │
├──────────┼────────┼─────────┤
│ Alice │
42 │ 8400.00 │
│ Bob │
31 │ 6200.00 │
└──────────┴────────┴─────────┘
2 rows (12ms)
→ .save
top-customers
✓ Saved view
'top-customers'
Next session, next machine, same query:
The following commands cover the full lifecycle:
.save NAMEsaves the last executed query as a named view.view NAMEexecutes a saved view.viewslists all saved views for this database.unsave NAMEdeletes a saved view
Names follow the same rules as filenames: alphanumeric, hyphens, and underscores only. No dots, slashes, or spaces.
Personal and shared views
Views are stored as plain
.sql
files, scoped per database. They live in your global config directory
~/.config/bunny/views/<database-id>/,
personal to you and available across every project.
You can override the storage location with the
--views-dir
flag to point to any directory, for example, a folder inside your repo. Because views are just
.sql
files, you can commit them and share them with your team: common reporting queries, debugging helpers, and onboarding examples. Everyone gets the same set.
Five output modes
Switch formats on the fly with
.mode:
defaultclean, borderless columnstablebordered ASCII tablejsonarray of objects, ready for piping tojqcsvproper RFC-compliant CSV with escaped fieldsmarkdownGitHub-flavored pipe tables, useful for pasting into issues or docs
You can also set the mode at launch with
--mode json
for scripting.
Automatic sensitive column masking
Most of the time, you don’t actually need to see the raw value of a
password_hash
or
api_key
column; you just need to know it’s there. The shell masks sensitive column names automatically, keeping raw secrets out of your terminal, your scrollback buffer, and anything you pipe or paste.
Detected patterns include
password,
secret,
api_key,
auth_token,
ssn,
and similar names. Emails get a partial mask
a****e@example.com;
secrets are fully redacted
********.
This works across all output modes, not just the interactive terminal. If you're exporting to CSV or JSON, masked columns stay masked.
Toggle it off with
.unmask
when you actually need the raw values, or launch with
--unmask.
Non-interactive mode
For scripting or quick lookups, skip the REPL entirely:
Install globally
npm install -g @bunny.net/database-shell
#
Inline query
bsql libsql://my-database.bunnydb.net --token ...
"SELECT count(*) FROM orders"
#
Execute a file
bsql libsql://my-database.bunnydb.net --token ... seed.sql
#
JSON output for scripting
bsql libsql://my-database.bunnydb.net --token ... --mode json
"SELECT * FROM products"
File execution splits on semicolons (properly handling quoted strings and comments) and stops on the first error.
Thank you libsql-shell-go
We want to acknowledge the
libsql-shell-go
project. It set the standard for what a libSQL shell should feel like: the dot-commands, the output modes, and the overall interaction model.
We released an early look at a wrapper on top of
libsql-shell-go,
but it was clear that we could do more to improve the experience for Bunny Database users. Our implementation follows the same spirit while adapting it to the TypeScript ecosystem and the bunny.net developer experience.
Why rewrite in TypeScript?
Three reasons:
- Single dependency chain — The upcoming bunny.net CLI is TypeScript. Embedding the shell directly means no sidecar binary, no version drift, and no separate release pipeline.
-
Shared auth and config — When you run
bunny db shell, the CLI resolves your database credentials from your project's.env, your authenticated profile, or explicit flags. The same way every other upcomingbunny dbcommand works. A separate Go binary can't participate in that flow without shelling out or duplicating logic. -
Standalone when you want it — Despite being built for the bunny.net CLI,
bsqlis published as its own package(@bunny.net/database-shell)with zero CLI dependencies. You can use it as a library.
Get started
Install the bunny.net CLI and connect to your first database:
npm install -g @bunny.net/database-shell
bsql
Or use the database-shell directly via npx:
Make sure to join us on Discord to share your experience and any feedback.
What's next
A platform is only as good as its developer experience, and that experience increasingly lives in the terminal. Our goal with the bunny.net CLI is to give you a single tool for everything on the bunny.net stack. No tab-switching, no copy-pasting tokens between dashboards. The interactive Database shell is the first piece of that vision.
Over the coming weeks, we’ll be rolling out commands for Database CRUD, Magic Containers, Edge Scripting, and Storage. The goal is a workflow where you can go from an empty project to a deployed, queryable application without ever leaving your terminal.

