protocol-proxy 2.9.0 → 2.10.1
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/lib/config-store.js +45 -2
- package/lib/mcp-client.js +423 -0
- package/lib/prompt-builder.js +94 -0
- package/lib/skill-store.js +150 -0
- package/package.json +2 -1
- package/public/app.js +676 -45
- package/public/index.html +184 -3
- package/public/style.css +270 -2
- package/server.js +1141 -47
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const SYSTEM_DIR = path.join(__dirname, '..', 'skills', 'system');
|
|
6
|
+
const PRESET_DIR = path.join(__dirname, '..', 'skills', 'preset');
|
|
7
|
+
const USER_DIR = path.join(os.homedir(), '.protocol-proxy', 'skills');
|
|
8
|
+
|
|
9
|
+
let skills = {}; // name → { name, description, content, category }
|
|
10
|
+
|
|
11
|
+
function parseFrontmatter(text) {
|
|
12
|
+
const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
13
|
+
if (!match) return { name: '', description: '', body: text };
|
|
14
|
+
const meta = match[1];
|
|
15
|
+
const body = match[2];
|
|
16
|
+
let name = '', description = '', mcpServers = [];
|
|
17
|
+
for (const line of meta.split('\n')) {
|
|
18
|
+
const trimmed = line.trim();
|
|
19
|
+
const nm = trimmed.match(/^name:\s*['"]?(.+?)['"]?\s*$/);
|
|
20
|
+
if (nm) name = nm[1];
|
|
21
|
+
const dm = trimmed.match(/^description:\s*['"]?(.+?)['"]?\s*$/);
|
|
22
|
+
if (dm) description = dm[1];
|
|
23
|
+
const mm = trimmed.match(/^mcp:\s*\[([^\]]*)\]/);
|
|
24
|
+
if (mm) mcpServers = mm[1].split(',').map(s => s.trim()).filter(Boolean);
|
|
25
|
+
}
|
|
26
|
+
return { name, description, mcpServers, body };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function listDir(dirPath) {
|
|
30
|
+
if (!fs.existsSync(dirPath)) return [];
|
|
31
|
+
const result = [];
|
|
32
|
+
function walk(dir, prefix) {
|
|
33
|
+
try {
|
|
34
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
35
|
+
if (entry.name.startsWith('.')) continue;
|
|
36
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
37
|
+
if (entry.isDirectory()) walk(path.join(dir, entry.name), rel);
|
|
38
|
+
else result.push(rel);
|
|
39
|
+
}
|
|
40
|
+
} catch (err) { console.error(`[skill-store] 读取目录失败:`, err.message); }
|
|
41
|
+
}
|
|
42
|
+
walk(dirPath, '');
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function loadFromDir(dir, category) {
|
|
47
|
+
if (!fs.existsSync(dir)) return;
|
|
48
|
+
try {
|
|
49
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
if (!entry.isDirectory()) continue;
|
|
52
|
+
const skillDir = path.join(dir, entry.name);
|
|
53
|
+
if (category === 'preset' && fs.existsSync(path.join(skillDir, '.deleted'))) continue;
|
|
54
|
+
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
55
|
+
if (!fs.existsSync(skillFile)) continue;
|
|
56
|
+
try {
|
|
57
|
+
const raw = fs.readFileSync(skillFile, 'utf8');
|
|
58
|
+
const { name, description, mcpServers, body } = parseFrontmatter(raw);
|
|
59
|
+
const skillName = name || entry.name;
|
|
60
|
+
const scripts = listDir(path.join(skillDir, 'scripts'));
|
|
61
|
+
const references = listDir(path.join(skillDir, 'reference'));
|
|
62
|
+
skills[skillName] = { name: skillName, description, mcpServers, content: body.trim(), category, dirPath: skillDir, scripts, references };
|
|
63
|
+
} catch (err) { console.error(`[skill-store] 加载 ${entry.name} 失败:`, err.message); }
|
|
64
|
+
}
|
|
65
|
+
} catch (err) { console.error(`[skill-store] 扫描 ${dir} 失败:`, err.message); }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function list() {
|
|
69
|
+
return Object.values(skills);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function get(name) {
|
|
73
|
+
return skills[name] || null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function create(name, description, content) {
|
|
77
|
+
if (skills[name]) return null; // 已存在
|
|
78
|
+
const dir = path.join(USER_DIR, name);
|
|
79
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
80
|
+
const frontmatter = `---\nname: ${name}\ndescription: ${description}\n---\n\n${content}`;
|
|
81
|
+
fs.writeFileSync(path.join(dir, 'SKILL.md'), frontmatter, 'utf8');
|
|
82
|
+
skills[name] = { name, description, content, category: 'user' };
|
|
83
|
+
return skills[name];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function createFromUpload(files) {
|
|
87
|
+
const skillMd = files.find(f => f.path === 'SKILL.md');
|
|
88
|
+
if (!skillMd) return null;
|
|
89
|
+
const raw = Buffer.from(skillMd.content, 'base64').toString('utf8');
|
|
90
|
+
const { name: parsedName, description, mcpServers, body } = parseFrontmatter(raw);
|
|
91
|
+
if (!parsedName) return null;
|
|
92
|
+
if (skills[parsedName]) return null;
|
|
93
|
+
const dir = path.join(USER_DIR, parsedName);
|
|
94
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
95
|
+
for (const f of files) {
|
|
96
|
+
const safePath = f.path.replace(/\\/g, '/');
|
|
97
|
+
if (safePath.includes('..')) continue;
|
|
98
|
+
const target = path.join(dir, safePath);
|
|
99
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
100
|
+
fs.writeFileSync(target, Buffer.from(f.content, 'base64'));
|
|
101
|
+
}
|
|
102
|
+
const scripts = listDir(path.join(dir, 'scripts'));
|
|
103
|
+
const references = listDir(path.join(dir, 'reference'));
|
|
104
|
+
skills[parsedName] = { name: parsedName, description, mcpServers, content: body.trim(), category: 'user', dirPath: dir, scripts, references };
|
|
105
|
+
return skills[parsedName];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function update(name, description, content) {
|
|
109
|
+
const skill = skills[name];
|
|
110
|
+
if (!skill || skill.category !== 'user') return null;
|
|
111
|
+
const dir = path.join(USER_DIR, name);
|
|
112
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
113
|
+
const frontmatter = `---\nname: ${name}\ndescription: ${description}\n---\n\n${content}`;
|
|
114
|
+
fs.writeFileSync(path.join(dir, 'SKILL.md'), frontmatter, 'utf8');
|
|
115
|
+
skill.description = description;
|
|
116
|
+
skill.content = content;
|
|
117
|
+
return skill;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function remove(name) {
|
|
121
|
+
const skill = skills[name];
|
|
122
|
+
if (!skill || skill.category === 'system') return false;
|
|
123
|
+
if (skill.category === 'user') {
|
|
124
|
+
const userPath = path.join(USER_DIR, name);
|
|
125
|
+
if (fs.existsSync(userPath)) {
|
|
126
|
+
fs.rmSync(userPath, { recursive: true, force: true });
|
|
127
|
+
if (fs.existsSync(userPath)) return false; // 删除失败
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (skill.category === 'preset') {
|
|
131
|
+
const presetPath = path.join(PRESET_DIR, name, '.deleted');
|
|
132
|
+
try { fs.writeFileSync(presetPath, '', 'utf8'); } catch (err) { console.error(`[skill-store] 写入 .deleted 失败:`, err.message); }
|
|
133
|
+
if (!fs.existsSync(presetPath)) return false; // 写入失败(目录只读等)
|
|
134
|
+
}
|
|
135
|
+
delete skills[name];
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function init() {
|
|
140
|
+
skills = {};
|
|
141
|
+
loadFromDir(SYSTEM_DIR, 'system');
|
|
142
|
+
loadFromDir(PRESET_DIR, 'preset');
|
|
143
|
+
loadFromDir(USER_DIR, 'user');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function getAvailableForChat() {
|
|
147
|
+
return Object.values(skills).map(s => ({ name: s.name, description: s.description }));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = { init, list, get, create, createFromUpload, update, remove, getAvailableForChat };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "protocol-proxy",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.1",
|
|
4
4
|
"description": "OpenAI / Anthropic 协议转换透明代理",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"build": "pkg . --out-path=dist"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
29
30
|
"cors": "^2.8.5",
|
|
30
31
|
"express": "^4.19.2",
|
|
31
32
|
"ws": "^8.20.1"
|