bfi-cli 0.1.73 → 0.1.75

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.
Files changed (2) hide show
  1. package/dist/cli.mjs +371 -10
  2. package/package.json +4 -3
package/dist/cli.mjs CHANGED
@@ -6,6 +6,7 @@ import { require_picocolors } from "./_chunks/libs/picocolors.mjs";
6
6
  import { addSkillToLocalLock, addSkillToLock, computeSkillFolderHash, discoverSkills, dismissPrompt, filterSkills, getAllLockedSkills, getGitHubToken, getLastSelectedAgents, getSkillDisplayName, getSkillFromLock, isPromptDismissed, parseFrontmatter, parseSkillMd, readLocalLock, readSkillLock, removeSkillFromLock, sanitizeMetadata, saveSelectedAgents, stripTerminalEscapes } from "./_chunks/skills.mjs";
7
7
  import { esm_default } from "./_chunks/libs/simple-git.mjs";
8
8
  import { EVE_SUBAGENTS_DIR, agents, detectInstalledAgents, getEveSubagents, getNonUniversalAgents, getUniversalAgents, getVisibleUniversalAgents, isUniversalAgent } from "./_chunks/agents.mjs";
9
+ import { createRequire } from "node:module";
9
10
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
10
11
  import { basename, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "path";
11
12
  import { fileURLToPath } from "url";
@@ -8058,6 +8059,7 @@ var YmcApi = class {
8058
8059
  if (Object.keys(body).length > 0) console.log(JSON.stringify(body, null, 2));
8059
8060
  process.exit(0);
8060
8061
  }
8062
+ if (!this.token) throw new Error("未配置 API token:请 `bfi auth login --token <token>` 或 export BFI_TOKEN");
8061
8063
  const url = `${this.baseUrl}/${path.replace(/^\//, "")}`;
8062
8064
  const payload = JSON.stringify(body);
8063
8065
  const mod = url.startsWith("https://") ? https : http;
@@ -8159,27 +8161,58 @@ function emit(result, jsonMode, summarize) {
8159
8161
  function raw0(text) {
8160
8162
  return text || "{}";
8161
8163
  }
8162
- const ENV_DOC = "环境: test/daily/pre/core1-4/commldev";
8164
+ const ENV_DOC = "环境: test/daily/pre/core1/core2/core3/core4/commldev";
8165
+ const VALID_ENVS = [
8166
+ "test",
8167
+ "daily",
8168
+ "pre",
8169
+ "core1",
8170
+ "core2",
8171
+ "core3",
8172
+ "core4",
8173
+ "commldev"
8174
+ ];
8175
+ const TIME_RE = /^\d+(m|h|d|w)$/;
8176
+ function envError(env) {
8177
+ if (!env || !env.trim()) return `缺少环境参数:-e <${ENV_DOC}>`;
8178
+ 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`;
8179
+ return null;
8180
+ }
8181
+ function timeError(t) {
8182
+ if (!t || !t.trim()) return `缺少时间参数:-t <简写>(如 1h/3d/10m/4w,表示过去 N 单位到现在)\n精确窗口:--start-time "yyyy-MM-dd HH:mm:ss" --end-time 同格式(北京时间)`;
8183
+ 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 同格式(北京时间)`;
8184
+ return null;
8185
+ }
8163
8186
  function requireEnv(args) {
8164
8187
  const env = opt(args, "-e", "--env", "--env-name");
8165
- if (!env) {
8166
- console.error(`缺少环境参数:-e <${ENV_DOC}>`);
8188
+ const err = envError(env);
8189
+ if (err) {
8190
+ console.error(err);
8167
8191
  process.exit(1);
8168
8192
  }
8169
8193
  return { env_name: env };
8170
8194
  }
8171
8195
  function timeOf(args, required = true) {
8172
8196
  const t = opt(args, "-t", "--time");
8173
- if (!t && required) {
8174
- console.error("缺少时间参数:-t <简写>(如 1h/3d/10m/4w,表示过去 N 单位到现在)");
8197
+ const err = timeError(t);
8198
+ if (err && required) {
8199
+ console.error(err);
8175
8200
  process.exit(1);
8176
8201
  }
8202
+ if (t && !err) validateTime(t);
8177
8203
  return t ? { time: t } : {};
8178
8204
  }
8179
8205
  function timeOpts(args) {
8180
8206
  const out = {};
8181
8207
  const t = opt(args, "-t", "--time");
8182
- if (t) out.time = t;
8208
+ if (t) {
8209
+ const err = timeError(t);
8210
+ if (err) {
8211
+ console.error(err);
8212
+ process.exit(1);
8213
+ }
8214
+ out.time = t;
8215
+ }
8183
8216
  const st = opt(args, "--start-time");
8184
8217
  if (st) out.start_time = st;
8185
8218
  const et = opt(args, "--end-time");
@@ -8207,7 +8240,7 @@ async function runYmc(args, globalOpts) {
8207
8240
  const token = globalOpts.token;
8208
8241
  const api = new YmcApi(token ?? null, void 0, dryRun);
8209
8242
  if (!sub || sub === "--help" || sub === "-h") {
8210
- printHelp();
8243
+ printHelp$1();
8211
8244
  return;
8212
8245
  }
8213
8246
  if (sub === "help") {
@@ -8725,6 +8758,27 @@ async function runYmc(args, globalOpts) {
8725
8758
  }
8726
8759
  break;
8727
8760
  }
8761
+ case "tool-download": {
8762
+ const uploadFileName = positionals(rest)[0];
8763
+ if (!uploadFileName) {
8764
+ console.error("用法:bfi ymc tool-download <uploadFileName> -e <env> [-o 输出文件]");
8765
+ console.error("例:bfi ymc tool-download monitor/17ff7937-xxx.html -e pre -o ~/flame.html");
8766
+ console.error("uploadFileName 来源:bfi ymc call tool/list(uploadPath 字段)或 bfi ymc dump-list(uploadPath)");
8767
+ process.exit(1);
8768
+ }
8769
+ const out = opt(rest, "-o", "--out") || uploadFileName.split("/").pop() || "download.bin";
8770
+ try {
8771
+ const n = await api.download("ymc/tool/download", {
8772
+ ...requireEnv(rest),
8773
+ uploadFileName
8774
+ }, out);
8775
+ console.log(`✓ 已下载 ${n} 字节 → ${out}`);
8776
+ } catch (e) {
8777
+ console.error(`✗ ${e.message}`);
8778
+ process.exit(1);
8779
+ }
8780
+ break;
8781
+ }
8728
8782
  case "call": {
8729
8783
  const route = positionals(rest)[0];
8730
8784
  const dataStr = opt(rest, "--data") || "{}";
@@ -8752,7 +8806,7 @@ async function runYmc(args, globalOpts) {
8752
8806
  }
8753
8807
  default:
8754
8808
  console.error(`未知子命令:${sub}`);
8755
- printHelp();
8809
+ printHelp$1();
8756
8810
  process.exit(1);
8757
8811
  }
8758
8812
  }
@@ -8866,6 +8920,10 @@ const HELP = {
8866
8920
  "bpr-download": `bfi ymc bpr-download <pk> -e <env> [-o <输出文件>]
8867
8921
  BPR 报告 HTML 下载(文件流写盘)。pk 来源:bfi ymc call bpr/list --data '{"page":1,"pageSize":10}' -e <env>。
8868
8922
  输出默认 bpr_<pk前16>.html。`,
8923
+ "tool-download": `bfi ymc tool-download <uploadFileName> -e <env> [-o <输出文件>]
8924
+ 诊断文件内容下载(CPU 火焰图/堆 dump/BPR HTML 等文件流写盘)。
8925
+ uploadFileName 来源:bfi ymc call tool/list(uploadPath)或 bfi ymc dump-list(uploadPath)。
8926
+ 输出默认取文件名部分(monitor/xx.html → xx.html)。`,
8869
8927
  call: `bfi ymc call <route> -e <env> [--data '<json>'] [-t <time> | --start-time <s> --end-time <e>] [--timeout <秒>] [--json]
8870
8928
  通用透传:覆盖全部注册表端点(warningCall/warningSql/exceptionHistory/bpr/list/alert/detail/tool/list/app/history/environment/uba/query ...)。
8871
8929
  完整清单与参数声明:curl http://172.20.35.178:8901/openapi.json 或 /docs(Swagger UI)。
@@ -8879,14 +8937,16 @@ function printCommandHelp(cmd) {
8879
8937
  if (h) console.log(h);
8880
8938
  else {
8881
8939
  console.error(`未知子命令:${cmd}`);
8882
- printHelp();
8940
+ printHelp$1();
8883
8941
  process.exit(1);
8884
8942
  }
8885
8943
  }
8886
- function printHelp() {
8944
+ function printHelp$1() {
8887
8945
  console.log(`
8888
8946
  bfi ymc — 观测查询(v2 API:YMC/Kibana/SLS/trace)
8889
8947
 
8948
+ 详细帮助/示例:bfi ymc help <命令>(首次使用必看——含参数默认值与易错点)
8949
+
8890
8950
  命令:
8891
8951
  status 登录会话状态(dev/prod)
8892
8952
  trace <trace_id> -e <env> 三源合一(SLS+链路+ingress)
@@ -8902,6 +8962,12 @@ bfi ymc — 观测查询(v2 API:YMC/Kibana/SLS/trace)
8902
8962
  url-stats </uri> -t <time> -e <env> [--app <service_name>]
8903
8963
  ingress --trace-id <id> | --uri </path> [-t <time>] -e <env> [--exact] [--app <service_name>]
8904
8964
  dump-list [-t <time>] -e <env> [--app] [--type profiler]
8965
+ tool-download <uploadPath> -e <env> [-o <文件>] 诊断文件内容下载
8966
+ log-url <...> SLS 直链(日志中心页面直达)
8967
+ events / sample-list 集群事件 / 采样数据列表
8968
+ callstat / sqlstat 慢调用/慢SQL 统计版聚合
8969
+ rca <...> 官方智能诊断 RCA(长耗时)
8970
+ bpr-download <pk> BPR 报告下载
8905
8971
  cases <search|show|upload|index> 案例库(KEDB)
8906
8972
  wiki search "<kw>" [--source] [--titles] 知识库统一检索(wiki/jira/文库)
8907
8973
  wiki show <id> [--source] 知识条目全文
@@ -8919,6 +8985,298 @@ bfi ymc — 观测查询(v2 API:YMC/Kibana/SLS/trace)
8919
8985
  详细帮助:bfi ymc help <命令>(或 bfi ymc <命令> -h)——含参数说明/默认值/示例。
8920
8986
  `);
8921
8987
  }
8988
+ function loadSourceMapConsumer() {
8989
+ return createRequire(import.meta.url)("source-map").SourceMapConsumer;
8990
+ }
8991
+ function parseErrorLog(log) {
8992
+ const out = {
8993
+ mdf: null,
8994
+ errorType: "",
8995
+ errorMessage: "",
8996
+ frames: []
8997
+ };
8998
+ const code = log.match(/\b(E\d{5})\b/);
8999
+ const kind = log.match(/\[([^\]]{2,20}错误)\]/);
9000
+ const diwork = log.match(/diworkCode:\s*([^\]]+)\]/);
9001
+ const method = log.match(/\[([a-zA-Z_$][\w$]*(?:\.[\w$$.]+)+)\]\s*:/);
9002
+ const event = log.match(/execute\[([^\]]+)\]/);
9003
+ const bill = log.match(/\[([^\]]+)\]\s*\[[^\]]{2,20}错误\]/);
9004
+ if (code && log.includes("【MDF】")) out.mdf = {
9005
+ code: code[1],
9006
+ billName: bill?.[1] ?? "",
9007
+ errorKind: kind?.[1] ?? "",
9008
+ diworkCode: diwork?.[1]?.trim() ?? "",
9009
+ method: method?.[1] ?? "",
9010
+ event: event?.[1] ?? ""
9011
+ };
9012
+ const errMatches = [...log.matchAll(/(TypeError|ReferenceError|SyntaxError|RangeError)\s*:\s*([^\n\r]+)/g)];
9013
+ if (errMatches.length > 0) {
9014
+ const last = errMatches[errMatches.length - 1];
9015
+ out.errorType = last[1];
9016
+ out.errorMessage = `${last[1]}: ${last[2].trim()}`;
9017
+ }
9018
+ for (const m of log.matchAll(/(https?:\/\/[^\s()"']+?\.min\.js):(\d+):(\d+)/g)) out.frames.push({
9019
+ index: out.frames.length,
9020
+ url: m[1],
9021
+ line: Number(m[2]),
9022
+ col: Number(m[3])
9023
+ });
9024
+ return out;
9025
+ }
9026
+ function pickFrame(frames) {
9027
+ const ext = frames.findIndex((f) => /\/extend\.\d+\.[a-f0-9]+/.test(f.url));
9028
+ return ext !== -1 ? ext : 0;
9029
+ }
9030
+ function resolveSourceMappingUrl(jsUrl, comment) {
9031
+ if (/^https?:\/\//.test(comment)) return comment;
9032
+ return `${jsUrl.replace(/\/[^/]*$/, "")}/${comment.replace(/^\.?\//, "")}`;
9033
+ }
9034
+ function webpackSourceInfo(source) {
9035
+ const m = source.match(/^webpack:\/\/([^/]+)\/(.+)$/);
9036
+ if (!m) return {
9037
+ repo: "",
9038
+ filePath: source.replace(/^webpack:\/\//, "")
9039
+ };
9040
+ return {
9041
+ repo: m[1],
9042
+ filePath: m[2].replace(/^\.\//, "")
9043
+ };
9044
+ }
9045
+ function extractContext(content, line, ctx) {
9046
+ const lines = content.split("\n");
9047
+ const start = Math.max(1, line - ctx);
9048
+ const end = Math.min(lines.length, line + ctx);
9049
+ const width = String(end).length;
9050
+ const out = [];
9051
+ for (let i = start; i <= end; i++) {
9052
+ const mark = i === line ? " --> " : " ";
9053
+ out.push(`${mark}${String(i).padStart(width)} | ${lines[i - 1]}`);
9054
+ }
9055
+ return out.join("\n");
9056
+ }
9057
+ const UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36";
9058
+ function httpGet(url, redirects = 0) {
9059
+ return new Promise((resolve, reject) => {
9060
+ if (redirects > 5) {
9061
+ reject(/* @__PURE__ */ new Error(`重定向超过 5 次: ${url}`));
9062
+ return;
9063
+ }
9064
+ const req = (url.startsWith("https://") ? https : http).get(url, {
9065
+ headers: {
9066
+ "user-agent": UA,
9067
+ accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
9068
+ cookie: "debug=true"
9069
+ },
9070
+ timeout: 6e4
9071
+ }, (res) => {
9072
+ const status = res.statusCode ?? 0;
9073
+ const loc = res.headers.location;
9074
+ if (status >= 300 && status < 400 && loc) {
9075
+ res.resume();
9076
+ httpGet(new URL(loc, url).toString(), redirects + 1).then(resolve, reject);
9077
+ return;
9078
+ }
9079
+ const chunks = [];
9080
+ res.on("data", (c) => chunks.push(c));
9081
+ res.on("end", () => resolve({
9082
+ status,
9083
+ body: Buffer.concat(chunks)
9084
+ }));
9085
+ });
9086
+ req.on("timeout", () => req.destroy(/* @__PURE__ */ new Error(`下载超时(60s): ${url}`)));
9087
+ req.on("error", reject);
9088
+ });
9089
+ }
9090
+ async function downloadTo(url, file, what) {
9091
+ const { status, body } = await httpGet(url);
9092
+ if (status !== 200 || body.length === 0) throw new Error(`${what}下载失败(HTTP ${status}): ${url}\n若 sourcemap 被剥离,降级:按 JS 文件名走 bfi-code-search 内容搜索定位`);
9093
+ fs.writeFileSync(file, body);
9094
+ return body.length;
9095
+ }
9096
+ function cacheDirFor(jsUrl, root) {
9097
+ const u = new URL(jsUrl);
9098
+ return path$1.join(root || path$1.join(os$1.homedir(), ".bfi", "fe-locate", "cache"), u.host, path$1.dirname(u.pathname).replace(/^\/|\/$/g, ""));
9099
+ }
9100
+ function readStdin() {
9101
+ return new Promise((resolve, reject) => {
9102
+ let data = "";
9103
+ process.stdin.setEncoding("utf-8");
9104
+ process.stdin.on("data", (c) => {
9105
+ data += c;
9106
+ });
9107
+ process.stdin.on("end", () => resolve(data));
9108
+ process.stdin.on("error", reject);
9109
+ });
9110
+ }
9111
+ function parseSourceMappingComment(jsContent) {
9112
+ const m = jsContent.slice(-3e3).match(/\/\/# sourceMappingURL=(\S+)/);
9113
+ if (!m) throw new Error("JS 文件末尾未找到 //# sourceMappingURL= 注释(构建未产出 sourcemap)");
9114
+ return m[1];
9115
+ }
9116
+ async function locateInMap(mapFile, line, col) {
9117
+ const consumer = await new (loadSourceMapConsumer())(JSON.parse(fs.readFileSync(mapFile, "utf-8")));
9118
+ try {
9119
+ let pos = consumer.originalPositionFor({
9120
+ line,
9121
+ column: Math.max(0, col - 1)
9122
+ });
9123
+ if (!pos?.source) pos = consumer.originalPositionFor({
9124
+ line,
9125
+ column: col
9126
+ });
9127
+ if (!pos?.source) throw new Error(`sourcemap 中无该位置映射(line=${line}, col=${col})——JS 与 map 版本可能不一致`);
9128
+ const content = pos.source ? consumer.sourceContentFor(pos.source, true) : null;
9129
+ return {
9130
+ source: pos.source,
9131
+ line: pos.line ?? 0,
9132
+ column: pos.column ?? 0,
9133
+ name: pos.name ?? "",
9134
+ content
9135
+ };
9136
+ } finally {
9137
+ consumer.destroy?.();
9138
+ }
9139
+ }
9140
+ function printHelp() {
9141
+ console.log(`bfi fe-locate — 前端错误定位(sourcemap 还原)
9142
+
9143
+ 用法:
9144
+ bfi fe-locate "<错误日志>" # 单参数(控制台报错栈粘贴)
9145
+ cat error.log | bfi fe-locate --stdin # 长日志走 stdin(推荐,免转义)
9146
+
9147
+ 选项:
9148
+ --json 结构化 JSON 输出(定位结果+全部帧+上下文)
9149
+ --stdin 从标准输入读取错误日志
9150
+ --context <N> 源码上下文行数(默认 5)
9151
+ --frame <N> 指定第 N 帧定位(1-based;默认智能选帧:extend 业务帧优先)
9152
+ --no-cache 强制重新下载 JS/sourcemap
9153
+
9154
+ 示例:
9155
+ bfi fe-locate "TypeError: e.get(...).setValue is not a function at y (https://static-dc-core1.yonyoucloud.com/static/mdf/udinghuo/latest/javascripts/extend.23.xxx.min.js:1:100358)"`);
9156
+ }
9157
+ function printResult(r, ctxLines) {
9158
+ const err = r.error;
9159
+ const mdf = r.mdf;
9160
+ console.log("═══ bfi fe-locate — 前端错误定位 ═══\n");
9161
+ if (mdf) console.log(`【MDF 头】${mdf.code} | 单据:${mdf.billName} | 类型:${mdf.errorKind} | diworkCode:${mdf.diworkCode} | 方法:${mdf.method} | 事件:${mdf.event}`);
9162
+ console.log(`【错误】${err.type}: ${err.message}`);
9163
+ const frames = r.frames;
9164
+ const sel = r.selected;
9165
+ console.log(`【选帧】第 ${sel.index} 帧(${sel.reason}): ${path$1.basename(String(sel.url))}:${sel.line}:${sel.col}(共 ${frames.length} 帧)`);
9166
+ console.log(`【压缩 JS】${r.jsUrl}`);
9167
+ const resolved = r.resolved;
9168
+ if (resolved) {
9169
+ console.log("\n── sourcemap 还原 ──");
9170
+ console.log(`原始文件: ${resolved.source}`);
9171
+ const info = resolved.webpack;
9172
+ if (info.repo) console.log(`包内仓库: ${info.repo}(注:包内仓库名≠GitLab 仓库名,远程定位按文件名搜索)`);
9173
+ console.log(`文件路径: ${info.filePath}`);
9174
+ console.log(`位置: 行 ${resolved.line}, 列 ${resolved.column}`);
9175
+ if (resolved.name) console.log(`函数名: ${resolved.name}`);
9176
+ if (ctxLines) {
9177
+ console.log(`\n── 源码上下文(sourcemap sourcesContent,±${r.context} 行)──`);
9178
+ console.log(ctxLines);
9179
+ }
9180
+ console.log("\n── 下一步 ──");
9181
+ console.log(`· 远程仓库: SourceGraph 精确文件名搜索 "${path$1.basename(info.filePath)}"(bfi-code-search)`);
9182
+ console.log("· 归因分析: 对照 bfi-diagnose-engine references/fe-error-attribution.md");
9183
+ console.log("· 数据格式类根因(接口返回变化)→ deep 档联动 bfi ymc ingress/trace 取证");
9184
+ }
9185
+ console.log(`\n缓存: ${r.cacheDir}`);
9186
+ }
9187
+ async function runFeLocate(args, globalOpts) {
9188
+ if (!args.length || flag(args, "--help") || flag(args, "-h")) {
9189
+ printHelp();
9190
+ return;
9191
+ }
9192
+ const jsonMode = flag(args, "--json");
9193
+ const noCache = flag(args, "--no-cache");
9194
+ const ctxN = Number(opt(args, "--context") ?? 5) || 5;
9195
+ const frameN = opt(args, "--frame");
9196
+ let log = positionals(args)[0] ?? "";
9197
+ if (!log && flag(args, "--stdin")) log = await readStdin();
9198
+ if (!log && !process.stdin.isTTY) log = await readStdin();
9199
+ if (!log.trim()) {
9200
+ console.error("缺少错误日志参数。粘贴控制台报错栈,或 cat error.log | bfi fe-locate --stdin");
9201
+ process.exit(1);
9202
+ }
9203
+ const parsed = parseErrorLog(log);
9204
+ if (parsed.frames.length === 0) {
9205
+ console.error("未在日志中找到压缩 JS 帧(https://...xxx.min.js:行:列)。请确认粘贴的是浏览器控制台报错栈。");
9206
+ process.exit(1);
9207
+ }
9208
+ const selIdx = frameN ? Math.max(0, Number(frameN) - 1) : pickFrame(parsed.frames);
9209
+ const selReason = frameN ? "--frame 指定" : parsed.frames[selIdx] && /\/extend\.\d+\./.test(parsed.frames[selIdx].url) ? "extend 业务帧优先" : "默认第一帧";
9210
+ if (selIdx >= parsed.frames.length) {
9211
+ console.error(`--frame ${frameN} 超出范围(共 ${parsed.frames.length} 帧)`);
9212
+ process.exit(1);
9213
+ }
9214
+ const frame = parsed.frames[selIdx];
9215
+ const cDir = cacheDirFor(frame.url, globalOpts["fe-cache-dir"]);
9216
+ fs.mkdirSync(cDir, { recursive: true });
9217
+ const jsFile = path$1.join(cDir, "error.min.js");
9218
+ const mapFile = path$1.join(cDir, "error.min.js.map");
9219
+ const metaFile = path$1.join(cDir, "meta.json");
9220
+ let sourceMappingURL = "";
9221
+ if (!noCache && fs.existsSync(metaFile)) try {
9222
+ sourceMappingURL = JSON.parse(fs.readFileSync(metaFile, "utf-8")).sourceMappingUrl ?? "";
9223
+ } catch {}
9224
+ if (sourceMappingURL && fs.existsSync(mapFile)) console.error(`[缓存命中] ${cDir}`);
9225
+ else {
9226
+ const jsSize = await downloadTo(frame.url, jsFile, "压缩 JS");
9227
+ console.error(`[下载] 压缩 JS(${(jsSize / 1024).toFixed(0)} KB)`);
9228
+ const comment = parseSourceMappingComment(fs.readFileSync(jsFile, "utf-8"));
9229
+ sourceMappingURL = resolveSourceMappingUrl(frame.url, comment);
9230
+ console.error(`[解析] sourceMappingURL → ${sourceMappingURL}`);
9231
+ const mapSize = await downloadTo(sourceMappingURL, mapFile, "sourcemap");
9232
+ console.error(`[下载] sourcemap(${(mapSize / 1024).toFixed(0)} KB)`);
9233
+ fs.writeFileSync(metaFile, JSON.stringify({
9234
+ jsUrl: frame.url,
9235
+ sourceMappingUrl: sourceMappingURL,
9236
+ downloadedAt: (/* @__PURE__ */ new Date()).toISOString()
9237
+ }, null, 2));
9238
+ }
9239
+ const located = await locateInMap(mapFile, frame.line, frame.col);
9240
+ const webpack = webpackSourceInfo(located.source);
9241
+ const ctxLines = located.content ? extractContext(located.content, located.line, ctxN) : null;
9242
+ const result = {
9243
+ error: {
9244
+ type: parsed.errorType,
9245
+ message: parsed.errorMessage
9246
+ },
9247
+ mdf: parsed.mdf,
9248
+ frames: parsed.frames,
9249
+ selected: {
9250
+ ...frame,
9251
+ index: selIdx + 1,
9252
+ reason: selReason
9253
+ },
9254
+ jsUrl: frame.url,
9255
+ sourceMappingURL,
9256
+ resolved: {
9257
+ source: located.source,
9258
+ webpack,
9259
+ line: located.line,
9260
+ column: located.column,
9261
+ name: located.name
9262
+ },
9263
+ context: ctxN,
9264
+ contextText: ctxLines,
9265
+ cacheDir: cDir,
9266
+ locatedAt: (/* @__PURE__ */ new Date()).toISOString()
9267
+ };
9268
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14);
9269
+ const runDir = path$1.join(cDir, stamp);
9270
+ fs.mkdirSync(runDir, { recursive: true });
9271
+ fs.writeFileSync(path$1.join(runDir, "error.log"), log);
9272
+ fs.writeFileSync(path$1.join(runDir, "result.json"), JSON.stringify(result, null, 2));
9273
+ if (jsonMode) {
9274
+ console.log(JSON.stringify(result, null, 2));
9275
+ return;
9276
+ }
9277
+ printResult(result, ctxLines);
9278
+ console.log(`\n结果已存: ${path$1.join(runDir, "result.json")}`);
9279
+ }
8922
9280
  const __dirname = dirname(fileURLToPath(import.meta.url));
8923
9281
  function getVersion() {
8924
9282
  try {
@@ -9309,6 +9667,9 @@ async function main() {
9309
9667
  case "ymc":
9310
9668
  await runYmc(restArgs, globalOpts);
9311
9669
  break;
9670
+ case "fe-locate":
9671
+ await runFeLocate(restArgs, globalOpts);
9672
+ break;
9312
9673
  case "auth":
9313
9674
  if (restArgs[0] === "logout") await runLogout();
9314
9675
  else if (restArgs[0] === "whoami") await runWhoami(globalOpts);
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "bfi-cli",
3
- "version": "0.1.73",
3
+ "version": "0.1.75",
4
4
  "description": "用友大业财BFI统一命令行工具,支持技能,技能包,元数据,UI元数据等",
5
5
  "type": "module",
6
6
  "bin": {
7
- "bfi": "./bin/bfi.js",
8
- "bfi-db-mcp": "./bin/bfi-db-mcp.js"
7
+ "bfi": "bin/bfi.js",
8
+ "bfi-db-mcp": "bin/bfi-db-mcp.js"
9
9
  },
10
10
  "files": [
11
11
  "dist",
@@ -50,6 +50,7 @@
50
50
  "@modelcontextprotocol/sdk": "^1.12.0",
51
51
  "@vercel/detect-agent": "^1.2.3",
52
52
  "mysql2": "^3.14.0",
53
+ "source-map": "^0.7.4",
53
54
  "yaml": "^2.8.3",
54
55
  "zod": "^3.25.0"
55
56
  }