coding-tool-x 3.2.0
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/CHANGELOG.md +599 -0
- package/LICENSE +21 -0
- package/README.md +439 -0
- package/bin/ctx.js +8 -0
- package/dist/web/assets/Analytics-DN_YsnkW.js +39 -0
- package/dist/web/assets/Analytics-DuYvId7u.css +1 -0
- package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
- package/dist/web/assets/ConfigTemplates-DpXIMy0p.js +1 -0
- package/dist/web/assets/Home-38JTUlYt.js +1 -0
- package/dist/web/assets/Home-CjupSEWE.css +1 -0
- package/dist/web/assets/PluginManager-CX2tgq2H.js +1 -0
- package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
- package/dist/web/assets/ProjectList-C1lDcsn6.js +1 -0
- package/dist/web/assets/ProjectList-oJIyIRkP.css +1 -0
- package/dist/web/assets/SessionList-C55tjV7i.css +1 -0
- package/dist/web/assets/SessionList-CZ7T6rVx.js +1 -0
- package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
- package/dist/web/assets/SkillManager-DLN9f79y.js +1 -0
- package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
- package/dist/web/assets/WorkspaceManager-DxlHZkpZ.js +1 -0
- package/dist/web/assets/icons-DRrXwWZi.js +1 -0
- package/dist/web/assets/index-CetESrXw.css +1 -0
- package/dist/web/assets/index-Cfvn-2Gb.js +2 -0
- package/dist/web/assets/markdown-BfC0goYb.css +10 -0
- package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
- package/dist/web/assets/naive-ui-DlpKk-8M.js +1 -0
- package/dist/web/assets/vendors-DMjSfzlv.js +7 -0
- package/dist/web/assets/vue-vendor-DET08QYg.js +45 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/index.html +20 -0
- package/dist/web/logo.png +0 -0
- package/docs/bannel.png +0 -0
- package/docs/home.png +0 -0
- package/docs/logo.png +0 -0
- package/docs/model-redirection.md +251 -0
- package/docs/multi-channel-load-balancing.md +249 -0
- package/package.json +80 -0
- package/src/commands/channels.js +551 -0
- package/src/commands/cli-type.js +101 -0
- package/src/commands/daemon.js +365 -0
- package/src/commands/doctor.js +333 -0
- package/src/commands/export-config.js +205 -0
- package/src/commands/list.js +222 -0
- package/src/commands/logs.js +261 -0
- package/src/commands/plugin.js +585 -0
- package/src/commands/port-config.js +135 -0
- package/src/commands/proxy-control.js +264 -0
- package/src/commands/proxy.js +152 -0
- package/src/commands/resume.js +137 -0
- package/src/commands/search.js +190 -0
- package/src/commands/security.js +37 -0
- package/src/commands/stats.js +398 -0
- package/src/commands/switch.js +48 -0
- package/src/commands/toggle-proxy.js +247 -0
- package/src/commands/ui.js +99 -0
- package/src/commands/update.js +97 -0
- package/src/commands/workspace.js +454 -0
- package/src/config/default.js +69 -0
- package/src/config/loader.js +149 -0
- package/src/config/model-metadata.js +167 -0
- package/src/config/model-metadata.json +125 -0
- package/src/config/model-pricing.js +35 -0
- package/src/config/paths.js +190 -0
- package/src/index.js +680 -0
- package/src/plugins/constants.js +15 -0
- package/src/plugins/event-bus.js +54 -0
- package/src/plugins/manifest-validator.js +129 -0
- package/src/plugins/plugin-api.js +128 -0
- package/src/plugins/plugin-installer.js +601 -0
- package/src/plugins/plugin-loader.js +229 -0
- package/src/plugins/plugin-manager.js +170 -0
- package/src/plugins/registry.js +152 -0
- package/src/plugins/schema/plugin-manifest.json +115 -0
- package/src/reset-config.js +94 -0
- package/src/server/api/agents.js +826 -0
- package/src/server/api/aliases.js +36 -0
- package/src/server/api/channels.js +368 -0
- package/src/server/api/claude-hooks.js +480 -0
- package/src/server/api/codex-channels.js +417 -0
- package/src/server/api/codex-projects.js +104 -0
- package/src/server/api/codex-proxy.js +195 -0
- package/src/server/api/codex-sessions.js +483 -0
- package/src/server/api/codex-statistics.js +57 -0
- package/src/server/api/commands.js +482 -0
- package/src/server/api/config-export.js +212 -0
- package/src/server/api/config-registry.js +357 -0
- package/src/server/api/config-sync.js +155 -0
- package/src/server/api/config-templates.js +248 -0
- package/src/server/api/config.js +521 -0
- package/src/server/api/convert.js +260 -0
- package/src/server/api/dashboard.js +142 -0
- package/src/server/api/env.js +144 -0
- package/src/server/api/favorites.js +77 -0
- package/src/server/api/gemini-channels.js +366 -0
- package/src/server/api/gemini-projects.js +91 -0
- package/src/server/api/gemini-proxy.js +173 -0
- package/src/server/api/gemini-sessions.js +376 -0
- package/src/server/api/gemini-statistics.js +57 -0
- package/src/server/api/health-check.js +31 -0
- package/src/server/api/mcp.js +399 -0
- package/src/server/api/opencode-channels.js +419 -0
- package/src/server/api/opencode-projects.js +99 -0
- package/src/server/api/opencode-proxy.js +207 -0
- package/src/server/api/opencode-sessions.js +327 -0
- package/src/server/api/opencode-statistics.js +57 -0
- package/src/server/api/plugins.js +463 -0
- package/src/server/api/pm2-autostart.js +269 -0
- package/src/server/api/projects.js +124 -0
- package/src/server/api/prompts.js +279 -0
- package/src/server/api/proxy.js +306 -0
- package/src/server/api/security.js +53 -0
- package/src/server/api/sessions.js +514 -0
- package/src/server/api/settings.js +142 -0
- package/src/server/api/skills.js +570 -0
- package/src/server/api/statistics.js +238 -0
- package/src/server/api/ui-config.js +64 -0
- package/src/server/api/workspaces.js +456 -0
- package/src/server/codex-proxy-server.js +681 -0
- package/src/server/dev-server.js +26 -0
- package/src/server/gemini-proxy-server.js +610 -0
- package/src/server/index.js +422 -0
- package/src/server/opencode-proxy-server.js +4771 -0
- package/src/server/proxy-server.js +669 -0
- package/src/server/services/agents-service.js +1137 -0
- package/src/server/services/alias.js +71 -0
- package/src/server/services/channel-health.js +234 -0
- package/src/server/services/channel-scheduler.js +240 -0
- package/src/server/services/channels.js +447 -0
- package/src/server/services/codex-channels.js +705 -0
- package/src/server/services/codex-config.js +90 -0
- package/src/server/services/codex-parser.js +322 -0
- package/src/server/services/codex-sessions.js +936 -0
- package/src/server/services/codex-settings-manager.js +619 -0
- package/src/server/services/codex-speed-test-template.json +24 -0
- package/src/server/services/codex-statistics-service.js +161 -0
- package/src/server/services/commands-service.js +574 -0
- package/src/server/services/config-export-service.js +1165 -0
- package/src/server/services/config-registry-service.js +828 -0
- package/src/server/services/config-sync-manager.js +941 -0
- package/src/server/services/config-sync-service.js +504 -0
- package/src/server/services/config-templates-service.js +913 -0
- package/src/server/services/enhanced-cache.js +196 -0
- package/src/server/services/env-checker.js +409 -0
- package/src/server/services/env-manager.js +436 -0
- package/src/server/services/favorites.js +165 -0
- package/src/server/services/format-converter.js +620 -0
- package/src/server/services/gemini-channels.js +459 -0
- package/src/server/services/gemini-config.js +73 -0
- package/src/server/services/gemini-sessions.js +689 -0
- package/src/server/services/gemini-settings-manager.js +263 -0
- package/src/server/services/gemini-statistics-service.js +157 -0
- package/src/server/services/health-check.js +85 -0
- package/src/server/services/mcp-client.js +790 -0
- package/src/server/services/mcp-service.js +1732 -0
- package/src/server/services/model-detector.js +1245 -0
- package/src/server/services/network-access.js +80 -0
- package/src/server/services/opencode-channels.js +366 -0
- package/src/server/services/opencode-gateway-adapters.js +1168 -0
- package/src/server/services/opencode-gateway-converter.js +639 -0
- package/src/server/services/opencode-sessions.js +931 -0
- package/src/server/services/opencode-settings-manager.js +478 -0
- package/src/server/services/opencode-statistics-service.js +161 -0
- package/src/server/services/plugins-service.js +1268 -0
- package/src/server/services/prompts-service.js +534 -0
- package/src/server/services/proxy-runtime.js +79 -0
- package/src/server/services/repo-scanner-base.js +708 -0
- package/src/server/services/request-logger.js +130 -0
- package/src/server/services/response-decoder.js +21 -0
- package/src/server/services/security-config.js +131 -0
- package/src/server/services/session-cache.js +127 -0
- package/src/server/services/session-converter.js +577 -0
- package/src/server/services/sessions.js +900 -0
- package/src/server/services/settings-manager.js +163 -0
- package/src/server/services/skill-service.js +1482 -0
- package/src/server/services/speed-test.js +1146 -0
- package/src/server/services/statistics-service.js +1043 -0
- package/src/server/services/ui-config.js +132 -0
- package/src/server/services/workspace-service.js +830 -0
- package/src/server/utils/pricing.js +73 -0
- package/src/server/websocket-server.js +513 -0
- package/src/ui/menu.js +139 -0
- package/src/ui/prompts.js +100 -0
- package/src/utils/format.js +43 -0
- package/src/utils/port-helper.js +108 -0
- package/src/utils/session.js +240 -0
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 格式转换服务
|
|
3
|
+
*
|
|
4
|
+
* 支持 Claude Code ↔ Codex CLI 格式互转
|
|
5
|
+
* - Skills: SKILL.md 格式转换
|
|
6
|
+
* - Commands/Prompts: 命令/提示格式转换
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Codex CLI 限制
|
|
10
|
+
const CODEX_LIMITS = {
|
|
11
|
+
skillName: 100,
|
|
12
|
+
skillDescription: 500
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 解析 YAML frontmatter
|
|
17
|
+
* 支持基本的 YAML 解析和嵌套 metadata 对象
|
|
18
|
+
*/
|
|
19
|
+
function parseFrontmatter(content) {
|
|
20
|
+
const result = {
|
|
21
|
+
frontmatter: {},
|
|
22
|
+
body: content
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// 移除 BOM
|
|
26
|
+
content = content.trim().replace(/^\uFEFF/, '');
|
|
27
|
+
|
|
28
|
+
// 解析 YAML frontmatter
|
|
29
|
+
const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/);
|
|
30
|
+
if (!match) {
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const frontmatterText = match[1];
|
|
35
|
+
result.body = match[2].trim();
|
|
36
|
+
|
|
37
|
+
// 解析 YAML(支持嵌套对象 + description 多行/块标量)
|
|
38
|
+
const lines = frontmatterText.split('\n');
|
|
39
|
+
let currentParent = null;
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < lines.length; i++) {
|
|
42
|
+
const line = lines[i];
|
|
43
|
+
const indent = line.match(/^(\s*)/)?.[1].length || 0;
|
|
44
|
+
const trimmed = line.trim();
|
|
45
|
+
if (!trimmed) continue;
|
|
46
|
+
|
|
47
|
+
const colonIndex = line.indexOf(':');
|
|
48
|
+
if (colonIndex === -1) continue;
|
|
49
|
+
|
|
50
|
+
const key = line.slice(0, colonIndex).trim();
|
|
51
|
+
const rawValue = line.slice(colonIndex + 1).trim();
|
|
52
|
+
|
|
53
|
+
// 处理嵌套属性
|
|
54
|
+
if (indent > 0 && currentParent && typeof result.frontmatter[currentParent] === 'object') {
|
|
55
|
+
result.frontmatter[currentParent][key] = unquoteYamlValue(rawValue);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
currentParent = null;
|
|
60
|
+
|
|
61
|
+
// YAML block scalar: >, >-, |, |- 等
|
|
62
|
+
if (/^[>|][+-]?$/.test(rawValue)) {
|
|
63
|
+
const parsed = parseMultilineYamlValue(lines, i + 1, indent, rawValue);
|
|
64
|
+
result.frontmatter[key] = parsed.value;
|
|
65
|
+
i = parsed.nextIndex - 1;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 空值:可能是嵌套对象,也可能是多行文本(Gemini description 常见)
|
|
70
|
+
if (!rawValue && indent === 0) {
|
|
71
|
+
const nextInfo = findNextNonEmptyLine(lines, i + 1);
|
|
72
|
+
if (nextInfo && nextInfo.indent > indent) {
|
|
73
|
+
if (shouldParseAsMultilineText(key, nextInfo.trimmed)) {
|
|
74
|
+
const parsed = parseMultilineYamlValue(lines, i + 1, indent);
|
|
75
|
+
result.frontmatter[key] = parsed.value;
|
|
76
|
+
i = parsed.nextIndex - 1;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
currentParent = key;
|
|
81
|
+
result.frontmatter[key] = {};
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
result.frontmatter[key] = unquoteYamlValue(rawValue);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function unquoteYamlValue(value) {
|
|
93
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
94
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
95
|
+
return value.slice(1, -1);
|
|
96
|
+
}
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function findNextNonEmptyLine(lines, startIndex) {
|
|
101
|
+
for (let i = startIndex; i < lines.length; i++) {
|
|
102
|
+
const line = lines[i];
|
|
103
|
+
const trimmed = line.trim();
|
|
104
|
+
if (!trimmed) continue;
|
|
105
|
+
return {
|
|
106
|
+
index: i,
|
|
107
|
+
indent: line.match(/^(\s*)/)?.[1].length || 0,
|
|
108
|
+
trimmed
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function shouldParseAsMultilineText(key, nextTrimmedLine) {
|
|
115
|
+
// Gemini skill frontmatter 常见:
|
|
116
|
+
// description:
|
|
117
|
+
// 多行描述...
|
|
118
|
+
if (key === 'description') {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 嵌套对象一般是 "childKey: value" 结构
|
|
123
|
+
if (/^[A-Za-z0-9_-]+\s*:/.test(nextTrimmedLine)) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function parseMultilineYamlValue(lines, startIndex, parentIndent, blockStyle = null) {
|
|
131
|
+
let endIndex = startIndex;
|
|
132
|
+
const collected = [];
|
|
133
|
+
|
|
134
|
+
while (endIndex < lines.length) {
|
|
135
|
+
const line = lines[endIndex];
|
|
136
|
+
const indent = line.match(/^(\s*)/)?.[1].length || 0;
|
|
137
|
+
const trimmed = line.trim();
|
|
138
|
+
|
|
139
|
+
if (!trimmed) {
|
|
140
|
+
collected.push('');
|
|
141
|
+
endIndex++;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (indent <= parentIndent) {
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
collected.push(line);
|
|
150
|
+
endIndex++;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (collected.length === 0) {
|
|
154
|
+
return { value: '', nextIndex: endIndex };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 去掉公共缩进
|
|
158
|
+
const baseIndent = collected
|
|
159
|
+
.filter(line => line.trim() !== '')
|
|
160
|
+
.reduce((min, line) => {
|
|
161
|
+
const indent = line.match(/^(\s*)/)?.[1].length || 0;
|
|
162
|
+
return Math.min(min, indent);
|
|
163
|
+
}, Infinity);
|
|
164
|
+
|
|
165
|
+
const normalizedLines = collected.map(line => {
|
|
166
|
+
if (!line) return '';
|
|
167
|
+
return line.slice(Math.min(baseIndent, line.length));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const style = blockStyle ? blockStyle[0] : null;
|
|
171
|
+
const chomp = blockStyle && blockStyle.length > 1 ? blockStyle[1] : null;
|
|
172
|
+
let value = '';
|
|
173
|
+
|
|
174
|
+
if (style === '>') {
|
|
175
|
+
value = foldYamlLines(normalizedLines);
|
|
176
|
+
} else {
|
|
177
|
+
value = normalizedLines.join('\n');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (chomp !== '+') {
|
|
181
|
+
value = value.replace(/\n+$/g, '');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return { value: value.trim(), nextIndex: endIndex };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function foldYamlLines(lines) {
|
|
188
|
+
if (lines.length === 0) return '';
|
|
189
|
+
|
|
190
|
+
let output = lines[0];
|
|
191
|
+
for (let i = 1; i < lines.length; i++) {
|
|
192
|
+
if (lines[i - 1] === '' || lines[i] === '') {
|
|
193
|
+
output += '\n' + lines[i];
|
|
194
|
+
} else {
|
|
195
|
+
output += ' ' + lines[i];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return output;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* 生成 YAML frontmatter 字符串
|
|
204
|
+
*/
|
|
205
|
+
function generateFrontmatter(data, format = 'claude') {
|
|
206
|
+
const lines = ['---'];
|
|
207
|
+
|
|
208
|
+
if (format === 'codex') {
|
|
209
|
+
// Codex 格式: name, description 必须,metadata 可选
|
|
210
|
+
if (data.name) {
|
|
211
|
+
lines.push(`name: "${escapeYamlString(data.name)}"`);
|
|
212
|
+
}
|
|
213
|
+
if (data.description) {
|
|
214
|
+
lines.push(`description: "${escapeYamlString(data.description)}"`);
|
|
215
|
+
}
|
|
216
|
+
if (data.metadata && Object.keys(data.metadata).length > 0) {
|
|
217
|
+
lines.push('metadata:');
|
|
218
|
+
for (const [key, value] of Object.entries(data.metadata)) {
|
|
219
|
+
lines.push(` ${key}: "${escapeYamlString(value)}"`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Codex commands/prompts 特有字段
|
|
223
|
+
if (data['argument-hint']) {
|
|
224
|
+
lines.push(`argument-hint: ${data['argument-hint']}`);
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
// Claude Code 格式
|
|
228
|
+
if (data.name) {
|
|
229
|
+
lines.push(`name: "${escapeYamlString(data.name)}"`);
|
|
230
|
+
}
|
|
231
|
+
if (data.description) {
|
|
232
|
+
lines.push(`description: "${escapeYamlString(data.description)}"`);
|
|
233
|
+
}
|
|
234
|
+
if (data.license) {
|
|
235
|
+
lines.push(`license: "${escapeYamlString(data.license)}"`);
|
|
236
|
+
}
|
|
237
|
+
// Claude Code commands 特有字段
|
|
238
|
+
if (data['allowed-tools']) {
|
|
239
|
+
lines.push(`allowed-tools: ${data['allowed-tools']}`);
|
|
240
|
+
}
|
|
241
|
+
if (data['argument-hint']) {
|
|
242
|
+
lines.push(`argument-hint: ${data['argument-hint']}`);
|
|
243
|
+
}
|
|
244
|
+
if (data.model) {
|
|
245
|
+
lines.push(`model: ${data.model}`);
|
|
246
|
+
}
|
|
247
|
+
if (data.context) {
|
|
248
|
+
lines.push(`context: ${data.context}`);
|
|
249
|
+
}
|
|
250
|
+
if (data.agent) {
|
|
251
|
+
lines.push(`agent: ${data.agent}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
lines.push('---');
|
|
256
|
+
return lines.join('\n');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* 转义 YAML 字符串中的特殊字符
|
|
261
|
+
*/
|
|
262
|
+
function escapeYamlString(str) {
|
|
263
|
+
if (!str) return '';
|
|
264
|
+
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 检测 Skill 格式
|
|
269
|
+
* @returns {'claude' | 'codex' | 'unknown'}
|
|
270
|
+
*/
|
|
271
|
+
function detectSkillFormat(content) {
|
|
272
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
273
|
+
|
|
274
|
+
// Codex 特征: 有 metadata 对象
|
|
275
|
+
if (frontmatter.metadata && typeof frontmatter.metadata === 'object') {
|
|
276
|
+
return 'codex';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Claude Code 特征: 有 allowed-tools 或 license
|
|
280
|
+
if (frontmatter['allowed-tools'] || frontmatter.license) {
|
|
281
|
+
return 'claude';
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// 两者都有 name 和 description,无法区分时默认 claude
|
|
285
|
+
if (frontmatter.name && frontmatter.description) {
|
|
286
|
+
return 'claude';
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return 'unknown';
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 检测 Command/Prompt 格式
|
|
294
|
+
* @returns {'claude' | 'codex' | 'unknown'}
|
|
295
|
+
*/
|
|
296
|
+
function detectCommandFormat(content) {
|
|
297
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
298
|
+
|
|
299
|
+
// Claude Code 特征: allowed-tools, model, context, agent, hooks
|
|
300
|
+
if (frontmatter['allowed-tools'] || frontmatter.model ||
|
|
301
|
+
frontmatter.context || frontmatter.agent || frontmatter.hooks) {
|
|
302
|
+
return 'claude';
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Codex 特征: 只有 description 和 argument-hint
|
|
306
|
+
if (frontmatter.description && !frontmatter['allowed-tools']) {
|
|
307
|
+
return 'codex';
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return 'unknown';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* 转换 Skill: Claude Code → Codex CLI
|
|
315
|
+
*/
|
|
316
|
+
function convertSkillToCodex(claudeContent, options = {}) {
|
|
317
|
+
const { frontmatter, body } = parseFrontmatter(claudeContent);
|
|
318
|
+
const warnings = [];
|
|
319
|
+
|
|
320
|
+
// 处理字段
|
|
321
|
+
const codexData = {
|
|
322
|
+
name: frontmatter.name || '',
|
|
323
|
+
description: frontmatter.description || '',
|
|
324
|
+
metadata: {}
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// 检查并截断 name
|
|
328
|
+
if (codexData.name.length > CODEX_LIMITS.skillName) {
|
|
329
|
+
warnings.push(`name 超过 ${CODEX_LIMITS.skillName} 字符,已截断`);
|
|
330
|
+
codexData.name = codexData.name.slice(0, CODEX_LIMITS.skillName);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// 检查并截断 description
|
|
334
|
+
if (codexData.description.length > CODEX_LIMITS.skillDescription) {
|
|
335
|
+
warnings.push(`description 超过 ${CODEX_LIMITS.skillDescription} 字符,已截断`);
|
|
336
|
+
codexData.description = codexData.description.slice(0, CODEX_LIMITS.skillDescription);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// 不支持的字段警告
|
|
340
|
+
if (frontmatter['allowed-tools']) {
|
|
341
|
+
warnings.push('allowed-tools 字段在 Codex 中不支持,已忽略');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 生成 Codex 格式内容
|
|
345
|
+
const codexFrontmatter = generateFrontmatter(codexData, 'codex');
|
|
346
|
+
const codexContent = codexFrontmatter + '\n\n' + body;
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
content: codexContent,
|
|
350
|
+
warnings,
|
|
351
|
+
format: 'codex'
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* 转换 Skill: Codex CLI → Claude Code
|
|
357
|
+
*/
|
|
358
|
+
function convertSkillToClaude(codexContent, options = {}) {
|
|
359
|
+
const { frontmatter, body } = parseFrontmatter(codexContent);
|
|
360
|
+
const warnings = [];
|
|
361
|
+
|
|
362
|
+
// 处理字段
|
|
363
|
+
const claudeData = {
|
|
364
|
+
name: frontmatter.name || '',
|
|
365
|
+
description: frontmatter.description || ''
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// 保留 metadata.short-description(如果有)作为注释
|
|
369
|
+
if (frontmatter.metadata && frontmatter.metadata['short-description']) {
|
|
370
|
+
// 可以选择将其添加到 description 或作为单独字段
|
|
371
|
+
warnings.push(`metadata.short-description 已保留: "${frontmatter.metadata['short-description']}"`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// 生成 Claude Code 格式内容
|
|
375
|
+
const claudeFrontmatter = generateFrontmatter(claudeData, 'claude');
|
|
376
|
+
const claudeContent = claudeFrontmatter + '\n\n' + body;
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
content: claudeContent,
|
|
380
|
+
warnings,
|
|
381
|
+
format: 'claude'
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* 转换 Command: Claude Code → Codex CLI (Custom Prompt)
|
|
387
|
+
*/
|
|
388
|
+
function convertCommandToCodex(claudeContent, options = {}) {
|
|
389
|
+
const { frontmatter, body } = parseFrontmatter(claudeContent);
|
|
390
|
+
const warnings = [];
|
|
391
|
+
|
|
392
|
+
// 处理字段
|
|
393
|
+
const codexData = {
|
|
394
|
+
description: frontmatter.description || ''
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// argument-hint 两者都支持
|
|
398
|
+
if (frontmatter['argument-hint']) {
|
|
399
|
+
codexData['argument-hint'] = frontmatter['argument-hint'];
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// 不支持的字段警告
|
|
403
|
+
const unsupportedFields = ['allowed-tools', 'model', 'context', 'agent', 'hooks'];
|
|
404
|
+
for (const field of unsupportedFields) {
|
|
405
|
+
if (frontmatter[field]) {
|
|
406
|
+
warnings.push(`${field} 字段在 Codex 中不支持,已忽略`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// 检查 body 中的 Claude Code 特有语法
|
|
411
|
+
let processedBody = body;
|
|
412
|
+
|
|
413
|
+
// 检测 Bash 执行语法 !`command`
|
|
414
|
+
if (/!\`[^`]+\`/.test(body)) {
|
|
415
|
+
warnings.push('Bash 执行语法 !`command` 在 Codex 中不支持,请手动替换');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// 检测文件引用语法 @filepath
|
|
419
|
+
if (/@[^\s]+/.test(body)) {
|
|
420
|
+
warnings.push('文件引用语法 @filepath 在 Codex 中不支持,请手动替换');
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// 生成 Codex 格式内容
|
|
424
|
+
let codexContent = '';
|
|
425
|
+
if (codexData.description || codexData['argument-hint']) {
|
|
426
|
+
codexContent = generateFrontmatter(codexData, 'codex') + '\n\n';
|
|
427
|
+
}
|
|
428
|
+
codexContent += processedBody;
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
content: codexContent,
|
|
432
|
+
warnings,
|
|
433
|
+
format: 'codex'
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* 转换 Command: Codex CLI (Custom Prompt) → Claude Code
|
|
439
|
+
*/
|
|
440
|
+
function convertCommandToClaude(codexContent, options = {}) {
|
|
441
|
+
const { frontmatter, body } = parseFrontmatter(codexContent);
|
|
442
|
+
const warnings = [];
|
|
443
|
+
|
|
444
|
+
// 处理字段
|
|
445
|
+
const claudeData = {
|
|
446
|
+
description: frontmatter.description || ''
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// argument-hint 两者都支持
|
|
450
|
+
if (frontmatter['argument-hint']) {
|
|
451
|
+
claudeData['argument-hint'] = frontmatter['argument-hint'];
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Codex 命名参数 $KEY 在 Claude Code 中也支持,无需转换
|
|
455
|
+
|
|
456
|
+
// 生成 Claude Code 格式内容
|
|
457
|
+
let claudeContent = '';
|
|
458
|
+
if (claudeData.description || claudeData['argument-hint']) {
|
|
459
|
+
claudeContent = generateFrontmatter(claudeData, 'claude') + '\n\n';
|
|
460
|
+
}
|
|
461
|
+
claudeContent += body;
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
content: claudeContent,
|
|
465
|
+
warnings,
|
|
466
|
+
format: 'claude'
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* 批量转换 Skills
|
|
472
|
+
*/
|
|
473
|
+
function convertSkillsBatch(skills, targetFormat) {
|
|
474
|
+
const results = [];
|
|
475
|
+
|
|
476
|
+
for (const skill of skills) {
|
|
477
|
+
try {
|
|
478
|
+
const converted = targetFormat === 'codex'
|
|
479
|
+
? convertSkillToCodex(skill.content)
|
|
480
|
+
: convertSkillToClaude(skill.content);
|
|
481
|
+
|
|
482
|
+
results.push({
|
|
483
|
+
name: skill.name,
|
|
484
|
+
success: true,
|
|
485
|
+
...converted
|
|
486
|
+
});
|
|
487
|
+
} catch (err) {
|
|
488
|
+
results.push({
|
|
489
|
+
name: skill.name,
|
|
490
|
+
success: false,
|
|
491
|
+
error: err.message
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return results;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* 批量转换 Commands
|
|
501
|
+
*/
|
|
502
|
+
function convertCommandsBatch(commands, targetFormat) {
|
|
503
|
+
const results = [];
|
|
504
|
+
|
|
505
|
+
for (const command of commands) {
|
|
506
|
+
try {
|
|
507
|
+
const converted = targetFormat === 'codex'
|
|
508
|
+
? convertCommandToCodex(command.content)
|
|
509
|
+
: convertCommandToClaude(command.content);
|
|
510
|
+
|
|
511
|
+
results.push({
|
|
512
|
+
name: command.name,
|
|
513
|
+
success: true,
|
|
514
|
+
...converted
|
|
515
|
+
});
|
|
516
|
+
} catch (err) {
|
|
517
|
+
results.push({
|
|
518
|
+
name: command.name,
|
|
519
|
+
success: false,
|
|
520
|
+
error: err.message
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
return results;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* 解析 Skill 内容(支持双格式)
|
|
530
|
+
*/
|
|
531
|
+
function parseSkillContent(content) {
|
|
532
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
533
|
+
const format = detectSkillFormat(content);
|
|
534
|
+
|
|
535
|
+
const result = {
|
|
536
|
+
name: frontmatter.name || '',
|
|
537
|
+
description: frontmatter.description || '',
|
|
538
|
+
body,
|
|
539
|
+
fullContent: content,
|
|
540
|
+
format
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
// 处理 Codex 特有字段
|
|
544
|
+
if (frontmatter.metadata) {
|
|
545
|
+
result.metadata = frontmatter.metadata;
|
|
546
|
+
if (frontmatter.metadata['short-description']) {
|
|
547
|
+
result.shortDescription = frontmatter.metadata['short-description'];
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// 处理 Claude Code 特有字段
|
|
552
|
+
if (frontmatter['allowed-tools']) {
|
|
553
|
+
result.allowedTools = frontmatter['allowed-tools'];
|
|
554
|
+
}
|
|
555
|
+
if (frontmatter.license) {
|
|
556
|
+
result.license = frontmatter.license;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return result;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* 解析 Command 内容(支持双格式)
|
|
564
|
+
*/
|
|
565
|
+
function parseCommandContent(content) {
|
|
566
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
567
|
+
const format = detectCommandFormat(content);
|
|
568
|
+
|
|
569
|
+
const result = {
|
|
570
|
+
description: frontmatter.description || '',
|
|
571
|
+
argumentHint: frontmatter['argument-hint'] || '',
|
|
572
|
+
body,
|
|
573
|
+
fullContent: content,
|
|
574
|
+
format
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
// 处理 Claude Code 特有字段
|
|
578
|
+
if (frontmatter['allowed-tools']) {
|
|
579
|
+
result.allowedTools = frontmatter['allowed-tools'];
|
|
580
|
+
}
|
|
581
|
+
if (frontmatter.model) {
|
|
582
|
+
result.model = frontmatter.model;
|
|
583
|
+
}
|
|
584
|
+
if (frontmatter.context) {
|
|
585
|
+
result.context = frontmatter.context;
|
|
586
|
+
}
|
|
587
|
+
if (frontmatter.agent) {
|
|
588
|
+
result.agent = frontmatter.agent;
|
|
589
|
+
}
|
|
590
|
+
if (frontmatter.hooks) {
|
|
591
|
+
result.hooks = frontmatter.hooks;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return result;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
module.exports = {
|
|
598
|
+
// 格式检测
|
|
599
|
+
detectSkillFormat,
|
|
600
|
+
detectCommandFormat,
|
|
601
|
+
|
|
602
|
+
// Skills 转换
|
|
603
|
+
convertSkillToCodex,
|
|
604
|
+
convertSkillToClaude,
|
|
605
|
+
convertSkillsBatch,
|
|
606
|
+
|
|
607
|
+
// Commands 转换
|
|
608
|
+
convertCommandToCodex,
|
|
609
|
+
convertCommandToClaude,
|
|
610
|
+
convertCommandsBatch,
|
|
611
|
+
|
|
612
|
+
// 通用解析(支持双格式)
|
|
613
|
+
parseSkillContent,
|
|
614
|
+
parseCommandContent,
|
|
615
|
+
parseFrontmatter,
|
|
616
|
+
generateFrontmatter,
|
|
617
|
+
|
|
618
|
+
// 常量
|
|
619
|
+
CODEX_LIMITS
|
|
620
|
+
};
|