Introducing the interactive Bunny Database shell

Posted by:

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/d​atabase-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:

npx @​bunny​.net​/database​-shell libsql://... --token ...

You get a readline-powered REPL with multi-line SQL support, persistent command history, and query timing:

✓ Connected to database
  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:

  • .tables list all tables
  • .schema [table] shows CREATE statements
  • .describe table column names, types, nullability, defaults, and primary keys
  • .indexes [table] list indexes with their target columns
  • .fk table show foreign key relationships
  • .er display a text-based entity-relationship overview of your entire schema, which is useful for getting to grips in an unfamiliar database without jumping through .describe calls
→  .er

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 table row count
  • .size table storage size estimate
  • .dump [table] export as SQL INSERT statements
  • .read file.sql execute 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.

→  SELECT u.name, count(o.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:

→ .view top-customers

The following commands cover the full lifecycle:

  • .save NAME saves the last executed query as a named view
  • .view NAME executes a saved view
  • .views lists all saved views for this database
  • .unsave NAME deletes 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:

  • default clean, borderless columns
  • table bordered ASCII table
  • json array of objects, ready for piping to jq
  • csv proper RFC-compliant CSV with escaped fields
  • markdown GitHub-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 upcoming bunny db command 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, bsql is 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:

npx @b​unny​.net/​database​-​shell libsql://your-database.bunnydb.net --token your-token

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.