@adversity/coding-tool-x 2.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 (125) hide show
  1. package/CHANGELOG.md +333 -0
  2. package/LICENSE +21 -0
  3. package/README.md +404 -0
  4. package/bin/ctx.js +8 -0
  5. package/dist/web/assets/index-D1AYlFLZ.js +3220 -0
  6. package/dist/web/assets/index-aL3cKxSK.css +41 -0
  7. package/dist/web/favicon.ico +0 -0
  8. package/dist/web/index.html +14 -0
  9. package/dist/web/logo.png +0 -0
  10. package/docs/CHANGELOG.md +582 -0
  11. package/docs/DIRECTORY_MIGRATION.md +112 -0
  12. package/docs/PROJECT_STRUCTURE.md +396 -0
  13. package/docs/bannel.png +0 -0
  14. package/docs/home.png +0 -0
  15. package/docs/logo.png +0 -0
  16. package/docs/multi-channel-load-balancing.md +249 -0
  17. package/package.json +73 -0
  18. package/src/commands/channels.js +504 -0
  19. package/src/commands/cli-type.js +99 -0
  20. package/src/commands/daemon.js +286 -0
  21. package/src/commands/doctor.js +332 -0
  22. package/src/commands/list.js +222 -0
  23. package/src/commands/logs.js +259 -0
  24. package/src/commands/port-config.js +115 -0
  25. package/src/commands/proxy-control.js +258 -0
  26. package/src/commands/proxy.js +152 -0
  27. package/src/commands/resume.js +137 -0
  28. package/src/commands/search.js +190 -0
  29. package/src/commands/stats.js +224 -0
  30. package/src/commands/switch.js +48 -0
  31. package/src/commands/toggle-proxy.js +222 -0
  32. package/src/commands/ui.js +92 -0
  33. package/src/commands/workspace.js +454 -0
  34. package/src/config/default.js +40 -0
  35. package/src/config/loader.js +75 -0
  36. package/src/config/paths.js +121 -0
  37. package/src/index.js +373 -0
  38. package/src/reset-config.js +92 -0
  39. package/src/server/api/agents.js +248 -0
  40. package/src/server/api/aliases.js +36 -0
  41. package/src/server/api/channels.js +258 -0
  42. package/src/server/api/claude-hooks.js +480 -0
  43. package/src/server/api/codex-channels.js +312 -0
  44. package/src/server/api/codex-projects.js +91 -0
  45. package/src/server/api/codex-proxy.js +182 -0
  46. package/src/server/api/codex-sessions.js +491 -0
  47. package/src/server/api/codex-statistics.js +57 -0
  48. package/src/server/api/commands.js +245 -0
  49. package/src/server/api/config-templates.js +182 -0
  50. package/src/server/api/config.js +147 -0
  51. package/src/server/api/convert.js +127 -0
  52. package/src/server/api/dashboard.js +125 -0
  53. package/src/server/api/env.js +144 -0
  54. package/src/server/api/favorites.js +77 -0
  55. package/src/server/api/gemini-channels.js +261 -0
  56. package/src/server/api/gemini-projects.js +91 -0
  57. package/src/server/api/gemini-proxy.js +160 -0
  58. package/src/server/api/gemini-sessions.js +397 -0
  59. package/src/server/api/gemini-statistics.js +57 -0
  60. package/src/server/api/health-check.js +118 -0
  61. package/src/server/api/mcp.js +336 -0
  62. package/src/server/api/pm2-autostart.js +269 -0
  63. package/src/server/api/projects.js +124 -0
  64. package/src/server/api/prompts.js +279 -0
  65. package/src/server/api/proxy.js +235 -0
  66. package/src/server/api/rules.js +271 -0
  67. package/src/server/api/sessions.js +595 -0
  68. package/src/server/api/settings.js +61 -0
  69. package/src/server/api/skills.js +305 -0
  70. package/src/server/api/statistics.js +91 -0
  71. package/src/server/api/terminal.js +202 -0
  72. package/src/server/api/ui-config.js +64 -0
  73. package/src/server/api/workspaces.js +407 -0
  74. package/src/server/codex-proxy-server.js +538 -0
  75. package/src/server/dev-server.js +26 -0
  76. package/src/server/gemini-proxy-server.js +518 -0
  77. package/src/server/index.js +305 -0
  78. package/src/server/proxy-server.js +469 -0
  79. package/src/server/services/agents-service.js +354 -0
  80. package/src/server/services/alias.js +71 -0
  81. package/src/server/services/channel-health.js +234 -0
  82. package/src/server/services/channel-scheduler.js +234 -0
  83. package/src/server/services/channels.js +347 -0
  84. package/src/server/services/codex-channels.js +625 -0
  85. package/src/server/services/codex-config.js +90 -0
  86. package/src/server/services/codex-parser.js +322 -0
  87. package/src/server/services/codex-sessions.js +665 -0
  88. package/src/server/services/codex-settings-manager.js +397 -0
  89. package/src/server/services/codex-speed-test-template.json +24 -0
  90. package/src/server/services/codex-statistics-service.js +255 -0
  91. package/src/server/services/commands-service.js +360 -0
  92. package/src/server/services/config-templates-service.js +732 -0
  93. package/src/server/services/env-checker.js +307 -0
  94. package/src/server/services/env-manager.js +300 -0
  95. package/src/server/services/favorites.js +163 -0
  96. package/src/server/services/gemini-channels.js +333 -0
  97. package/src/server/services/gemini-config.js +73 -0
  98. package/src/server/services/gemini-sessions.js +689 -0
  99. package/src/server/services/gemini-settings-manager.js +263 -0
  100. package/src/server/services/gemini-statistics-service.js +253 -0
  101. package/src/server/services/health-check.js +399 -0
  102. package/src/server/services/mcp-service.js +1188 -0
  103. package/src/server/services/prompts-service.js +492 -0
  104. package/src/server/services/proxy-runtime.js +79 -0
  105. package/src/server/services/pty-manager.js +435 -0
  106. package/src/server/services/rules-service.js +401 -0
  107. package/src/server/services/session-cache.js +127 -0
  108. package/src/server/services/session-converter.js +577 -0
  109. package/src/server/services/sessions.js +757 -0
  110. package/src/server/services/settings-manager.js +163 -0
  111. package/src/server/services/skill-service.js +965 -0
  112. package/src/server/services/speed-test.js +545 -0
  113. package/src/server/services/statistics-service.js +386 -0
  114. package/src/server/services/terminal-commands.js +155 -0
  115. package/src/server/services/terminal-config.js +140 -0
  116. package/src/server/services/terminal-detector.js +306 -0
  117. package/src/server/services/ui-config.js +130 -0
  118. package/src/server/services/workspace-service.js +662 -0
  119. package/src/server/utils/pricing.js +41 -0
  120. package/src/server/websocket-server.js +557 -0
  121. package/src/ui/menu.js +129 -0
  122. package/src/ui/prompts.js +100 -0
  123. package/src/utils/format.js +43 -0
  124. package/src/utils/port-helper.js +94 -0
  125. package/src/utils/session.js +239 -0
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Commands 服务
3
+ *
4
+ * 管理 Claude Code 自定义命令的 CRUD 操作
5
+ * 命令目录:
6
+ * - 用户级: ~/.claude/commands/
7
+ * - 项目级: .claude/commands/
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const os = require('os');
13
+
14
+ // 命令目录路径
15
+ const USER_COMMANDS_DIR = path.join(os.homedir(), '.claude', 'commands');
16
+
17
+ /**
18
+ * 确保目录存在
19
+ */
20
+ function ensureDir(dirPath) {
21
+ if (!fs.existsSync(dirPath)) {
22
+ fs.mkdirSync(dirPath, { recursive: true });
23
+ }
24
+ }
25
+
26
+ /**
27
+ * 解析 YAML frontmatter
28
+ */
29
+ function parseFrontmatter(content) {
30
+ const result = {
31
+ frontmatter: {},
32
+ body: content
33
+ };
34
+
35
+ // 移除 BOM
36
+ content = content.trim().replace(/^\uFEFF/, '');
37
+
38
+ // 解析 YAML frontmatter
39
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/);
40
+ if (!match) {
41
+ return result;
42
+ }
43
+
44
+ const frontmatterText = match[1];
45
+ result.body = match[2].trim();
46
+
47
+ // 简单解析 YAML(支持基本字段)
48
+ const lines = frontmatterText.split('\n');
49
+ for (const line of lines) {
50
+ const colonIndex = line.indexOf(':');
51
+ if (colonIndex === -1) continue;
52
+
53
+ const key = line.slice(0, colonIndex).trim();
54
+ let value = line.slice(colonIndex + 1).trim();
55
+
56
+ // 去除引号
57
+ if ((value.startsWith('"') && value.endsWith('"')) ||
58
+ (value.startsWith("'") && value.endsWith("'"))) {
59
+ value = value.slice(1, -1);
60
+ }
61
+
62
+ result.frontmatter[key] = value;
63
+ }
64
+
65
+ return result;
66
+ }
67
+
68
+ /**
69
+ * 生成 frontmatter 字符串
70
+ */
71
+ function generateFrontmatter(data) {
72
+ const lines = ['---'];
73
+
74
+ if (data.description) {
75
+ lines.push(`description: "${data.description}"`);
76
+ }
77
+ if (data['allowed-tools']) {
78
+ lines.push(`allowed-tools: ${data['allowed-tools']}`);
79
+ }
80
+ if (data['argument-hint']) {
81
+ lines.push(`argument-hint: ${data['argument-hint']}`);
82
+ }
83
+
84
+ lines.push('---');
85
+ return lines.join('\n');
86
+ }
87
+
88
+ /**
89
+ * 递归扫描目录获取命令文件
90
+ */
91
+ function scanCommandsDir(dir, basePath, scope) {
92
+ const commands = [];
93
+
94
+ if (!fs.existsSync(dir)) {
95
+ return commands;
96
+ }
97
+
98
+ try {
99
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
100
+
101
+ for (const entry of entries) {
102
+ const fullPath = path.join(dir, entry.name);
103
+
104
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
105
+ // 递归扫描子目录
106
+ const subCommands = scanCommandsDir(fullPath, basePath, scope);
107
+ commands.push(...subCommands);
108
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
109
+ // 解析命令文件
110
+ try {
111
+ const content = fs.readFileSync(fullPath, 'utf-8');
112
+ const { frontmatter, body } = parseFrontmatter(content);
113
+
114
+ // 计算相对路径和命令名
115
+ const relativePath = path.relative(basePath, fullPath);
116
+ const commandName = entry.name.replace(/\.md$/, '');
117
+ const namespace = path.dirname(relativePath);
118
+
119
+ commands.push({
120
+ name: commandName,
121
+ namespace: namespace === '.' ? null : namespace,
122
+ scope,
123
+ path: relativePath,
124
+ fullPath,
125
+ description: frontmatter.description || '',
126
+ allowedTools: frontmatter['allowed-tools'] || '',
127
+ argumentHint: frontmatter['argument-hint'] || '',
128
+ body,
129
+ fullContent: content,
130
+ updatedAt: fs.statSync(fullPath).mtime.getTime()
131
+ });
132
+ } catch (err) {
133
+ console.warn(`[CommandsService] Failed to parse ${fullPath}:`, err.message);
134
+ }
135
+ }
136
+ }
137
+ } catch (err) {
138
+ console.error(`[CommandsService] Failed to scan ${dir}:`, err.message);
139
+ }
140
+
141
+ return commands;
142
+ }
143
+
144
+ /**
145
+ * Commands 服务类
146
+ */
147
+ class CommandsService {
148
+ constructor() {
149
+ this.userCommandsDir = USER_COMMANDS_DIR;
150
+ ensureDir(this.userCommandsDir);
151
+ }
152
+
153
+ /**
154
+ * 获取所有命令列表
155
+ * @param {string} projectPath - 项目路径(可选,用于获取项目级命令)
156
+ */
157
+ listCommands(projectPath = null) {
158
+ const commands = [];
159
+
160
+ // 获取用户级命令
161
+ const userCommands = scanCommandsDir(this.userCommandsDir, this.userCommandsDir, 'user');
162
+ commands.push(...userCommands);
163
+
164
+ // 获取项目级命令(如果提供了项目路径)
165
+ if (projectPath) {
166
+ const projectCommandsDir = path.join(projectPath, '.claude', 'commands');
167
+ const projectCommands = scanCommandsDir(projectCommandsDir, projectCommandsDir, 'project');
168
+ commands.push(...projectCommands);
169
+ }
170
+
171
+ // 按名称排序
172
+ commands.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
173
+
174
+ return {
175
+ commands,
176
+ total: commands.length,
177
+ userCount: userCommands.length,
178
+ projectCount: commands.length - userCommands.length
179
+ };
180
+ }
181
+
182
+ /**
183
+ * 获取单个命令详情
184
+ */
185
+ getCommand(name, scope, projectPath = null, namespace = null) {
186
+ const baseDir = scope === 'user'
187
+ ? this.userCommandsDir
188
+ : path.join(projectPath, '.claude', 'commands');
189
+
190
+ const relativePath = namespace
191
+ ? path.join(namespace, `${name}.md`)
192
+ : `${name}.md`;
193
+
194
+ const fullPath = path.join(baseDir, relativePath);
195
+
196
+ if (!fs.existsSync(fullPath)) {
197
+ return null;
198
+ }
199
+
200
+ const content = fs.readFileSync(fullPath, 'utf-8');
201
+ const { frontmatter, body } = parseFrontmatter(content);
202
+
203
+ return {
204
+ name,
205
+ namespace,
206
+ scope,
207
+ path: relativePath,
208
+ fullPath,
209
+ description: frontmatter.description || '',
210
+ allowedTools: frontmatter['allowed-tools'] || '',
211
+ argumentHint: frontmatter['argument-hint'] || '',
212
+ body,
213
+ fullContent: content,
214
+ updatedAt: fs.statSync(fullPath).mtime.getTime()
215
+ };
216
+ }
217
+
218
+ /**
219
+ * 创建命令
220
+ */
221
+ createCommand({ name, scope, projectPath, namespace, description, allowedTools, argumentHint, body }) {
222
+ if (!name || !name.trim()) {
223
+ throw new Error('命令名称不能为空');
224
+ }
225
+
226
+ // 验证命令名:只允许字母、数字、横杠、下划线
227
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
228
+ throw new Error('命令名只能包含字母、数字、横杠和下划线');
229
+ }
230
+
231
+ const baseDir = scope === 'user'
232
+ ? this.userCommandsDir
233
+ : path.join(projectPath, '.claude', 'commands');
234
+
235
+ const targetDir = namespace ? path.join(baseDir, namespace) : baseDir;
236
+ ensureDir(targetDir);
237
+
238
+ const filePath = path.join(targetDir, `${name}.md`);
239
+
240
+ // 检查是否已存在
241
+ if (fs.existsSync(filePath)) {
242
+ throw new Error(`命令 "${name}" 已存在`);
243
+ }
244
+
245
+ // 生成文件内容
246
+ const frontmatterData = {};
247
+ if (description) frontmatterData.description = description;
248
+ if (allowedTools) frontmatterData['allowed-tools'] = allowedTools;
249
+ if (argumentHint) frontmatterData['argument-hint'] = argumentHint;
250
+
251
+ let content = '';
252
+ if (Object.keys(frontmatterData).length > 0) {
253
+ content = generateFrontmatter(frontmatterData) + '\n\n';
254
+ }
255
+ content += body || '';
256
+
257
+ fs.writeFileSync(filePath, content, 'utf-8');
258
+
259
+ return this.getCommand(name, scope, projectPath, namespace);
260
+ }
261
+
262
+ /**
263
+ * 更新命令
264
+ */
265
+ updateCommand({ name, scope, projectPath, namespace, description, allowedTools, argumentHint, body }) {
266
+ const baseDir = scope === 'user'
267
+ ? this.userCommandsDir
268
+ : path.join(projectPath, '.claude', 'commands');
269
+
270
+ const relativePath = namespace
271
+ ? path.join(namespace, `${name}.md`)
272
+ : `${name}.md`;
273
+
274
+ const filePath = path.join(baseDir, relativePath);
275
+
276
+ if (!fs.existsSync(filePath)) {
277
+ throw new Error(`命令 "${name}" 不存在`);
278
+ }
279
+
280
+ // 生成文件内容
281
+ const frontmatterData = {};
282
+ if (description) frontmatterData.description = description;
283
+ if (allowedTools) frontmatterData['allowed-tools'] = allowedTools;
284
+ if (argumentHint) frontmatterData['argument-hint'] = argumentHint;
285
+
286
+ let content = '';
287
+ if (Object.keys(frontmatterData).length > 0) {
288
+ content = generateFrontmatter(frontmatterData) + '\n\n';
289
+ }
290
+ content += body || '';
291
+
292
+ fs.writeFileSync(filePath, content, 'utf-8');
293
+
294
+ return this.getCommand(name, scope, projectPath, namespace);
295
+ }
296
+
297
+ /**
298
+ * 删除命令
299
+ */
300
+ deleteCommand(name, scope, projectPath = null, namespace = null) {
301
+ const baseDir = scope === 'user'
302
+ ? this.userCommandsDir
303
+ : path.join(projectPath, '.claude', 'commands');
304
+
305
+ const relativePath = namespace
306
+ ? path.join(namespace, `${name}.md`)
307
+ : `${name}.md`;
308
+
309
+ const filePath = path.join(baseDir, relativePath);
310
+
311
+ if (!fs.existsSync(filePath)) {
312
+ return { success: false, message: '命令不存在' };
313
+ }
314
+
315
+ fs.unlinkSync(filePath);
316
+
317
+ // 如果目录为空,删除目录
318
+ if (namespace) {
319
+ const namespaceDir = path.join(baseDir, namespace);
320
+ try {
321
+ const remaining = fs.readdirSync(namespaceDir);
322
+ if (remaining.length === 0) {
323
+ fs.rmdirSync(namespaceDir);
324
+ }
325
+ } catch (err) {
326
+ // 忽略删除目录错误
327
+ }
328
+ }
329
+
330
+ return { success: true, message: '命令已删除' };
331
+ }
332
+
333
+ /**
334
+ * 获取统计信息
335
+ */
336
+ getStats(projectPath = null) {
337
+ const { commands, userCount, projectCount } = this.listCommands(projectPath);
338
+
339
+ // 按命名空间分组
340
+ const namespaces = {};
341
+ for (const cmd of commands) {
342
+ const ns = cmd.namespace || '(root)';
343
+ if (!namespaces[ns]) {
344
+ namespaces[ns] = 0;
345
+ }
346
+ namespaces[ns]++;
347
+ }
348
+
349
+ return {
350
+ total: commands.length,
351
+ userCount,
352
+ projectCount,
353
+ namespaces
354
+ };
355
+ }
356
+ }
357
+
358
+ module.exports = {
359
+ CommandsService
360
+ };