bfi-cli 0.1.72 → 0.1.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +71 -6
- package/dist/mcp-db/main.mjs +19 -14
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -8058,6 +8058,7 @@ var YmcApi = class {
|
|
|
8058
8058
|
if (Object.keys(body).length > 0) console.log(JSON.stringify(body, null, 2));
|
|
8059
8059
|
process.exit(0);
|
|
8060
8060
|
}
|
|
8061
|
+
if (!this.token) throw new Error("未配置 API token:请 `bfi auth login --token <token>` 或 export BFI_TOKEN");
|
|
8061
8062
|
const url = `${this.baseUrl}/${path.replace(/^\//, "")}`;
|
|
8062
8063
|
const payload = JSON.stringify(body);
|
|
8063
8064
|
const mod = url.startsWith("https://") ? https : http;
|
|
@@ -8159,27 +8160,58 @@ function emit(result, jsonMode, summarize) {
|
|
|
8159
8160
|
function raw0(text) {
|
|
8160
8161
|
return text || "{}";
|
|
8161
8162
|
}
|
|
8162
|
-
const ENV_DOC = "环境: test/daily/pre/core1
|
|
8163
|
+
const ENV_DOC = "环境: test/daily/pre/core1/core2/core3/core4/commldev";
|
|
8164
|
+
const VALID_ENVS = [
|
|
8165
|
+
"test",
|
|
8166
|
+
"daily",
|
|
8167
|
+
"pre",
|
|
8168
|
+
"core1",
|
|
8169
|
+
"core2",
|
|
8170
|
+
"core3",
|
|
8171
|
+
"core4",
|
|
8172
|
+
"commldev"
|
|
8173
|
+
];
|
|
8174
|
+
const TIME_RE = /^\d+(m|h|d|w)$/;
|
|
8175
|
+
function envError(env) {
|
|
8176
|
+
if (!env || !env.trim()) return `缺少环境参数:-e <${ENV_DOC}>`;
|
|
8177
|
+
if (!VALID_ENVS.includes(env.toLowerCase())) return `环境参数无效:收到 '${env}'\n合法值:${VALID_ENVS.join(" | ")}(若为新环境请升级 bfi CLI)\n例:bfi ymc slowsql -t 1d --app yonbip-scm-scmmp -e core3`;
|
|
8178
|
+
return null;
|
|
8179
|
+
}
|
|
8180
|
+
function timeError(t) {
|
|
8181
|
+
if (!t || !t.trim()) return `缺少时间参数:-t <简写>(如 1h/3d/10m/4w,表示过去 N 单位到现在)\n精确窗口:--start-time "yyyy-MM-dd HH:mm:ss" --end-time 同格式(北京时间)`;
|
|
8182
|
+
if (!TIME_RE.test(t)) return `时间简写无效:收到 '${t}'\n合法格式:<N><单位>,单位 m(分)/h(时)/d(天)/w(周),如 10m/2h/3d/4w\n精确窗口:--start-time "yyyy-MM-dd HH:mm:ss" --end-time 同格式(北京时间)`;
|
|
8183
|
+
return null;
|
|
8184
|
+
}
|
|
8163
8185
|
function requireEnv(args) {
|
|
8164
8186
|
const env = opt(args, "-e", "--env", "--env-name");
|
|
8165
|
-
|
|
8166
|
-
|
|
8187
|
+
const err = envError(env);
|
|
8188
|
+
if (err) {
|
|
8189
|
+
console.error(err);
|
|
8167
8190
|
process.exit(1);
|
|
8168
8191
|
}
|
|
8169
8192
|
return { env_name: env };
|
|
8170
8193
|
}
|
|
8171
8194
|
function timeOf(args, required = true) {
|
|
8172
8195
|
const t = opt(args, "-t", "--time");
|
|
8173
|
-
|
|
8174
|
-
|
|
8196
|
+
const err = timeError(t);
|
|
8197
|
+
if (err && required) {
|
|
8198
|
+
console.error(err);
|
|
8175
8199
|
process.exit(1);
|
|
8176
8200
|
}
|
|
8201
|
+
if (t && !err) validateTime(t);
|
|
8177
8202
|
return t ? { time: t } : {};
|
|
8178
8203
|
}
|
|
8179
8204
|
function timeOpts(args) {
|
|
8180
8205
|
const out = {};
|
|
8181
8206
|
const t = opt(args, "-t", "--time");
|
|
8182
|
-
if (t)
|
|
8207
|
+
if (t) {
|
|
8208
|
+
const err = timeError(t);
|
|
8209
|
+
if (err) {
|
|
8210
|
+
console.error(err);
|
|
8211
|
+
process.exit(1);
|
|
8212
|
+
}
|
|
8213
|
+
out.time = t;
|
|
8214
|
+
}
|
|
8183
8215
|
const st = opt(args, "--start-time");
|
|
8184
8216
|
if (st) out.start_time = st;
|
|
8185
8217
|
const et = opt(args, "--end-time");
|
|
@@ -8725,6 +8757,27 @@ async function runYmc(args, globalOpts) {
|
|
|
8725
8757
|
}
|
|
8726
8758
|
break;
|
|
8727
8759
|
}
|
|
8760
|
+
case "tool-download": {
|
|
8761
|
+
const uploadFileName = positionals(rest)[0];
|
|
8762
|
+
if (!uploadFileName) {
|
|
8763
|
+
console.error("用法:bfi ymc tool-download <uploadFileName> -e <env> [-o 输出文件]");
|
|
8764
|
+
console.error("例:bfi ymc tool-download monitor/17ff7937-xxx.html -e pre -o ~/flame.html");
|
|
8765
|
+
console.error("uploadFileName 来源:bfi ymc call tool/list(uploadPath 字段)或 bfi ymc dump-list(uploadPath)");
|
|
8766
|
+
process.exit(1);
|
|
8767
|
+
}
|
|
8768
|
+
const out = opt(rest, "-o", "--out") || uploadFileName.split("/").pop() || "download.bin";
|
|
8769
|
+
try {
|
|
8770
|
+
const n = await api.download("ymc/tool/download", {
|
|
8771
|
+
...requireEnv(rest),
|
|
8772
|
+
uploadFileName
|
|
8773
|
+
}, out);
|
|
8774
|
+
console.log(`✓ 已下载 ${n} 字节 → ${out}`);
|
|
8775
|
+
} catch (e) {
|
|
8776
|
+
console.error(`✗ ${e.message}`);
|
|
8777
|
+
process.exit(1);
|
|
8778
|
+
}
|
|
8779
|
+
break;
|
|
8780
|
+
}
|
|
8728
8781
|
case "call": {
|
|
8729
8782
|
const route = positionals(rest)[0];
|
|
8730
8783
|
const dataStr = opt(rest, "--data") || "{}";
|
|
@@ -8866,6 +8919,10 @@ const HELP = {
|
|
|
8866
8919
|
"bpr-download": `bfi ymc bpr-download <pk> -e <env> [-o <输出文件>]
|
|
8867
8920
|
BPR 报告 HTML 下载(文件流写盘)。pk 来源:bfi ymc call bpr/list --data '{"page":1,"pageSize":10}' -e <env>。
|
|
8868
8921
|
输出默认 bpr_<pk前16>.html。`,
|
|
8922
|
+
"tool-download": `bfi ymc tool-download <uploadFileName> -e <env> [-o <输出文件>]
|
|
8923
|
+
诊断文件内容下载(CPU 火焰图/堆 dump/BPR HTML 等文件流写盘)。
|
|
8924
|
+
uploadFileName 来源:bfi ymc call tool/list(uploadPath)或 bfi ymc dump-list(uploadPath)。
|
|
8925
|
+
输出默认取文件名部分(monitor/xx.html → xx.html)。`,
|
|
8869
8926
|
call: `bfi ymc call <route> -e <env> [--data '<json>'] [-t <time> | --start-time <s> --end-time <e>] [--timeout <秒>] [--json]
|
|
8870
8927
|
通用透传:覆盖全部注册表端点(warningCall/warningSql/exceptionHistory/bpr/list/alert/detail/tool/list/app/history/environment/uba/query ...)。
|
|
8871
8928
|
完整清单与参数声明:curl http://172.20.35.178:8901/openapi.json 或 /docs(Swagger UI)。
|
|
@@ -8887,6 +8944,8 @@ function printHelp() {
|
|
|
8887
8944
|
console.log(`
|
|
8888
8945
|
bfi ymc — 观测查询(v2 API:YMC/Kibana/SLS/trace)
|
|
8889
8946
|
|
|
8947
|
+
详细帮助/示例:bfi ymc help <命令>(首次使用必看——含参数默认值与易错点)
|
|
8948
|
+
|
|
8890
8949
|
命令:
|
|
8891
8950
|
status 登录会话状态(dev/prod)
|
|
8892
8951
|
trace <trace_id> -e <env> 三源合一(SLS+链路+ingress)
|
|
@@ -8902,6 +8961,12 @@ bfi ymc — 观测查询(v2 API:YMC/Kibana/SLS/trace)
|
|
|
8902
8961
|
url-stats </uri> -t <time> -e <env> [--app <service_name>]
|
|
8903
8962
|
ingress --trace-id <id> | --uri </path> [-t <time>] -e <env> [--exact] [--app <service_name>]
|
|
8904
8963
|
dump-list [-t <time>] -e <env> [--app] [--type profiler]
|
|
8964
|
+
tool-download <uploadPath> -e <env> [-o <文件>] 诊断文件内容下载
|
|
8965
|
+
log-url <...> SLS 直链(日志中心页面直达)
|
|
8966
|
+
events / sample-list 集群事件 / 采样数据列表
|
|
8967
|
+
callstat / sqlstat 慢调用/慢SQL 统计版聚合
|
|
8968
|
+
rca <...> 官方智能诊断 RCA(长耗时)
|
|
8969
|
+
bpr-download <pk> BPR 报告下载
|
|
8905
8970
|
cases <search|show|upload|index> 案例库(KEDB)
|
|
8906
8971
|
wiki search "<kw>" [--source] [--titles] 知识库统一检索(wiki/jira/文库)
|
|
8907
8972
|
wiki show <id> [--source] 知识条目全文
|
package/dist/mcp-db/main.mjs
CHANGED
|
@@ -164,6 +164,10 @@ var RestError = class extends Error {
|
|
|
164
164
|
this.errorKind = errorKind;
|
|
165
165
|
}
|
|
166
166
|
};
|
|
167
|
+
function requireParam(name, value) {
|
|
168
|
+
if (!value || !value.trim()) throw new RestError("invalid_params", `${name} 不能为空:请从 db_list_envs 返回结果中原样复制(如 env_code=core1_3)`);
|
|
169
|
+
return value;
|
|
170
|
+
}
|
|
167
171
|
async function post(path, payload, fetchImpl = fetch) {
|
|
168
172
|
const token = resolveToken();
|
|
169
173
|
if (!token) throw new RestError("unauthorized", "未找到 bfi token:请先执行 `bfi auth login --token <sk-token>`(token 获取地址 https://bfi.yyuap.com/user/profile),或设置 BFI_TOKEN 环境变量");
|
|
@@ -185,24 +189,24 @@ async function post(path, payload, fetchImpl = fetch) {
|
|
|
185
189
|
if (body.code === 0) return body.data;
|
|
186
190
|
throw new RestError(body.data?.error_kind || "upstream_error", body.msg || "bfi-api 返回错误");
|
|
187
191
|
}
|
|
188
|
-
function listEnvs(keyword, envType, fetchImpl) {
|
|
192
|
+
async function listEnvs(keyword, envType, fetchImpl) {
|
|
189
193
|
return post("/bficli/api/v2/db/envs", {
|
|
190
194
|
keyword,
|
|
191
195
|
env_type: envType,
|
|
192
196
|
enabled_only: true
|
|
193
197
|
}, fetchImpl);
|
|
194
198
|
}
|
|
195
|
-
function execSql(envCode, sql, maxRows, fetchImpl) {
|
|
199
|
+
async function execSql(envCode, sql, maxRows, fetchImpl) {
|
|
196
200
|
return post("/bficli/api/v2/db/exec", {
|
|
197
|
-
env_code: envCode,
|
|
198
|
-
sql,
|
|
201
|
+
env_code: requireParam("env_code", envCode),
|
|
202
|
+
sql: requireParam("sql", sql),
|
|
199
203
|
max_rows: maxRows
|
|
200
204
|
}, fetchImpl);
|
|
201
205
|
}
|
|
202
|
-
function explainSql(envCode, sql, fetchImpl) {
|
|
206
|
+
async function explainSql(envCode, sql, fetchImpl) {
|
|
203
207
|
return post("/bficli/api/v2/db/explain", {
|
|
204
|
-
env_code: envCode,
|
|
205
|
-
sql
|
|
208
|
+
env_code: requireParam("env_code", envCode),
|
|
209
|
+
sql: requireParam("sql", sql)
|
|
206
210
|
}, fetchImpl);
|
|
207
211
|
}
|
|
208
212
|
function textResult(payload, isError = false) {
|
|
@@ -234,7 +238,8 @@ function buildServer() {
|
|
|
234
238
|
}, { instructions: [
|
|
235
239
|
"数据库取证通道(只读):查询环境列表 → EXPLAIN 执行计划(优先)→ 必要时小行数 SELECT 采样。",
|
|
236
240
|
"DDL/DML 默认被环境权限位拒绝;db_direct 为用户自备连接的本地直连,词法级闸门仅放行 SELECT/WITH/EXPLAIN,结果无审计,写报告须标注\"本地直连取证\"。",
|
|
237
|
-
"执行前先用 db_list_envs 锁定 env_code(对应诊断的 appCode 先行铁律)。"
|
|
241
|
+
"执行前先用 db_list_envs 锁定 env_code(对应诊断的 appCode 先行铁律)。",
|
|
242
|
+
"降级链:环境清单匹配不到目标环境(env_not_found),或选中环境连接不可达(connection_failed)时,向用户说明并**提示附上数据库连接串**(mysql://user:pwd@host:3306/db),改走 db_direct 本地直连兜底。"
|
|
238
243
|
].join("\n") });
|
|
239
244
|
server.registerTool("db_list_envs", {
|
|
240
245
|
description: "获取可查询的数据库环境列表(env_code/env_name/db_type/datacenter/权限位 allow_ddl·dml·query·explain)。不含密码。执行任何 SQL 前必须先用本工具锁定 env_code。",
|
|
@@ -250,23 +255,23 @@ function buildServer() {
|
|
|
250
255
|
server.registerTool("db_execute", {
|
|
251
256
|
description: "在指定环境执行单条 SQL(只读取证)。仅 SELECT/WITH/SHOW/DESC/EXPLAIN;DML/DDL 需环境显式授权位否则 permission_denied。行数上限默认 200。非 mysql 库自动翻译(mysql 语法输入即可)。诊断场景优先 db_explain。",
|
|
252
257
|
inputSchema: {
|
|
253
|
-
env_code: z.string().describe("环境编码,来自 db_list_envs"),
|
|
254
|
-
sql: z.string().describe("单条 SQL(mysql 语法)"),
|
|
258
|
+
env_code: z.string().min(1, "env_code 不能为空:请从 db_list_envs 返回的 env_code 字段原样复制(如 core1_3)").describe("环境编码,来自 db_list_envs 返回的 env_code 字段(如 core1_3)原样传入"),
|
|
259
|
+
sql: z.string().min(1, "sql 不能为空").describe("单条 SQL(mysql 语法)"),
|
|
255
260
|
max_rows: z.number().int().min(1).max(2e3).optional().describe("行数上限,默认 200")
|
|
256
261
|
}
|
|
257
262
|
}, wrap((args) => execSql(args.env_code, args.sql, args.max_rows)));
|
|
258
263
|
server.registerTool("db_explain", {
|
|
259
264
|
description: "获取执行计划(慢 SQL 实证首选)。目标语句必须为 SELECT/WITH;gscm 按 db_type 组装 EXPLAIN(oracle 两步 EXPLAIN PLAN + DBMS_XPLAN)。结果为计划行,EXPLAIN 结果进报告须附原始计划行。",
|
|
260
265
|
inputSchema: {
|
|
261
|
-
env_code: z.string().describe("环境编码,来自 db_list_envs"),
|
|
262
|
-
sql: z.string().describe("目标 SELECT/WITH 语句(mysql 语法)")
|
|
266
|
+
env_code: z.string().min(1, "env_code 不能为空:请从 db_list_envs 返回的 env_code 字段原样复制(如 core1_3)").describe("环境编码,来自 db_list_envs 返回的 env_code 字段(如 core1_3)原样传入"),
|
|
267
|
+
sql: z.string().min(1, "sql 不能为空").describe("目标 SELECT/WITH 语句(mysql 语法)")
|
|
263
268
|
}
|
|
264
269
|
}, wrap((args) => explainSql(args.env_code, args.sql)));
|
|
265
270
|
server.registerTool("db_direct", {
|
|
266
271
|
description: "用户自备连接串的本地直连(不经服务端,无审计)。词法级闸门仅放行单条 SELECT/WITH/EXPLAIN(EXPLAIN ANALYZE 拒绝),执行前连接预检。仅当用户明确提供连接串且环境通道不可达时使用;报告引用本工具结果必须标注\"本地直连取证\"。",
|
|
267
272
|
inputSchema: {
|
|
268
|
-
dsn: z.string().describe("连接串 mysql://user:pwd@host:3306/db(密码仅内存持有)"),
|
|
269
|
-
sql: z.string().describe("单条 SELECT/WITH/EXPLAIN"),
|
|
273
|
+
dsn: z.string().min(1, "dsn 不能为空").describe("连接串 mysql://user:pwd@host:3306/db(密码仅内存持有)"),
|
|
274
|
+
sql: z.string().min(1, "sql 不能为空").describe("单条 SELECT/WITH/EXPLAIN"),
|
|
270
275
|
max_rows: z.number().int().min(1).max(2e3).optional().describe("行数上限,默认 200")
|
|
271
276
|
}
|
|
272
277
|
}, wrap((args) => directExecute(args.dsn, args.sql, args.max_rows)));
|