📖 项目主页:https://cassianflorin.github.io/database-cli/ — 安全模型概览、护栏演示与快速上手
这是一个 CLI-first 的 Codex Plugin,内置 database-cli Skill。它面向数据库问题排查:查看表结构、搜索 schema/table/column/index/procedure、执行有范围的 SQL、跨环境核对记录,以及在需要修数时产出或执行用户明确允许的变更 SQL。
底层使用 sq 作为数据库 CLI 后端,并通过本项目的 wrapper 做安全限制。scripts/db-query 是唯一真实执行入口;scripts/database-mcp 是可选的薄适配层,只把同一套 CLI 能力暴露给支持 MCP 的客户端。
database-cli 不是 DBHub 替代品,也不是 MCP 平台。它的核心是一个可安装、可审计、可由 Agent 和人类共同使用的数据库 CLI Skill。
项目边界:
- CLI 是产品本体:安装、配置、临时连接、schema 搜索、SQL 安全校验和修数 SQL 工作流都先落在
scripts/db-query和 Skill 指令中。 - MCP 是适配层:
scripts/database-mcp不能另起查询逻辑,只能委托 CLI;安全规则必须由 CLI 层兜底。 - Skill 是使用规范:Agent 应按
SKILL.md先确认环境、读取真实 schema、默认执行有界只读查询;需要改数据时,必须先获得用户明确允许,再使用显式写入开关。 - 不追求 DBHub 平台能力:不做 Workbench、权限平台、多租户服务端或独立数据库管理产品。
- 通过
scripts/db-query执行 SQL,这是唯一真实执行入口;默认只读,只有显式--allow-write才允许 DML。 - 通过
scripts/db-query --setup-status输出 Agent 友好的安装状态、缺失项和下一步动作;这个命令不连接数据库。 - 通过
SKILL.md固化 Agent 查询流程、安全边界和人工修数 SQL 输出规范。 - 通过
scripts/database-mcp可选暴露 stdio MCP 工具;它只委托scripts/db-query,不拥有独立查询逻辑。 - 可通过本地
connections.local.json保存连接配置,也可以由用户在单次调用里提供--url、--host、用户名和密码环境变量等连接信息。 - 按环境/账号权限管理连接,例如
qa01、prod,让 Agent 和开发者使用同一套入口。 - 连接配置支持
display_name、environment、project、description、aliases等元信息,便于 Agent 选择正确连接。 - 默认按数据库实例/服务端连接,不把配置限制到某个 database/schema。
- 支持只配置域名,不配置端口;适用于域名或反向代理已处理端口的场景。
- 支持对象搜索:schema、table、column、index、procedure/function metadata,并支持
names、summary、full三档详情。 - 支持配置级
max_rows上限;readonly=false会被拒绝,不会用配置隐式打开写权限。 - 默认拦截写入;用户明确允许后可用
--allow-write执行INSERT、UPDATE、DELETE、REPLACE。DDL、权限、事务、过程、锁、导出和副作用 SQL 仍会被拦截。 - 写权限由配置声明,不由命令行声明:环境必须写明
"writable": true才接受--allow-write,临时连接(--url/--host)则需要额外的--writable,避免把受保护的库改写成临时连接绕过配置。 - 已批准的
UPDATE/DELETE在执行前会先 COUNT 实际影响行数,超过max_write_rows(默认 1000)直接拒绝,WHERE 1=1这类看似有 WHERE 实则全表的语句不会放行。 - 避免把密码写入命令行 DSN;本地密钥文件不提交到仓库。
.
├── .codex-plugin/
│ └── plugin.json
├── INSTALL.md
├── README.md
├── skills/
│ └── database-cli/
│ ├── SKILL.md
│ ├── agents/openai.yaml
│ ├── references/
│ │ ├── config.example.json
│ │ └── config.md
│ └── scripts/
│ ├── database-mcp
│ ├── db-query
│ ├── install
│ └── init-config
└── scripts/
├── database-mcp
├── db-query
├── install
└── init-config
安装或复制这个 Plugin 后,在插件根目录运行安装入口:
scripts/install这个命令会:
- 检查本机是否已安装
sq。 - 如果没有
sq,询问是否通过 Homebrew 安装。 - 向用户收集数据库连接信息。用户可以给完整数据库链接,也可以分开给 driver、host、用户名、密码或密码环境变量。
- 生成本地
connections.local.json。
标准 Codex Plugin/Skill 安装机制不会自动执行任意 post-install hook。因此,scripts/install 是这个插件的安装后必跑配置入口。
如果已经知道连接信息,优先用一条命令完成依赖检查和配置写入:
scripts/install \
--env qa01 \
--url "mysql://mysql-qa01.example.internal" \
--display-name "QNVIP QA01" \
--environment qa01 \
--project qnvip \
--description "QA01 shared readonly connection; search all visible schemas unless narrowed." \
--alias qa-01 \
--username readonly_user \
--password-env QA01_DB_PASSWORD \
--non-interactive这条命令会把连接参数透传给 scripts/init-config。如需写到指定配置文件,可加 --config /path/to/connections.local.json;如需覆盖已有环境,可加 --force。
安装后优先让 Agent 运行状态检查,而不是直接尝试连接数据库:
scripts/db-query --setup-status输出是 JSON,包含 ready、sq_available、config、environments、connections、problems 和 next_actions。Agent 应先按 next_actions 处理缺失项;只有 ready=true 且用户确认目标环境后,才继续做真实只读查询。
如果你已经知道常用环境名和连接信息,不需要先理解 sq 的 source 管理方式,直接用 scripts/install 创建本地环境即可:
scripts/install \
--env qa01 \
--url "mysql://mysql-qa01.example.internal" \
--display-name "QNVIP QA01" \
--environment qa01 \
--project qnvip \
--description "Shared QA readonly connection; search all visible schemas unless narrowed." \
--alias qa-01 \
--username readonly_user \
--password-env QA01_DB_PASSWORD \
--non-interactive
scripts/db-query --setup-status
scripts/db-query --list-envs
scripts/db-query --env qa01 --sql "SELECT 1"建议保留你原来习惯的环境名,并把连接理解为“这个环境下账号可访问的一组库”,而不是某一个 database。--setup-status 和 --list-envs 会返回连接元信息,帮助 Agent 判断该用哪个连接。真正查询数据时,在 SQL 里写全限定表名:
scripts/db-query --env qa01 --sql "SELECT id FROM qnvip_center_commerce.cc_order WHERE order_no = 'YP...'"如果只知道表名、字段名或索引名的一部分,可以直接查 metadata,不必先手写 information_schema SQL。默认不要要求用户先指定 database;不传 --schema 时,会在当前连接账号权限可见的 schema/database 范围内搜索:
scripts/db-query --env qa01 --search-objects "%cc_order%" --object-type table
scripts/db-query --env qa01 --search-objects "%order_no%" --object-type column --table cc_order
scripts/db-query --env qa01 --search-objects "%idx_order%" --object-type index --table cc_order
scripts/db-query --env qa01 --search-objects "%sync_order%" --object-type procedure
scripts/db-query --env qa01 --search-objects "%calc%" --object-type function --detail-level full当结果过多或用户已明确范围时,再用 --schema 收窄:
scripts/db-query --env qa01 --schema qnvip_center_commerce --search-objects "%order_no%" --object-type column--detail-level 支持:
names:只返回对象定位字段。summary:返回常用排查字段,默认值。full:返回更完整的 metadata,例如 MySQL routine definition、table comment、index detail 等。
当前对象搜索支持 MySQL/MariaDB/Postgres 直连配置。其他数据库或高级 sq source 配置仍可使用:
scripts/db-query --env qa01 --inspect
scripts/db-query --env qa01 --inspect cc_orderscripts/database-mcp 是一个无额外 Python 依赖的 stdio MCP adapter。它不是独立产品入口,实际查询、校验、限流和连接配置仍全部复用 scripts/db-query。它提供:
setup_status:返回安装状态、缺失项和下一步动作;不连接数据库。list_envs:列出当前配置的环境。add_connection:运行中的 Agent 新增或更新连接配置;后续工具调用会立即读取新配置,不需要重启 Agent。query_readonly:执行只读 SQL。execute_sql:执行 SQL,默认只读;用户明确允许后传allow_write=true才允许 DML。inspect:查看 source 或表结构。search_objects:搜索 schema/table/column/index/procedure/function metadata。check_sql:只校验 SQL 安全性,不执行。
每次 tools/call 都保留文本 content,并额外返回 structuredContent,包含 exit_code、stdout、stderr,以及 stdout 可解析为 JSON 时的 json 字段。
如果 connections.local.json 配置了 top-level tools,MCP tools/list 会额外暴露这些参数化工具。custom tool 是 MCP 适配层的辅助能力,不是项目主线。它的 SQL 使用 :param_name 占位,调用时会先把参数渲染为 SQL literal,再交给 scripts/db-query 做只读校验和 max_rows 限制。
{
"environments": {
"qa01": {
"driver": "mysql",
"host": "mysql-qa01.example.internal",
"username": "readonly_user",
"password_env": "QA01_DB_PASSWORD",
"max_rows": 100
}
},
"tools": {
"find_order": {
"description": "Find one order by order number.",
"env": "qa01",
"sql": "SELECT id, order_no, status FROM cc_order WHERE order_no = :order_no",
"parameters": {
"order_no": {
"type": "string",
"description": "Order number."
}
}
}
}
}MCP 客户端如需结构化工具入口,可以把 command 指到插件根目录下的入口:
/path/to/database-cli/scripts/database-mcp也可以在安装到任意目录后使用对应的绝对路径。连接配置仍由 connections.local.json 或 DATABASE_CLI_CONFIG 控制。
用户可以直接让 Agent 代为安装和初始化。推荐把下面这段发给 Agent:
请使用 GitHub CLI 安装 database-cli:
1. 如果本地没有仓库,运行 gh repo clone CassianFlorin/database-cli "$HOME/src/database-cli"。
2. 进入 "$HOME/src/database-cli"。
3. 把 skills/database-cli 安装到 ~/.codex/skills/database-cli(可用符号链接)。
4. 运行 scripts/install,帮我完成 database-cli 的安装后配置;如果我已给出连接信息,直接把 --env/--url/--username/--password-env 等参数传给 scripts/install。
5. 运行 scripts/db-query --setup-status,并根据 JSON 里的 next_actions 继续处理。
如果缺少 sq,请先询问我是否允许用 Homebrew 安装。
如果缺少数据库连接信息,请向我询问连接链接或 host、用户名、密码或密码环境变量,不要猜测地址、用户名、密码或访问范围。
如果已经知道连接信息,也可以让 Agent 走非交互式配置:
请为 database-cli 创建 qa01 环境配置:
url=mysql://mysql-qa01.example.internal
display_name=QNVIP QA01
environment=qa01
project=qnvip
description=QA01 共享只读连接,默认搜索账号可见库
alias=qa-01
username=readonly_user
password 使用环境变量 QA01_DB_PASSWORD
然后运行 scripts/db-query --setup-status 验证配置已写入;ready=true 后再运行 scripts/db-query --list-envs。
Agent 实际会执行类似命令:
mkdir -p "$HOME/src"
gh repo clone CassianFlorin/database-cli "$HOME/src/database-cli"
cd "$HOME/src/database-cli"
ln -sfn "$PWD/skills/database-cli" "$HOME/.codex/skills/database-cli"
scripts/install
scripts/db-query --setup-status或非交互式安装并创建配置:
scripts/install \
--env qa01 \
--url "mysql://mysql-qa01.example.internal" \
--display-name "QNVIP QA01" \
--environment qa01 \
--project qnvip \
--description "QA01 shared readonly connection; search all visible schemas unless narrowed." \
--alias qa-01 \
--username readonly_user \
--password-env QA01_DB_PASSWORD \
--non-interactive也可以更新已有环境:
scripts/install \
--env qa01 \
--url "mysql://mysql-new.example.internal" \
--display-name "QNVIP QA01" \
--environment qa01 \
--project qnvip \
--username readonly_user \
--password-env QA01_DB_PASSWORD \
--non-interactive \
--force如果 Agent 已经通过 MCP adapter 运行,不需要重启 Agent 来新增连接。让 Agent 调用 add_connection 工具即可,例如:
{
"config": "/path/to/connections.local.json",
"env": "qa02",
"url": "mysql://mysql-qa02.example.internal:3306/qnvip_center_order",
"username": "readonly_user",
"password_env": "QA02_DB_PASSWORD",
"display_name": "QNVIP QA02",
"aliases": ["qa-02"],
"max_rows": 100
}调用成功后,Agent 可以先调用 setup_status 确认配置已可见,再继续调用 list_envs、search_objects、query_readonly 或 execute_sql 使用 qa02。MCP adapter 每次工具调用都会读取配置文件,所以不依赖进程重启。
边界如下:
- Agent 可以运行
scripts/install、scripts/init-config、scripts/db-query。 - Agent 可以先运行
scripts/db-query --setup-status或 MCPsetup_status,根据结构化next_actions决定下一步。 - Agent 可以不创建本地配置,直接把用户提供的
--url/--host、--username、--password-env传给scripts/db-query或 MCPexecute_sql。 - Agent 默认只能执行只读 SQL;只有用户明确允许修改时,才可以传
--allow-write或 MCPallow_write=true执行 DML。 - Agent 可以通过 MCP
add_connection工具在运行中创建或更新连接配置。 - Agent 可以根据用户提供的信息创建或更新
connections.local.json。 - Agent 可以帮用户检查
sq是否安装,并在用户允许时通过 Homebrew 安装。 - Agent 不会也不应该猜测数据库地址、用户名、密码或访问范围;缺失时必须向用户询问。
- 敏感密码优先由用户配置到环境变量,然后在配置中使用
password_env。 - 如果用户直接提供明文密码,Agent 只能写入本地且被 git 忽略的
connections.local.json,不应提交或展示。 - custom tools 只适合固化只读查询模板;不要把修数 SQL 写进配置。
插件入口位于:
.codex-plugin/plugin.json
它声明:
- 插件名:
database-cli - Skill 目录:
./skills/ - UI 名称:
Database CLI - 能力:
Read、Write、Interactive
这与 Superpowers 的插件化思路一致:插件安装负责把 Skill 暴露给 Codex,安装后的敏感连接配置由 scripts/install 向用户收集。
真实查询前需要安装 sq:
brew install sq检查是否可用:
command -v sq如果只想创建或更新数据库配置,可以直接运行:
scripts/init-config它会询问:
- 环境名,例如
qa01、prod - 数据库类型,例如
mysql - host 或域名
- 可选端口
- 可选默认 database/catalog
- 用户名
- 密码或密码环境变量
默认生成的配置路径是:
~/.config/database-cli/connections.json
目录权限 0700,文件权限 0600。放在仓库外是为了避免明文密码留在工作区里——.gitignore 只是最后一道防线,一次 git add -f 或上游调整忽略规则就可能把凭据带进版本库。
如果 skills/database-cli/connections.local.json 已经存在,则继续写入该文件并在 stderr 提示迁移。因为配置查找在第一个存在的文件处停止,且库内路径优先级高于 ~/.config,此时改写新位置不会生效。迁移时要移动而不是复制:
mkdir -p ~/.config/database-cli && chmod 700 ~/.config/database-cli
mv skills/database-cli/connections.local.json ~/.config/database-cli/connections.json配置文件专用的非交互式示例:
scripts/init-config \
--env qa01 \
--driver mysql \
--host mysql-qa01.example.internal \
--username readonly_user \
--password-env QA01_DB_PASSWORD \
--non-interactive如果必须显式指定端口:
scripts/init-config \
--env qa01 \
--driver mysql \
--host mysql.example.internal \
--port 3306 \
--username readonly_user \
--password-env QA01_DB_PASSWORD \
--non-interactive域名或代理已经处理端口时:
{
"environments": {
"qa01": {
"display_name": "QNVIP QA01",
"environment": "qa01",
"project": "qnvip",
"description": "QA01 shared readonly connection; search all visible schemas unless narrowed.",
"aliases": ["qa-01", "test"],
"driver": "mysql",
"host": "mysql-qa01.example.internal",
"username": "readonly_user",
"password_env": "QA01_DB_PASSWORD",
"max_rows": 200,
"limit_style": "limit"
}
}
}显式端口:
{
"environments": {
"qa01": {
"driver": "mysql",
"host": "mysql.example.internal",
"port": 3306,
"username": "readonly_user",
"password_env": "QA01_DB_PASSWORD",
"limit_style": "limit"
}
}
}database 和 schema 都是可选默认值,不是访问范围限制。用户要查哪个库,优先在 SQL 里写全限定名:
SELECT id, order_no
FROM qnvip_center_commerce.cc_order
WHERE order_no = 'YP...'对于支持三段式名称的数据库:
SELECT column_name
FROM dbname.schema_name.table_name
WHERE id = 1列出已配置环境:
scripts/db-query --list-envs执行只读查询:
scripts/db-query --env qa01 --sql "SELECT id FROM qnvip_center_commerce.cc_order WHERE order_no = 'YP...'"不落本地配置,直接使用用户提供的临时连接信息:
scripts/db-query \
--url "mysql://mysql-qa01.example.internal:3306/qnvip_center_commerce" \
--username readonly_user \
--password-env QA01_DB_PASSWORD \
--sql "SELECT id, order_no FROM cc_order WHERE order_no = 'YP...'"预览将要执行的 sq 命令,不真正查询数据库:
scripts/db-query --env qa01 --sql "SELECT 1" --print-command查看数据源元信息:
scripts/db-query --env qa01 --inspect查看表元信息:
scripts/db-query --env qa01 --inspect qnvip_center_commerce.cc_order使用指定配置文件:
DATABASE_CLI_CONFIG=/path/to/connections.json scripts/db-query --list-envs只校验 SQL,不执行:
scripts/db-query --check-sql "SELECT * FROM qnvip_center_commerce.cc_order WHERE order_no = 'YP...'"用户明确允许修改后,才可以显式开启 DML。前提是该环境已在配置中声明
"writable": true,否则 --allow-write 会被拒绝:
scripts/db-query \
--env qa01 \
--allow-write \
--sql "UPDATE qnvip_center_commerce.cc_order SET status = 1 WHERE id = 10"临时连接(--url/--host)没有配置项可承载这个声明,需要额外传 --writable:
scripts/db-query \
--url "mysql://mysql-qa01.example.internal" --username repair_user \
--writable --allow-write \
--sql "UPDATE cc_order SET status = 1 WHERE id = 10"默认允许的起始关键字:
SELECTSHOWDESCDESCRIBEEXPLAIN- 只读的
WITH ... SELECT
环境声明 "writable": true、用户明确允许并传入 --allow-write 后,额外允许:
INSERTUPDATEDELETEREPLACE
会被拦截的示例:
- 未传
--allow-write的INSERT、UPDATE、DELETE、REPLACE - 环境未声明
"writable": true时的一切 DML,即使传了--allow-write - 实际影响行数超过
max_write_rows的UPDATE/DELETE - 对 DML 的
EXPLAIN ANALYZE(MySQL 8.0.18+ 会真的执行) MERGECREATE、ALTER、DROP、TRUNCATEGRANT、REVOKEBEGIN、COMMIT、ROLLBACKCALL、EXEC、EXECUTELOCK、UNLOCKSELECT ... FOR UPDATE- 具有副作用或风险的函数,例如
nextval、setval、get_lock、sleep、pg_sleep、benchmark、dblink_exec
SELECT 和 WITH 查询会自动补 LIMIT 200,除非 SQL 已经包含 limit,或显式使用 --no-auto-limit。DML 不会自动补 limit;Agent 必须使用精确业务键和可审计的 WHERE 条件。
配置了 max_rows 时,它是硬上限;用户传入更大的 --limit 或 SQL 里已有更大的 LIMIT,都会被压到 max_rows。readonly=false 不会启用写 SQL,database-cli 会直接拒绝该配置;readonly=true 也不授予任何权限,它只是默认姿态。写 SQL 需要两个条件同时成立:环境在配置里声明 "writable": true(临时连接则传 --writable),以及单次命令显式传 --allow-write。前者刻意放在配置文件里,使拼命令的一方无法自行主张写权限。
推荐使用环境变量保存密码:
export QA01_DB_PASSWORD='...'配置里写:
{
"password_env": "QA01_DB_PASSWORD"
}如果直接把 password 写进配置文件,必须只保存在本地,并优先放在仓库外的 ~/.config/database-cli/connections.json。Wrapper 会通过 sq add --password 的 stdin 传递密码,不会把密码放进命令行 DSN。
直连配置支持:
mysqlmariadbpostgrespostgresqlsqlite3duckdbsqlserverclickhouse
其他 sq source 类型可以使用高级 handle 配置:
{
"sq_config": "/path/to/sq.yml",
"environments": {
"qa01": {
"source": "@qnvip_qa01_commerce"
}
}
}默认不要通过这个 Skill 执行写 SQL。需要修数时,优先输出给人工执行的 SQL,并包含:
- 目标环境
- 执行前
SELECT校验 - 变更 SQL
- 执行后
SELECT验证 - 回滚或恢复方案
如果用户明确要求并允许 Agent 直接执行修数 SQL,必须满足:
- 目标环境已在配置声明
"writable": true(临时连接另需--writable) - 使用
--allow-write或 MCPallow_write=true - 只执行单条
INSERT、UPDATE、DELETE或REPLACE - 先给出执行前
SELECT校验 UPDATE/DELETE必须有WHERE,且 SQL 必须有精确业务键或主键条件- 执行后再跑
SELECT验证
发版只发生在 release 与 hotfix 分支上,推 main 不会发版。
git switch -c release # 或 release/1.2.0、hotfix、hotfix/1.1.2
git push -u origin release推送后自动执行:跑测试 → 按 Conventional Commits 计算版本 → 把版本号写入
plugin.json、SERVER_VERSION 与主页徽章并提交 → 在该提交上打 tag → 发布
GitHub Release → 把版本号同步回 main。
几点需要知道:
- 只有
feat、fix和BREAKING CHANGE会产生新版本(default_bump: false)。 分支上若只有docs、chore、ci、test提交,本次运行不发版,日志会写明原因。 - tag 指向的是已经写入新版本号的那个提交,不是它的父提交。
- 同步回
main时不会降级版本号:hotfix常从旧 tag 切出,此时若main已声明 更高版本,这一步会跳过。 - 只同步版本号,不做分支合并。
hotfix分支上的代码修复需要人工合回main。 - 发版会在
main上产生一个回写提交,本地下次推送前先git pull。
基础脚本校验:
python3 -m py_compile \
skills/database-cli/scripts/_common.py \
skills/database-cli/scripts/db-query \
skills/database-cli/scripts/init-config \
skills/database-cli/scripts/install \
skills/database-cli/scripts/database-mcp
python3 -m unittest discover -s teststests/test_integration.py 会用真实的 sq 与临时 SQLite 文件跑端到端流程;本地没有 sq 时这部分会自动跳过,其余测试照常运行。CI 会安装 sq 并设置 DATABASE_CLI_REQUIRE_SQ=1,使安装失败导致构建失败,而不是静默跳过。
当环境里有 PyYAML 时,执行 Skill 校验:
python3 /path/to/skill-creator/scripts/quick_validate.py /path/to/database-cli