A headless database CLI for humans and agents.
miu-db is the Go database core behind the miudb binary. It keeps the
database layer focused on
connections, credentials, tunnels, adapters, query execution, schema inspection,
MCP, and machine-readable output.
One command, auto-detects OS and architecture.
macOS / Linux:
curl -fsSL https://db.miu.sh/install.sh | shWindows (PowerShell):
irm https://db.miu.sh/install.ps1 | iexBoth honor MIUDB_VERSION (pin a release) and MIUDB_INSTALL_DIR. Other options:
- Homebrew —
brew install vanducng/tap/miudb - Go —
go install github.com/vanducng/miu-db/cmd/miudb@latest
Verify:
miudb version --output json
miudb commands --output jsonUpgrade in place later (downloads the matching release, verifies its checksum, and replaces the binary):
miudb upgrade # to the latest release
miudb upgrade --check # report whether a newer version exists
miudb upgrade --version v0.2.4All commands take -o/--output: json (default, compact) or pretty.
Homebrew installs should upgrade with brew upgrade miudb.
The default native config lives under:
~/.config/miu/db/connections.json
Sensitive values are classified before persistence. New connections store
database and SSH passwords outside connections.json by default using the
miudb OS Keychain/keyring service.
miudb connections add \
--name local-app \
--db-type sqlite \
--path ./app.db \
--secret-store keyring \
--output jsonmiudb connections list --output json
miudb connections list --basic --output json # scannable: ref/name/group/db_type/host
miudb connections test local-app --output json
miudb query run -c local-app --sql 'select 1 as one' --output jsonConnections are addressed by group/name (e.g. team/prod-db) when names
repeat across groups; a bare name works when it is unique. The ref column
from --basic is exactly what you pass to --connection (short flag -c).
Use --session key=value (repeatable) to set temporary, per-call session context.
Keys are provider-specific and rejected when unsupported; they do not persist
across calls. For example, switch the Snowflake role for one query:
miudb query run -c snowflake-prod \
--session role=ANALYST_ROLE \
--sql 'select current_role()' --output jsonquery run executes a single statement. To run several statements in one call,
use query script (Snowflake and MySQL only). It returns data.results — an
array with one entry per statement (kind is rows or exec). Fail-fast by
default; pass --atomic to wrap the script in a transaction (DML-only
atomicity). Other datasources are rejected with a clear error.
miudb query script -c snowflake-prod \
--session role=SECURITYADMIN \
--sql "SHOW USERS LIKE 'alice'; SHOW ROLES LIKE 'analyst'" --output jsonSupported secret stores for new connections:
keyring: OS Keychain/keyring service namedmiudbby default.file:~/.config/miu/db/credentials.jsonwith mode0600.inline: keep the value in the connection file.none: discard the supplied secret and require another resolver later.
If credentials.json is absent, miudb also reads an existing
credentials-export.json in the same directory for migrated connections.
Future secret providers can plug into the same SecretRef model, including
1Password and Bitwarden.
To onboard a new machine or teammate, share a connections JSON file and import
it. Import merges by name: matching connections are overwritten and the existing
connections.json is backed up first to connections.json.bak-<timestamp>.
miudb connections import ./shared-connections.json --output json
miudb connections import ./shared-connections.json --dry-run --output jsonSecrets are imported as they appear in the file. A self-contained file with
inline passwords needs no extra setup on the target machine; treat it as a
secret (mode 0600, secure channel only). Use --dry-run to preview the
added/overwritten connections before writing.
Snowflake and BigQuery support token-based authentication with a one-time interactive login. All subsequent queries refresh and use the stored token silently.
Snowflake — add the connection once, then log in:
miudb connections add \
--name sf-prod \
--db-type snowflake \
--host <account>.snowflakecomputing.com \
--username <user> \
--option authenticator=oauth \
--option oauth_client_id=<client-id> \
--option oauth_authorization_url=https://<account>.snowflakecomputing.com/oauth/authorize \
--option oauth_token_request_url=https://<account>.snowflakecomputing.com/oauth/token-request \
--option oauth_scope=session:role:ANALYST \
--output json
miudb auth login sf-prod --output json
miudb auth status sf-prod --output json
miudb query run --connection sf-prod --sql 'SELECT CURRENT_ROLE()' --output jsonNo --password is required for OAuth connections.
BigQuery — authenticate once with gcloud, then add the connection:
gcloud auth application-default login
miudb connections add \
--name bq-analytics \
--db-type bigquery \
--option project=my-gcp-project \
--option dataset=analytics \
--output jsonSee the authentication docs for
the Snowflake CREATE SECURITY INTEGRATION snippet, all option keys, and
secret-handling guarantees.
Daily-driver adapters:
- SQLite
- PostgreSQL
- MySQL
- Snowflake (password, JWT, OAuth)
- BigQuery (ADC, service account)
SSH tunnel-backed connections are supported for TCP adapters.
Use miu-db as a local stdio MCP server for coding-agent hosts:
miudb mcp serve --transport stdioThe MCP server exposes redacted connection inventory, schema inspection,
bounded read-only query execution, pagination, and miudb:// resources.
Claude Code — one command:
claude mcp add --transport stdio miudb -- miudb mcp serve --transport stdioCursor — add to ~/.cursor/mcp.json (global) or .cursor/mcp.json (project):
{
"mcpServers": {
"miudb": { "command": "miudb", "args": ["mcp", "serve", "--transport", "stdio"] }
}
}Codex — add to ~/.codex/config.toml:
[mcp_servers.miudb]
command = "miudb"
args = ["mcp", "serve", "--transport", "stdio"]If the host can't find miudb, use the full path from which miudb. See
the MCP docs for VS Code, per-connection
scoping, and tool reference.
Every query, exec, and schema operation is recorded as a JSONL event under:
~/.config/miu/db/activity/{date}/{session_id}.jsonl
Each line captures SQL text, sql_shape (literals replaced with ?),
connection name, group, db_type, latency, rows_returned count, and error
info. Result rows are never stored — only counts and metadata.
# all events from the last day
miudb activity --since 1d --output json
# all events for a specific connection
miudb activity --connection local-app --since 7d --output json
# only failures
miudb activity --failed --since 24h --output json
# reconstruct a full session trace (spans multiple date dirs if needed)
miudb activity --session <session_id> --output jsonmiudb activity prune --older-than 30d # delete dirs older than 30 days
miudb activity prune --older-than 30d --dry-run # preview without deletingDisable for a single invocation:
miudb query run --connection local-app --sql 'select 1' --no-activity-logDisable globally via environment variable:
MIUDB_ACTIVITY_LOG=off miudb query run --connection local-app --sql 'select 1'Disable per-connection by setting "log_sql": false in connections.json:
{ "name": "local-app", "db_type": "sqlite", "log_sql": false, ... }With log_sql: false, sql_shape (normalized, no literals) is still
recorded; only the raw SQL text is omitted.
query run --session key=value sets a temporary, per-call connection context
(e.g. Snowflake role). This is unrelated to the activity log session_id, which
is a machine-generated identifier minted once per CLI invocation or MCP server
start. The flags share a name for historical reasons; renaming one is a
separate, deferred decision.
For ad-hoc analytics across all sessions, load the raw JSONL directly in DuckDB:
select session_id, connection, op, sql_shape, latency_ms, rows_returned, ts
from read_json_auto('~/.config/miu/db/activity/**/*.jsonl')
order by ts desc
limit 100;go test ./...
go build -buildvcs=false -o ./.miu-db/miudb ./cmd/miudb
./.miu-db/miudb commands --output jsonSee the architecture docs and the agent CLI docs.
