An AI-powered database agent with a professional terminal UI — inspect, query, and administer SQL databases in natural language, in the style of Claude Code and opencode.
┌──────────┐ AgentMessage / ToolSpec ┌─────────────┐
│ TUI │ ─────────────────────────────► │ Agent Loop │
└──────────┘ └──────┬──────┘
│ LLMAdapter interface
┌───────────┬─────┴────────┬─────────┐
▼ ▼ ▼ ▼
OpenAI OpenRouter Anthropic Gemini · Ollama
(provider-agnostic loop, streaming, abortable)
│ DatabaseDriver interface
┌──────────┬──────┴────────┬───────────┐
▼ ▼ ▼ ▼
SQLite · PostgreSQL · MySQL/MariaDB · SQL Server · Oracle
- 5 built-in LLM providers — OpenAI, OpenRouter (one key, hundreds of models as
vendor/model), Anthropic, Google Gemini, and Ollama (local), plus saved custom OpenAI-compatible gateways (LM Studio, vLLM, and others) - 6 databases — SQLite, PostgreSQL, MySQL, MariaDB, SQL Server (T-SQL), and Oracle Database with schema introspection
- Performance-first agent —
performance_report(broken/disabled/invalid, missing, unused and duplicate indexes · cache hit · wait stats · top expensive queries · blocking · bloat) andexplain_query(dialect-aware plan analysis) are core tools; the system prompt makes performance the primary workflow - Claude-Code-class TUI — streaming markdown, collapsible tool blocks, interactive table viewer, live token/cost status bar
- Token-level streaming — SSE for all providers, cancel any response with
Esc - MCP client — connect trusted Model Context Protocol servers over stdio or Streamable HTTP; tools are opt-in per session
- Hard safety rails — read-only by default, enforced LIMIT, single-statement rule, DROP/ALTER always blocked, path sandbox, permission tiers
- System operations — stats, server info, CSV/JSON export, CSV import, backups (
VACUUM INTO,pg_dump,mysqldump,BACKUP DATABASE), local database discovery (Oracle backup is operator-managed) - File & shell tools — read/write/edit/glob/grep files, run shell commands, behind explicit opt-in gates
- Persistent sessions — full transcript (tool results, usage, timestamps) auto-saved and restorable with
--resume - Agent Skills — SKILL.md packages following the open standard used by Claude Code, Codex CLI, GitHub Copilot and Qwen Code: reusable playbooks the model loads on demand, invocable directly as
/<name>
Requires Node.js ≥ 22.5 (built-in node:sqlite; falls back to better-sqlite3).
npm install -g dbagent # or run without installing:
npx dbagent@latest chat --db sqlite://./app.db --provider openrouterFrom source:
git clone https://github.com/ParsaAzima/DBAgent.git && cd DBAgent
npm install
npm run build
cp dbagent.example.toml dbagent.toml # provider + database settings (or cp .env.example .env)# 1. Create a demo database (e-commerce: customers, orders, products)
node scripts/make-demo-sqlite.mjs
# 2. Chat with it
node dist/cli.js chat --db sqlite://./demo.db --provider openrouterAsk things like "Which product sold the most last month?" or "Export the customers table and tell me the DB size" — the agent explores the schema, writes and runs SQL, and answers with rendered tables.
MariaDB uses the MySQL-compatible driver: mariadb://host:3306/database. Oracle Database supports service-name URLs (oracle://user:password@host:1521/FREEPDB1), SID via ?sid=ORCL, and TCPS via the oracle+tcps:// scheme. Oracle TCPS connections require the Oracle wallet/network configuration expected by node-oracledb.
Save each database once under a short name. Connections are stored in your user profile (~/.dbagent/connections.json) and used by default for chat, ask, and health:
dbagent db add analytics "postgres://user:password@localhost:5432/analytics"
dbagent db add local "sqlite://./app.db"
dbagent db use analytics
dbagent chat --provider openrouterUse dbagent db list to see saved names, dbagent db use <name> to switch the default, or pass --db <name> for a one-off connection. dbagent db remove <name> deletes a saved connection. The existing --db <url>, DBAGENT_DB_URL, and [database] url options continue to work; explicit --db and DBAGENT_DB_URL take precedence over the saved default. Connection storage is restricted to the current user where the operating system supports file permissions.
Fetch the live model list from the provider before choosing a model:
dbagent models openrouter
dbagent chat --provider openrouter --model anthropic/claude-sonnet-4Inside an interactive chat, /model lists available models and /model 2 or /model <model-id> switches models without restarting.
Add any OpenAI-compatible API endpoint as a named provider. The API key is read from an environment variable and is never saved in the provider profile:
dbagent provider add lmstudio --base-url http://localhost:1234/v1 --model local-model
dbagent provider add mygateway --base-url https://llm.example.com/v1 --model model-id --api-key-env MYGATEWAY_API_KEY
$env:MYGATEWAY_API_KEY = "..." # PowerShell; use export MYGATEWAY_API_KEY=... in Bash
dbagent provider list
dbagent models mygateway
dbagent chat --provider mygatewayProvider profiles are stored in ~/.dbagent/providers.json with user-only file permissions. Use dbagent provider remove <name> to remove one. Built-in model listing is supported for OpenAI-compatible APIs, Anthropic, Gemini, and Ollama.
Add trusted servers to ~/.dbagent/mcp.json (or use --mcp-config <path>). Servers use stdio with command, or Streamable HTTP with url:
{
"mcpServers": {
"local-tools": {
"command": "python",
"args": ["-m", "my_mcp_server"],
"env": { "SERVICE_TOKEN": "${SERVICE_TOKEN}" }
},
"team-service": {
"url": "https://mcp.example.com/mcp",
"headers": { "Authorization": "Bearer ${MCP_ACCESS_TOKEN}" }
}
}
}dbagent mcp list displays configured servers without starting them; dbagent mcp test <name> connects to one and lists its tools. MCP servers are never started automatically. Enable them for a session with dbagent chat --allow-mcp or dbagent ask --allow-mcp. Only enable servers you trust: their tools can perform external actions, and model-called MCP tools run without a per-call confirmation. Environment placeholders are resolved at runtime; do not store secrets directly in the config.
chat opens a full-screen terminal experience in the alternate screen buffer when both stdin and stdout are TTYs. The header and status bar stay fixed, the prompt remains at the bottom, and recent conversation activity uses the available viewport; older transcript messages remain saved and restored with the session. Non-interactive streams fall back to a plain REPL; --plain forces it.
| Key / Command | Action |
|---|---|
| (type + Enter) | Ask anything — answers stream token-by-token as rendered markdown |
Esc |
Abort the in-flight response |
ctrl+o |
Interactive table viewer over the latest query result (scroll ↑↓←→/jkhl, page PgUp/PgDn, jump g/G) |
ctrl+t |
Expand/collapse tool-call details (SQL, result previews) |
ctrl+l |
Clear screen (session keeps the transcript) |
PgUp / PgDn |
Browse older/newer transcript history |
| Mouse wheel | Scroll the conversation viewport without changing the prompt |
↑ / ↓ / Tab |
Navigate and complete slash-command suggestions; outside the menu, arrows browse input history |
/help [command] |
List commands or show details for one command |
/model [name|number] |
List available models or switch mid-session, e.g. /model 2 or /model anthropic/claude-sonnet-4 |
/theme [name] |
List or switch terminal themes; switching is saved for future sessions |
/schema · /refresh |
Show tables/columns/relationships · reload the live schema after external DDL |
/status · /cost |
Show session/database/model state · cumulative tokens and estimated cost |
/clear · ctrl+l |
Clear the visible transcript; the agent context is retained |
/save · /reset · /exit |
Persist the session · clear transcript/context/usage · save and quit |
/skills · /<name> |
List installed skills · run one directly (its instructions are inlined into the prompt) |
Start typing / to see matching commands (installed skills appear in the menu too). Use ↑/↓ to highlight a suggestion, Tab to complete it, or Enter to complete a partial command; Enter on a complete command runs it. /quit is an alias for /exit, and /cls is an alias for /clear. Unknown slash commands are reported instead of being sent to the model.
Choose from catppuccin-mocha (default), dracula, tokyo-night, one-dark, nord, gruvbox-dark, solarized-dark, monokai, kanagawa, github-dark, github-light, and catppuccin-latte. Use dbagent theme list to view them, dbagent theme set <name> to save a preference, or dbagent chat --theme <name> for a one-session choice. Set DBAGENT_THEME to override the saved theme; use /theme [name] while chatting to inspect or switch themes.
The status bar is always visible: current provider/model, safety mode, session id, cumulative tokens in/out, and estimated cost (built-in price table for popular models; shows $0 when pricing is unknown).
Session transcripts — including tool results, per-turn usage and timestamps — are auto-saved after every turn to ~/.dbagent/sessions/ and restored with --resume.
Database URLs: sqlite://./app.db · postgres://user:pass@host:5432/db?ssl=true · mysql://user:pass@host:3306/db · sqlserver://user:pass@host:1433/db?encrypt=true&trustServerCertificate=false (named instance: &instance=SQLEXPRESS)
Skills are reusable SKILL.md packages — the open Agent Skills standard shared by Claude Code, Codex CLI, GitHub Copilot and Qwen Code. A skill packages a playbook (a slow-query triage, a weekly sales report, a data audit) as markdown. DBAgent ships with built-in database exploration, SQL analysis, performance triage, and data-quality audit skills. Only names and descriptions are loaded up front; full instructions load when a skill is used.
Where skills live — bundled skills are always available. User skills override bundled defaults; project skills override both:
| Location | Scope |
|---|---|
bundled skills/<name>/SKILL.md |
built-in DBAgent playbooks |
.dbagent/skills/<name>/SKILL.md |
this project — commit it so the whole team (and every agent in CI) gets it |
~/.dbagent/skills/<name>/SKILL.md |
your personal skills, available in every project on this machine |
Format — YAML frontmatter between --- markers (description is required; name is optional and defaults to the directory name) plus a markdown body with the instructions:
---
name: slow-query-triage
description: Diagnose a slow SQL query end to end. Use when the user reports slowness for a specific statement.
---
1. Run `explain_query` on the exact SQL the user reports.
2. Check the plan for full scans, missing indexes and estimate gaps.
3. Rank the findings by impact, then present the exact CREATE INDEX SQL with its trade-offs.How skills run:
- Automatic — the model sees each skill as a
name: descriptionline in its system prompt; when a task matches, it calls theuse_skilltool and follows the returned instructions. - Explicit — type
/<name> [arguments]in chat (TUI or plain REPL) to run a skill directly; its body is inlined into the prompt and anything you type after the name is passed along as extra context. - Listing —
/skillsinside the session, ornode dist/cli.js skillsin the terminal (shows each skill's source and path).
A skill directory may hold supporting files next to SKILL.md (SQL scripts, reference tables); mention them in the body and let the file tools (--allow-system) read them.
Core (always available)
| Tool | Purpose |
|---|---|
list_tables |
Tables + views with row-count estimates (cached per session) — avoid on 30+ table schemas, use search_tables |
refresh_schema |
Clear the schema cache and re-read the live catalog (use after external DDL/migrations) |
search_tables |
Find tables by meaning (embedding-ranked, keyword fallback offline; limit, empty query lists alphabetically) + column names — then describe_table the hits |
list_relations |
Foreign-key edges (optional table filter) — join keys for correct JOINs |
get_schema_graph |
Full FK graph: all edges plus tables with no edges |
list_routines |
Stored procedures, functions, triggers (SQLite: triggers only; optional kind filter) |
describe_table |
Columns, types, nullability, primary keys, defaults (sensitive flags PII columns) |
sample_rows |
Peek at the first N rows of a table (PII masked; unmask: true reveals, audited) |
query |
Run read-only SQL (LIMIT auto-enforced; PII masked; unmask: true reveals, audited) |
execute |
Run INSERT/UPDATE/DELETE — needs --allow-writes plus interactive y/n confirmation (SQL + impact estimate); every call is audit-logged |
propose_index |
Analyze a CREATE INDEX proposal without executing it (validates + EXPLAINs the target query); read-only, works in every DDL mode |
apply_ddl |
Execute one CREATE INDEX after y/n confirmation — only with --allow-ddl=on + --allow-writes |
performance_report |
Dialect-aware diagnostics: broken/disabled/invalid indexes, missing indexes, unused/duplicate indexes, cache hit ratio, waits, top queries, blocking, bloat (deep: true adds fragmentation scans) |
explain_query |
Run the dialect's plan analyzer on one read-only statement (SQLite EXPLAIN QUERY PLAN, PostgreSQL EXPLAIN [ANALYZE], MySQL EXPLAIN, SQL Server SET STATISTICS PROFILE) |
System mode (--allow-system)
| Tool | Purpose |
|---|---|
db_stats |
Per-table sizes, row counts, index counts, total DB size |
system_info |
Server version, uptime, active connections, key settings |
export_data |
Dump any table to CSV/JSON under ./dbagent-out/ (streamed in chunks — full dump, flat memory) |
import_csv |
Load a CSV into a table, auto-creating it from headers (also needs --allow-writes; single transaction, multi-row batches) |
backup |
SQLite: VACUUM INTO · PostgreSQL: pg_dump · MySQL/MariaDB: mysqldump · SQL Server: BACKUP DATABASE; Oracle uses operator-managed Data Pump/RMAN |
discover_local_databases |
Probe local servers, scan for SQLite files, check client tools |
read_file / write_file / edit_file |
Read (paged, 512 KB cap), create (overwrite-protected), surgical-edit files |
list_dir / glob_files / grep_files |
Navigate and search the project (node_modules skipped) |
delete_file |
Delete a single file (directories refused) |
Shell mode (--allow-shell) — separate, stronger gate
| Tool | Purpose |
|---|---|
bash |
Run shell commands (psql, migration runners, git...) with a hard 30 s timeout (max 120 s), capped output, sandboxed cwd |
MCP tools are exposed as mcp_<server>_<tool> only when --allow-mcp is enabled.
Performance work is the agent's primary workflow. A typical session:
node dist/cli.js chat --db "postgres://user:pass@host:5432/prod" --provider openrouter- Ask "why is this slow?" — the agent starts with
performance_report, which runs a per-dialect diagnostic catalog (each section best-effort: a missing system view or privilege degrades to an error note, never a failed report). - For a suspect query it runs
explain_queryon the exact SQL — plans come back as rows the agent can interpret (full scans, missing indexes, estimate gaps, sort/temp spills). - Recommendations come with evidence and trade-offs: exact
CREATE INDEX/DROP INDEX/REINDEX/REBUILDSQL, write and maintenance cost, and the constraint checks that prevent bad drops (unique/FK-backing indexes are excluded from "unused" lists). Nothing is executed — DDL stays blocked.
| Dialect | Highlights in the report |
|---|---|
| SQL Server | dm_db_missing_index_*, disabled indexes, usage stats, duplicates, fragmentation (deep), wait stats, top queries by reads, blocking, buffer cache hit |
| PostgreSQL | invalid indexes (indisvalid), unused/duplicate indexes, seq-scan hotspots, dead tuples/autovacuum, cache hit, long queries, pg_stat_statements |
| MySQL | sys unused/redundant indexes, full-scan and slow statements, InnoDB buffer hit, temp-disk counters, table free space |
| SQLite | page/freelist space, index inventory, sqlite_stat1 (ANALYZE) stats, per-object sizes (deep) |
Safety is layered and opt-in. Out of the box the agent cannot modify anything.
| Layer | Default | Enabled by |
|---|---|---|
| Data writes (INSERT/UPDATE/DELETE) | blocked | --allow-writes |
| Write confirmation (SQL + impact) | interactive y/n per statement | --yes skips it (dangerous) |
| Audit log (every SQL statement) | on (~/.dbagent/audit.log) |
DBAGENT_AUDIT_FILE to relocate |
| PII masking (email/phone/…) | on in sample_rows/query |
unmask: true per call (audited) or --no-mask-pii / DBAGENT_MASK_PII=0 |
| CREATE INDEX (only executable DDL) | blocked | --allow-ddl=dry-run (analyze) or --allow-ddl=on (apply + confirm, needs --allow-writes) |
| DROP / ALTER / TRUNCATE / RENAME | always blocked | — |
| Multi-statement SQL | blocked (1 statement per call) | — |
| Read result size | hard LIMIT 100 rows | --max-rows |
| File access | outside workdir blocked | --allow-system + sandbox |
| Escaping the path sandbox | impossible | DBAGENT_ANY_PATH=1 (dangerous) |
| Shell execution | unavailable | --allow-shell |
When --allow-system is on, exports and backups always land inside the working directory (./dbagent-out/ by default), and the system prompt instructs the agent to take a backup before risky operations and to prefer dedicated tools over shell commands.
All settings live in one TOML file — copy dbagent.example.toml to dbagent.toml:
[llm]
provider = "openrouter"
model = "openai/gpt-4o-mini"
api_key = "${OPENROUTER_API_KEY}" # ${VAR} / ${VAR:-fallback} read from the environment
[database]
url = "sqlite://./app.db"
[safety]
allow_writes = false
allow_ddl = "off" # off | dry-run | onLookup order for the file: --config <path> → $DBAGENT_CONFIG → ./dbagent.toml → ~/.dbagent/config.toml. Unknown keys/sections and mistyped values fail fast with a did-you-mean hint. Every setting resolves as CLI flags > environment variables (.env still works) > dbagent.toml > defaults, so existing env-based setups are unaffected. dbagent.toml is gitignored; prefer ${VAR} interpolation for API keys instead of plain text.
| TOML key | Env equivalent | Purpose |
|---|---|---|
[llm] provider / model |
DBAGENT_PROVIDER / _MODEL |
active provider and model, overridable by --provider/--model |
[llm] api_key / base_url |
DBAGENT_API_KEY / _BASE_URL |
generic key/base URL for the active provider |
[llm] openrouter / [llm] gemini |
OPENROUTER_* / GEMINI_* |
provider-specific extras (attribution headers, api version) |
[database] url |
DBAGENT_DB_URL |
default database, overridable by --db |
[safety] allow_writes/system/shell |
DBAGENT_ALLOW_* |
permission tiers |
[safety] assume_yes |
DBAGENT_ASSUME_YES |
auto-confirm writes (--yes), default off |
[safety] allow_ddl |
DBAGENT_ALLOW_DDL |
off / dry-run / on (--allow-ddl), default off |
[safety] mask_pii |
DBAGENT_MASK_PII |
false disables PII masking (--no-mask-pii), default on |
[schema] embed_model |
DBAGENT_EMBED_MODEL |
embedding model for search_tables |
[schema] large_tables |
DBAGENT_LARGE_SCHEMA_TABLES |
search-first navigation threshold (default 30) |
[limits] max_rows / max_rounds / timeout_ms |
DBAGENT_MAX_* / _TIMEOUT_MS |
guardrail tuning |
[output] dir / audit_file |
DBAGENT_OUT_DIR / _AUDIT_FILE |
export/backup directory and audit log |
[output] allow_any_path |
DBAGENT_ANY_PATH |
escaping the path sandbox (dangerous) |
Provider key variables (OPENAI_API_KEY, OPENROUTER_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, OLLAMA_BASE_URL) still work directly — any one provider is enough.
The agent loop is provider- and database-agnostic — use it programmatically:
import { Agent, createDatabaseDriver, createLLMAdapter } from './src/index.js';
const db = createDatabaseDriver('sqlite://./demo.db', { maxRows: 100 });
const llm = createLLMAdapter({
id: 'openrouter',
model: 'anthropic/claude-sonnet-4',
apiKey: process.env.OPENROUTER_API_KEY!,
});
const agent = new Agent(llm, db, { id: 'openrouter', model: 'anthropic/claude-sonnet-4' });
// Simple
const { text } = await agent.runTurn('Top 5 customers by total spend?');
// Streaming with live events
const result = await agent.runTurnStream('How many orders per status?', {
onEvent: (ev) => {
if (ev.kind === 'text_delta') process.stdout.write(ev.text);
if (ev.kind === 'tool_done') console.log(`\n[tool] ${ev.name} (${ev.durationMs}ms)`);
},
});
await db.close();npm run dev # run the CLI from source (tsx)
npm run build # compile to dist/
npm run lint # eslint (0 errors; unused-var warnings allowed)
npm run format:check # prettier check (CI enforces this)
npm test # full suite (130+ checks):
# typecheck · agent-loop smoke · e2e over HTTP (mock
# OpenAI + OpenRouter) · SSE streaming e2e · TUI render
# · markdown render · system ops · skills · file/shell
# tools · SQL Server dialect · performance diagnostics
npm run demo:seed # regenerate demo.dbTest suites run without any real API keys — LLMs are scripted or served by a local mock.
src/
core/ agent loop, shared types, config, system prompt, skills
llm/ adapters (OpenAI-compatible/OpenRouter, Anthropic, Gemini) + SSE streaming
db/ drivers (SQLite, PostgreSQL, MySQL/MariaDB, SQL Server, Oracle), SQL guardrails, URL factory
tools/ tool registry: core DB tools, system tools, file tools, shell
session/ transcript persistence
tui/ Ink/React UI: app, components, markdown renderer, table viewer
system/ stats/export/import/backup ops, performance diagnostics, local discovery, path sandbox
scripts/ demo seeding + smoke/e2e test suites
- Foreign-key relationship discovery → schema graph in the prompt
- Embedding-based schema search for very large schemas (100+ tables)
- Web UI on the same core (REST API)
- MongoDB support
- Scheduled backups with retention
MIT