bfi-cli 0.1.95 → 0.1.97
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/bin/bfi-weknora-mcp.js +13 -0
- package/dist/cli.mjs +2 -77
- package/dist/mcp-weknora/main.d.mts +1 -0
- package/dist/mcp-weknora/main.mjs +189 -0
- package/package.json +3 -2
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import module from 'node:module';
|
|
4
|
+
|
|
5
|
+
if (module.enableCompileCache && !process.env.NODE_DISABLE_COMPILE_CACHE) {
|
|
6
|
+
try {
|
|
7
|
+
module.enableCompileCache();
|
|
8
|
+
} catch {
|
|
9
|
+
// Ignore errors
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
await import('../dist/mcp-weknora/main.mjs');
|
package/dist/cli.mjs
CHANGED
|
@@ -7162,7 +7162,6 @@ async function handleInstall(args, api, globalOpts) {
|
|
|
7162
7162
|
console.error(`${RED$1}获取包信息失败:${error.message}${RESET$3}`);
|
|
7163
7163
|
process.exit(1);
|
|
7164
7164
|
}
|
|
7165
|
-
if (pkgInfo.kind === "PLUGIN") throw new Error("这是完整插件,不是技能组合;请使用 --as plugin --agent <目标Agent> 安装,插件可能包含 MCP 和 Hooks。");
|
|
7166
7165
|
const skills = pkgInfo.skills || [];
|
|
7167
7166
|
if (skills.length === 0) {
|
|
7168
7167
|
console.log(`${YELLOW$1}包中没有可独立安装的技能。${RESET$3}`);
|
|
@@ -7485,9 +7484,7 @@ async function handleUpdate(args, api, globalOpts) {
|
|
|
7485
7484
|
console.log(`${DIM$3}正在更新 ${packagesToUpdate.length} 个包...${RESET$3}`);
|
|
7486
7485
|
for (const pkg of packagesToUpdate) try {
|
|
7487
7486
|
const pkgInfo = await api.getPackage(pkg.namespace, pkg.slug);
|
|
7488
|
-
const
|
|
7489
|
-
if (info.kind === "PLUGIN") throw new Error("这是完整插件,请使用 --as plugin --agent <目标Agent> 更新;不会按技能组合安装。");
|
|
7490
|
-
const skills = info.skills || [];
|
|
7487
|
+
const skills = (pkgInfo.data || pkgInfo).skills || [];
|
|
7491
7488
|
console.log(` ${DIM$3}更新 @${pkg.namespace}/${pkg.slug}(${skills.length} 个技能)...${RESET$3}`);
|
|
7492
7489
|
for (const skill of skills) {
|
|
7493
7490
|
const skillRef = skill.namespace ? `@${skill.namespace}/${skill.slug}` : skill.slug;
|
|
@@ -8118,17 +8115,6 @@ const ALLOWED_FLAGS = {
|
|
|
8118
8115
|
"--app",
|
|
8119
8116
|
"--type"
|
|
8120
8117
|
],
|
|
8121
|
-
cases: [
|
|
8122
|
-
"--type",
|
|
8123
|
-
"--finger",
|
|
8124
|
-
"--service",
|
|
8125
|
-
"--kw",
|
|
8126
|
-
"--limit",
|
|
8127
|
-
"--file",
|
|
8128
|
-
"--env",
|
|
8129
|
-
"--confidence",
|
|
8130
|
-
"--title"
|
|
8131
|
-
],
|
|
8132
8118
|
wiki: [
|
|
8133
8119
|
"--limit",
|
|
8134
8120
|
"--source",
|
|
@@ -8571,68 +8557,6 @@ async function runYmc(args, globalOpts) {
|
|
|
8571
8557
|
emit(await api.call("ymc/tool/list", body), jsonMode, rowsSummary("诊断文件"));
|
|
8572
8558
|
break;
|
|
8573
8559
|
}
|
|
8574
|
-
case "cases": {
|
|
8575
|
-
const sub2 = rest[0] || "search";
|
|
8576
|
-
const rest2 = rest.slice(1);
|
|
8577
|
-
if (sub2 === "search") {
|
|
8578
|
-
const body = {};
|
|
8579
|
-
for (const [k, name] of [
|
|
8580
|
-
["type", "--type"],
|
|
8581
|
-
["fingerprint", "--finger"],
|
|
8582
|
-
["service", "--service"],
|
|
8583
|
-
["keyword", "--kw"]
|
|
8584
|
-
]) {
|
|
8585
|
-
const v = opt(rest2, name);
|
|
8586
|
-
if (v) body[k] = v;
|
|
8587
|
-
}
|
|
8588
|
-
if (opt(rest2, "--limit")) body.limit = Number(opt(rest2, "--limit"));
|
|
8589
|
-
emit(await api.call("cases/search", body), jsonMode, (data) => {
|
|
8590
|
-
const d = data;
|
|
8591
|
-
const list = d.cases || [];
|
|
8592
|
-
if (!list.length) return "(无命中案例)";
|
|
8593
|
-
return `✓ 命中 ${d.count} 条:\n` + list.map((x) => ` [${x.case_id}] (${x.type}/${x.confidence}) ${x.title}\n 指纹: ${JSON.stringify(x.fingerprint)} ${x.env || ""} ${x.service || ""} by ${x.author}`).join("\n");
|
|
8594
|
-
});
|
|
8595
|
-
} else if (sub2 === "show") {
|
|
8596
|
-
const id = positionals(rest2)[0];
|
|
8597
|
-
if (!id) {
|
|
8598
|
-
console.error("用法:bfi ymc cases show <case_id>");
|
|
8599
|
-
process.exit(1);
|
|
8600
|
-
}
|
|
8601
|
-
emit(await api.get(`cases/${id}`), jsonMode);
|
|
8602
|
-
} else if (sub2 === "upload") {
|
|
8603
|
-
const file = opt(rest2, "--file");
|
|
8604
|
-
if (!file) {
|
|
8605
|
-
console.error("用法:bfi ymc cases upload --file <case.md> [--type] [--finger \"a,b\"] [--env] [--service]");
|
|
8606
|
-
process.exit(1);
|
|
8607
|
-
}
|
|
8608
|
-
const { readFileSync } = await import("node:fs");
|
|
8609
|
-
const body = {
|
|
8610
|
-
type: opt(rest2, "--type") || "review",
|
|
8611
|
-
fingerprint: (opt(rest2, "--finger") || "").split(",").map((s) => s.trim()).filter(Boolean),
|
|
8612
|
-
content: readFileSync(file, "utf-8")
|
|
8613
|
-
};
|
|
8614
|
-
for (const [k, name] of [
|
|
8615
|
-
["env", "--env"],
|
|
8616
|
-
["service", "--service"],
|
|
8617
|
-
["confidence", "--confidence"],
|
|
8618
|
-
["title", "--title"]
|
|
8619
|
-
]) {
|
|
8620
|
-
const v = opt(rest2, name);
|
|
8621
|
-
if (v) body[k] = v;
|
|
8622
|
-
}
|
|
8623
|
-
emit(await api.call("cases", body), jsonMode, (data) => `✓ 案例已上传: ${data.case_id}`);
|
|
8624
|
-
} else if (sub2 === "index") emit(await api.get("cases/index/overview"), jsonMode, (data) => {
|
|
8625
|
-
const d = data;
|
|
8626
|
-
const byType = Object.entries(d.by_type || {}).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
8627
|
-
const fps = (d.top_fingerprints || []).map((x) => `${x.fingerprint}(${x.count})`).join(", ");
|
|
8628
|
-
return `✓ 案例总数: ${d.total}\n 类型分布: ${byType}\n 高频指纹: ${fps}`;
|
|
8629
|
-
});
|
|
8630
|
-
else {
|
|
8631
|
-
console.error("用法:bfi ymc cases <search|show|upload|index>");
|
|
8632
|
-
process.exit(1);
|
|
8633
|
-
}
|
|
8634
|
-
break;
|
|
8635
|
-
}
|
|
8636
8560
|
case "wiki": {
|
|
8637
8561
|
const sub2 = rest[0] || "search";
|
|
8638
8562
|
const rest2 = rest.slice(1);
|
|
@@ -8869,6 +8793,7 @@ async function runYmc(args, globalOpts) {
|
|
|
8869
8793
|
break;
|
|
8870
8794
|
}
|
|
8871
8795
|
default: {
|
|
8796
|
+
if (sub === "cases" || sub.startsWith("cases")) console.error("案例库已迁移 WeKnora:检索/归档改用 weknora-bfi MCP 工具(kb_search / kb_archive_case)。");
|
|
8872
8797
|
console.error(`未知子命令:${sub}`);
|
|
8873
8798
|
const hint = SUBCOMMAND_HINTS[sub];
|
|
8874
8799
|
if (hint) console.error(`提示:${hint}`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { CallToolRequestSchema, CallToolResultSchema, ListToolsRequestSchema, ListToolsResultSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
5
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
const DEFAULT_WEKNORA_URL = process.env.WEKNORA_MCP_URL || "https://bfi.yyuap.com/weknora/mcp/e8e50146-81f6-4aca-b5cf-cce711bd2b99";
|
|
10
|
+
const CASE_KB_NAME = "案例库";
|
|
11
|
+
var WeknoraError = class extends Error {
|
|
12
|
+
errorKind;
|
|
13
|
+
constructor(errorKind, message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.errorKind = errorKind;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var WeknoraRemote = class {
|
|
19
|
+
url;
|
|
20
|
+
token;
|
|
21
|
+
client = null;
|
|
22
|
+
constructor(url, token) {
|
|
23
|
+
this.url = url;
|
|
24
|
+
this.token = token;
|
|
25
|
+
}
|
|
26
|
+
async listTools() {
|
|
27
|
+
const client = await this.ensure();
|
|
28
|
+
try {
|
|
29
|
+
return (await client.request({ method: "tools/list" }, ListToolsResultSchema)).tools;
|
|
30
|
+
} catch (e) {
|
|
31
|
+
this.handleFailure(e);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async callTool(name, args) {
|
|
35
|
+
if (!this.token()) throw new WeknoraError("unauthorized", this.loginHint());
|
|
36
|
+
const client = await this.ensure();
|
|
37
|
+
try {
|
|
38
|
+
return await client.request({
|
|
39
|
+
method: "tools/call",
|
|
40
|
+
params: {
|
|
41
|
+
name,
|
|
42
|
+
arguments: args ?? {}
|
|
43
|
+
}
|
|
44
|
+
}, CallToolResultSchema);
|
|
45
|
+
} catch (e) {
|
|
46
|
+
this.handleFailure(e);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async ensure() {
|
|
50
|
+
if (this.client) return this.client;
|
|
51
|
+
const token = this.token();
|
|
52
|
+
if (!token) throw new WeknoraError("unauthorized", this.loginHint());
|
|
53
|
+
const client = new Client({
|
|
54
|
+
name: "bfi-weknora-mcp",
|
|
55
|
+
version: "0.1.0"
|
|
56
|
+
});
|
|
57
|
+
const transport = new StreamableHTTPClientTransport(new URL(this.url), { requestInit: { headers: { Authorization: `Bearer ${token}` } } });
|
|
58
|
+
try {
|
|
59
|
+
await client.connect(transport);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
throw new WeknoraError("weknora_unreachable", `WeKnora 服务不可达(${this.url}): ${e instanceof Error ? e.message : String(e)}`);
|
|
62
|
+
}
|
|
63
|
+
this.client = client;
|
|
64
|
+
return client;
|
|
65
|
+
}
|
|
66
|
+
handleFailure(e) {
|
|
67
|
+
this.client = null;
|
|
68
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
69
|
+
if (/401|unauthorized|Unauthorized/i.test(message)) throw new WeknoraError("unauthorized", `WeKnora token 无效或过期。${this.loginHint()}`);
|
|
70
|
+
throw new WeknoraError("weknora_error", `WeKnora 远端调用失败: ${message}`);
|
|
71
|
+
}
|
|
72
|
+
loginHint() {
|
|
73
|
+
return "未找到 bfi token:请先执行 `bfi auth login --token <sk-token>`(token 获取地址 https://bfi.yyuap.com/user/profile),或设置 BFI_TOKEN 环境变量";
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const GLOBAL_CONFIG_DIR = path.join(os.homedir(), ".bfi");
|
|
77
|
+
const GLOBAL_CONFIG_FILE = path.join(GLOBAL_CONFIG_DIR, "config.json");
|
|
78
|
+
path.join(".bfi", "lock.json");
|
|
79
|
+
function loadGlobalConfig() {
|
|
80
|
+
try {
|
|
81
|
+
if (fs.existsSync(GLOBAL_CONFIG_FILE)) {
|
|
82
|
+
const content = fs.readFileSync(GLOBAL_CONFIG_FILE, "utf8");
|
|
83
|
+
return JSON.parse(content);
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.warn(`Warning: Failed to load global config: ${error.message}`);
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function resolveToken() {
|
|
91
|
+
if (process.env.BFI_TOKEN) return process.env.BFI_TOKEN;
|
|
92
|
+
return loadGlobalConfig()?.token ?? null;
|
|
93
|
+
}
|
|
94
|
+
const BLOCKED_PREFIXES = [
|
|
95
|
+
"create_",
|
|
96
|
+
"delete_",
|
|
97
|
+
"update_",
|
|
98
|
+
"add_"
|
|
99
|
+
];
|
|
100
|
+
const ARCHIVE_TOOL = {
|
|
101
|
+
name: "kb_archive_case",
|
|
102
|
+
description: `把诊断结论归档到「${CASE_KB_NAME}」知识库(唯一写通道)。content_md 建议结构:# 标题 / ## 现象 / ## 根因链 / ## 修复 / ## 证据与复现 / 指纹一行(供先验检索)。归档后先验检索即可命中,避免同类问题重复排查。`,
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: "object",
|
|
105
|
+
properties: {
|
|
106
|
+
title: {
|
|
107
|
+
type: "string",
|
|
108
|
+
description: "案例标题(症状+组件,如「OOM OCR 切图重复解码」)"
|
|
109
|
+
},
|
|
110
|
+
content_md: {
|
|
111
|
+
type: "string",
|
|
112
|
+
description: "案例正文(Markdown)"
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
required: ["title", "content_md"]
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
function textResult(payload, isError = false) {
|
|
119
|
+
return {
|
|
120
|
+
content: [{
|
|
121
|
+
type: "text",
|
|
122
|
+
text: JSON.stringify(payload, null, 2)
|
|
123
|
+
}],
|
|
124
|
+
isError
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function isBlocked(name) {
|
|
128
|
+
return BLOCKED_PREFIXES.some((p) => name.startsWith(p));
|
|
129
|
+
}
|
|
130
|
+
function buildServer(remote) {
|
|
131
|
+
const weknora = remote ?? new WeknoraRemote(process.env.WEKNORA_MCP_URL || DEFAULT_WEKNORA_URL, resolveToken);
|
|
132
|
+
const server = new Server({
|
|
133
|
+
name: "bfi-weknora-mcp",
|
|
134
|
+
version: "0.1.0"
|
|
135
|
+
}, {
|
|
136
|
+
capabilities: { tools: {} },
|
|
137
|
+
instructions: [
|
|
138
|
+
"BFI 知识库/案例库通道(bfi cli 登录态鉴权,零配置)。",
|
|
139
|
+
"先验查询:search_knowledge(跨库语义检索:案例库+wiki×4+jira-支持案例+领域知识,诊断前必查)",
|
|
140
|
+
"ask(合成答案+引用)/ grep_chunks(确切关键词)",
|
|
141
|
+
"归档:kb_archive_case(诊断完成后把根因链+修复写回案例库,供下次先验命中;唯一写通道)",
|
|
142
|
+
"阅读:list_knowledge_bases/list_documents/read_document + wiki_*"
|
|
143
|
+
].join("\n")
|
|
144
|
+
});
|
|
145
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
146
|
+
return { tools: [...(await weknora.listTools()).filter((t) => !isBlocked(t.name)), ARCHIVE_TOOL] };
|
|
147
|
+
});
|
|
148
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
149
|
+
const { name, arguments: args } = request.params;
|
|
150
|
+
try {
|
|
151
|
+
if (name === "kb_archive_case") {
|
|
152
|
+
const title = String(args?.title ?? "").trim();
|
|
153
|
+
const content = String(args?.content_md ?? "").trim();
|
|
154
|
+
if (!title || !content) return textResult({
|
|
155
|
+
ok: false,
|
|
156
|
+
error_kind: "invalid_params",
|
|
157
|
+
message: "title 与 content_md 必填"
|
|
158
|
+
}, true);
|
|
159
|
+
const md = `# ${title}\n\n${content}\n`;
|
|
160
|
+
return await weknora.callTool("add_document", {
|
|
161
|
+
knowledge_base_id: CASE_KB_NAME,
|
|
162
|
+
title,
|
|
163
|
+
content: md
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (isBlocked(name)) return textResult({
|
|
167
|
+
ok: false,
|
|
168
|
+
error_kind: "write_blocked",
|
|
169
|
+
message: `写类工具 ${name} 不透传。诊断归档请用 kb_archive_case(自动写入案例库)`
|
|
170
|
+
}, true);
|
|
171
|
+
return await weknora.callTool(name, args ?? {});
|
|
172
|
+
} catch (e) {
|
|
173
|
+
return textResult({
|
|
174
|
+
ok: false,
|
|
175
|
+
error_kind: e instanceof WeknoraError ? e.errorKind : "internal_error",
|
|
176
|
+
message: e instanceof Error ? e.message : String(e)
|
|
177
|
+
}, true);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
return server;
|
|
181
|
+
}
|
|
182
|
+
async function main() {
|
|
183
|
+
await buildServer().connect(new StdioServerTransport());
|
|
184
|
+
}
|
|
185
|
+
main().catch((e) => {
|
|
186
|
+
console.error("[bfi-weknora-mcp] 启动失败:", e);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
});
|
|
189
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bfi-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.97",
|
|
4
4
|
"description": "用友大业财BFI统一命令行工具,支持技能,技能包,元数据,UI元数据等",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"bfi": "bin/bfi.js",
|
|
8
8
|
"bfi-db-mcp": "bin/bfi-db-mcp.js",
|
|
9
|
-
"bfi-codesearch-mcp": "bin/bfi-codesearch-mcp.js"
|
|
9
|
+
"bfi-codesearch-mcp": "bin/bfi-codesearch-mcp.js",
|
|
10
|
+
"bfi-weknora-mcp": "bin/bfi-weknora-mcp.js"
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
12
13
|
"dist",
|