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.
Files changed (185) hide show
  1. package/CHANGELOG.md +599 -0
  2. package/LICENSE +21 -0
  3. package/README.md +439 -0
  4. package/bin/ctx.js +8 -0
  5. package/dist/web/assets/Analytics-DN_YsnkW.js +39 -0
  6. package/dist/web/assets/Analytics-DuYvId7u.css +1 -0
  7. package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
  8. package/dist/web/assets/ConfigTemplates-DpXIMy0p.js +1 -0
  9. package/dist/web/assets/Home-38JTUlYt.js +1 -0
  10. package/dist/web/assets/Home-CjupSEWE.css +1 -0
  11. package/dist/web/assets/PluginManager-CX2tgq2H.js +1 -0
  12. package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
  13. package/dist/web/assets/ProjectList-C1lDcsn6.js +1 -0
  14. package/dist/web/assets/ProjectList-oJIyIRkP.css +1 -0
  15. package/dist/web/assets/SessionList-C55tjV7i.css +1 -0
  16. package/dist/web/assets/SessionList-CZ7T6rVx.js +1 -0
  17. package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
  18. package/dist/web/assets/SkillManager-DLN9f79y.js +1 -0
  19. package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
  20. package/dist/web/assets/WorkspaceManager-DxlHZkpZ.js +1 -0
  21. package/dist/web/assets/icons-DRrXwWZi.js +1 -0
  22. package/dist/web/assets/index-CetESrXw.css +1 -0
  23. package/dist/web/assets/index-Cfvn-2Gb.js +2 -0
  24. package/dist/web/assets/markdown-BfC0goYb.css +10 -0
  25. package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
  26. package/dist/web/assets/naive-ui-DlpKk-8M.js +1 -0
  27. package/dist/web/assets/vendors-DMjSfzlv.js +7 -0
  28. package/dist/web/assets/vue-vendor-DET08QYg.js +45 -0
  29. package/dist/web/favicon.ico +0 -0
  30. package/dist/web/index.html +20 -0
  31. package/dist/web/logo.png +0 -0
  32. package/docs/bannel.png +0 -0
  33. package/docs/home.png +0 -0
  34. package/docs/logo.png +0 -0
  35. package/docs/model-redirection.md +251 -0
  36. package/docs/multi-channel-load-balancing.md +249 -0
  37. package/package.json +80 -0
  38. package/src/commands/channels.js +551 -0
  39. package/src/commands/cli-type.js +101 -0
  40. package/src/commands/daemon.js +365 -0
  41. package/src/commands/doctor.js +333 -0
  42. package/src/commands/export-config.js +205 -0
  43. package/src/commands/list.js +222 -0
  44. package/src/commands/logs.js +261 -0
  45. package/src/commands/plugin.js +585 -0
  46. package/src/commands/port-config.js +135 -0
  47. package/src/commands/proxy-control.js +264 -0
  48. package/src/commands/proxy.js +152 -0
  49. package/src/commands/resume.js +137 -0
  50. package/src/commands/search.js +190 -0
  51. package/src/commands/security.js +37 -0
  52. package/src/commands/stats.js +398 -0
  53. package/src/commands/switch.js +48 -0
  54. package/src/commands/toggle-proxy.js +247 -0
  55. package/src/commands/ui.js +99 -0
  56. package/src/commands/update.js +97 -0
  57. package/src/commands/workspace.js +454 -0
  58. package/src/config/default.js +69 -0
  59. package/src/config/loader.js +149 -0
  60. package/src/config/model-metadata.js +167 -0
  61. package/src/config/model-metadata.json +125 -0
  62. package/src/config/model-pricing.js +35 -0
  63. package/src/config/paths.js +190 -0
  64. package/src/index.js +680 -0
  65. package/src/plugins/constants.js +15 -0
  66. package/src/plugins/event-bus.js +54 -0
  67. package/src/plugins/manifest-validator.js +129 -0
  68. package/src/plugins/plugin-api.js +128 -0
  69. package/src/plugins/plugin-installer.js +601 -0
  70. package/src/plugins/plugin-loader.js +229 -0
  71. package/src/plugins/plugin-manager.js +170 -0
  72. package/src/plugins/registry.js +152 -0
  73. package/src/plugins/schema/plugin-manifest.json +115 -0
  74. package/src/reset-config.js +94 -0
  75. package/src/server/api/agents.js +826 -0
  76. package/src/server/api/aliases.js +36 -0
  77. package/src/server/api/channels.js +368 -0
  78. package/src/server/api/claude-hooks.js +480 -0
  79. package/src/server/api/codex-channels.js +417 -0
  80. package/src/server/api/codex-projects.js +104 -0
  81. package/src/server/api/codex-proxy.js +195 -0
  82. package/src/server/api/codex-sessions.js +483 -0
  83. package/src/server/api/codex-statistics.js +57 -0
  84. package/src/server/api/commands.js +482 -0
  85. package/src/server/api/config-export.js +212 -0
  86. package/src/server/api/config-registry.js +357 -0
  87. package/src/server/api/config-sync.js +155 -0
  88. package/src/server/api/config-templates.js +248 -0
  89. package/src/server/api/config.js +521 -0
  90. package/src/server/api/convert.js +260 -0
  91. package/src/server/api/dashboard.js +142 -0
  92. package/src/server/api/env.js +144 -0
  93. package/src/server/api/favorites.js +77 -0
  94. package/src/server/api/gemini-channels.js +366 -0
  95. package/src/server/api/gemini-projects.js +91 -0
  96. package/src/server/api/gemini-proxy.js +173 -0
  97. package/src/server/api/gemini-sessions.js +376 -0
  98. package/src/server/api/gemini-statistics.js +57 -0
  99. package/src/server/api/health-check.js +31 -0
  100. package/src/server/api/mcp.js +399 -0
  101. package/src/server/api/opencode-channels.js +419 -0
  102. package/src/server/api/opencode-projects.js +99 -0
  103. package/src/server/api/opencode-proxy.js +207 -0
  104. package/src/server/api/opencode-sessions.js +327 -0
  105. package/src/server/api/opencode-statistics.js +57 -0
  106. package/src/server/api/plugins.js +463 -0
  107. package/src/server/api/pm2-autostart.js +269 -0
  108. package/src/server/api/projects.js +124 -0
  109. package/src/server/api/prompts.js +279 -0
  110. package/src/server/api/proxy.js +306 -0
  111. package/src/server/api/security.js +53 -0
  112. package/src/server/api/sessions.js +514 -0
  113. package/src/server/api/settings.js +142 -0
  114. package/src/server/api/skills.js +570 -0
  115. package/src/server/api/statistics.js +238 -0
  116. package/src/server/api/ui-config.js +64 -0
  117. package/src/server/api/workspaces.js +456 -0
  118. package/src/server/codex-proxy-server.js +681 -0
  119. package/src/server/dev-server.js +26 -0
  120. package/src/server/gemini-proxy-server.js +610 -0
  121. package/src/server/index.js +422 -0
  122. package/src/server/opencode-proxy-server.js +4771 -0
  123. package/src/server/proxy-server.js +669 -0
  124. package/src/server/services/agents-service.js +1137 -0
  125. package/src/server/services/alias.js +71 -0
  126. package/src/server/services/channel-health.js +234 -0
  127. package/src/server/services/channel-scheduler.js +240 -0
  128. package/src/server/services/channels.js +447 -0
  129. package/src/server/services/codex-channels.js +705 -0
  130. package/src/server/services/codex-config.js +90 -0
  131. package/src/server/services/codex-parser.js +322 -0
  132. package/src/server/services/codex-sessions.js +936 -0
  133. package/src/server/services/codex-settings-manager.js +619 -0
  134. package/src/server/services/codex-speed-test-template.json +24 -0
  135. package/src/server/services/codex-statistics-service.js +161 -0
  136. package/src/server/services/commands-service.js +574 -0
  137. package/src/server/services/config-export-service.js +1165 -0
  138. package/src/server/services/config-registry-service.js +828 -0
  139. package/src/server/services/config-sync-manager.js +941 -0
  140. package/src/server/services/config-sync-service.js +504 -0
  141. package/src/server/services/config-templates-service.js +913 -0
  142. package/src/server/services/enhanced-cache.js +196 -0
  143. package/src/server/services/env-checker.js +409 -0
  144. package/src/server/services/env-manager.js +436 -0
  145. package/src/server/services/favorites.js +165 -0
  146. package/src/server/services/format-converter.js +620 -0
  147. package/src/server/services/gemini-channels.js +459 -0
  148. package/src/server/services/gemini-config.js +73 -0
  149. package/src/server/services/gemini-sessions.js +689 -0
  150. package/src/server/services/gemini-settings-manager.js +263 -0
  151. package/src/server/services/gemini-statistics-service.js +157 -0
  152. package/src/server/services/health-check.js +85 -0
  153. package/src/server/services/mcp-client.js +790 -0
  154. package/src/server/services/mcp-service.js +1732 -0
  155. package/src/server/services/model-detector.js +1245 -0
  156. package/src/server/services/network-access.js +80 -0
  157. package/src/server/services/opencode-channels.js +366 -0
  158. package/src/server/services/opencode-gateway-adapters.js +1168 -0
  159. package/src/server/services/opencode-gateway-converter.js +639 -0
  160. package/src/server/services/opencode-sessions.js +931 -0
  161. package/src/server/services/opencode-settings-manager.js +478 -0
  162. package/src/server/services/opencode-statistics-service.js +161 -0
  163. package/src/server/services/plugins-service.js +1268 -0
  164. package/src/server/services/prompts-service.js +534 -0
  165. package/src/server/services/proxy-runtime.js +79 -0
  166. package/src/server/services/repo-scanner-base.js +708 -0
  167. package/src/server/services/request-logger.js +130 -0
  168. package/src/server/services/response-decoder.js +21 -0
  169. package/src/server/services/security-config.js +131 -0
  170. package/src/server/services/session-cache.js +127 -0
  171. package/src/server/services/session-converter.js +577 -0
  172. package/src/server/services/sessions.js +900 -0
  173. package/src/server/services/settings-manager.js +163 -0
  174. package/src/server/services/skill-service.js +1482 -0
  175. package/src/server/services/speed-test.js +1146 -0
  176. package/src/server/services/statistics-service.js +1043 -0
  177. package/src/server/services/ui-config.js +132 -0
  178. package/src/server/services/workspace-service.js +830 -0
  179. package/src/server/utils/pricing.js +73 -0
  180. package/src/server/websocket-server.js +513 -0
  181. package/src/ui/menu.js +139 -0
  182. package/src/ui/prompts.js +100 -0
  183. package/src/utils/format.js +43 -0
  184. package/src/utils/port-helper.js +108 -0
  185. 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
+ };