@adversity/coding-tool-x 2.2.0 → 2.4.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 (34) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +12 -14
  3. package/dist/web/assets/index-Bu1oPcKu.js +4009 -0
  4. package/dist/web/assets/index-XSok7-mN.css +41 -0
  5. package/dist/web/index.html +2 -2
  6. package/package.json +5 -4
  7. package/src/config/default.js +1 -1
  8. package/src/index.js +2 -2
  9. package/src/server/api/agents.js +188 -0
  10. package/src/server/api/commands.js +261 -0
  11. package/src/server/api/config-export.js +122 -0
  12. package/src/server/api/config-templates.js +26 -5
  13. package/src/server/api/health-check.js +1 -89
  14. package/src/server/api/permissions.js +370 -0
  15. package/src/server/api/rules.js +188 -0
  16. package/src/server/api/skills.js +66 -14
  17. package/src/server/api/workspaces.js +30 -55
  18. package/src/server/index.js +7 -11
  19. package/src/server/services/agents-service.js +179 -1
  20. package/src/server/services/commands-service.js +231 -47
  21. package/src/server/services/config-export-service.js +209 -0
  22. package/src/server/services/config-templates-service.js +481 -107
  23. package/src/server/services/format-converter.js +506 -0
  24. package/src/server/services/health-check.js +1 -315
  25. package/src/server/services/permission-templates-service.js +339 -0
  26. package/src/server/services/repo-scanner-base.js +678 -0
  27. package/src/server/services/rules-service.js +179 -1
  28. package/src/server/services/skill-service.js +114 -61
  29. package/src/server/services/workspace-service.js +52 -1
  30. package/dist/web/assets/index-D1AYlFLZ.js +0 -3220
  31. package/dist/web/assets/index-aL3cKxSK.css +0 -41
  32. package/docs/CHANGELOG.md +0 -582
  33. package/docs/DIRECTORY_MIGRATION.md +0 -112
  34. package/docs/PROJECT_STRUCTURE.md +0 -396
@@ -1,11 +1,6 @@
1
1
  const express = require('express');
2
2
  const router = express.Router();
3
- const {
4
- healthCheckAllProjects,
5
- scanLegacySessionFiles,
6
- migrateSessionFiles,
7
- cleanLegacySessionFiles
8
- } = require('../services/health-check');
3
+ const { healthCheckAllProjects } = require('../services/health-check');
9
4
  const { getProjects } = require('../services/sessions');
10
5
 
11
6
  module.exports = (config) => {
@@ -31,88 +26,5 @@ module.exports = (config) => {
31
26
  }
32
27
  });
33
28
 
34
- /**
35
- * GET /api/health-check/scan-legacy - 扫描旧文件
36
- */
37
- router.get('/scan-legacy', (req, res) => {
38
- try {
39
- const result = scanLegacySessionFiles();
40
-
41
- res.json({
42
- success: true,
43
- timestamp: new Date().toISOString(),
44
- ...result
45
- });
46
- } catch (error) {
47
- console.error('Legacy scan failed:', error);
48
- res.status(500).json({
49
- success: false,
50
- error: error.message
51
- });
52
- }
53
- });
54
-
55
- /**
56
- * POST /api/health-check/migrate-legacy - 迁移旧文件到正确位置
57
- * Body:
58
- * {
59
- * "dryRun": boolean, // 是否只是预演
60
- * "projectNames": string[] // 可选:指定要迁移的项目
61
- * }
62
- */
63
- router.post('/migrate-legacy', (req, res) => {
64
- try {
65
- const { dryRun = false, projectNames = null } = req.body;
66
-
67
- const result = migrateSessionFiles({
68
- dryRun,
69
- projectNames
70
- });
71
-
72
- res.json({
73
- success: true,
74
- timestamp: new Date().toISOString(),
75
- ...result
76
- });
77
- } catch (error) {
78
- console.error('Legacy migration failed:', error);
79
- res.status(500).json({
80
- success: false,
81
- error: error.message
82
- });
83
- }
84
- });
85
-
86
- /**
87
- * POST /api/health-check/clean-legacy - 清理旧文件
88
- * Body:
89
- * {
90
- * "dryRun": boolean, // 是否只是预演
91
- * "projectNames": string[] // 可选:指定要清理的项目
92
- * }
93
- */
94
- router.post('/clean-legacy', (req, res) => {
95
- try {
96
- const { dryRun = false, projectNames = null } = req.body;
97
-
98
- const result = cleanLegacySessionFiles({
99
- dryRun,
100
- projectNames
101
- });
102
-
103
- res.json({
104
- success: true,
105
- timestamp: new Date().toISOString(),
106
- ...result
107
- });
108
- } catch (error) {
109
- console.error('Legacy cleanup failed:', error);
110
- res.status(500).json({
111
- success: false,
112
- error: error.message
113
- });
114
- }
115
- });
116
-
117
29
  return router;
118
30
  };
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Command Permissions API 路由
3
+ *
4
+ * 管理 Claude Code / Codex / Gemini CLI 命令执行权限
5
+ *
6
+ * 三个 CLI 工具的权限控制机制:
7
+ *
8
+ * 1. Claude Code:
9
+ * - 配置文件: ~/.claude/settings.json (用户级) 或 .claude/settings.json (项目级)
10
+ * - 权限格式: permissions.allow / permissions.deny 数组
11
+ * - 支持通配符: Bash(npm run *), Read(./src/**)
12
+ *
13
+ * 2. Codex CLI:
14
+ * - 启动参数: --ask-for-approval never/on-request/on-failure
15
+ * - 沙箱模式: --sandbox read-only/workspace-write/danger-full-access
16
+ * - YOLO 模式: --yolo 或 --dangerously-bypass-approvals-and-sandbox
17
+ *
18
+ * 3. Gemini CLI:
19
+ * - 启动参数: --approval-mode default/auto_edit/yolo
20
+ * - 允许工具: --allowed-tools "ShellTool(git status)"
21
+ * - 沙箱: --sandbox / -s
22
+ */
23
+
24
+ const express = require('express');
25
+ const fs = require('fs');
26
+ const path = require('path');
27
+ const os = require('os');
28
+ const permissionTemplatesService = require('../services/permission-templates-service');
29
+
30
+ const router = express.Router();
31
+
32
+ // Claude Code 设置文件路径
33
+ function getClaudeSettingsPath(projectPath, isLocal = false) {
34
+ if (projectPath) {
35
+ return path.join(projectPath, '.claude', isLocal ? 'settings.local.json' : 'settings.json');
36
+ }
37
+ return path.join(os.homedir(), '.claude', 'settings.json');
38
+ }
39
+
40
+ // 读取 Claude Code settings.json
41
+ function readClaudeSettings(projectPath, isLocal = false) {
42
+ const settingsPath = getClaudeSettingsPath(projectPath, isLocal);
43
+ try {
44
+ if (fs.existsSync(settingsPath)) {
45
+ const content = fs.readFileSync(settingsPath, 'utf-8');
46
+ return JSON.parse(content);
47
+ }
48
+ } catch (err) {
49
+ console.error('[Permissions API] Error reading Claude settings:', err);
50
+ }
51
+ return {};
52
+ }
53
+
54
+ // 保存 Claude Code settings.json
55
+ function saveClaudeSettings(projectPath, settings, isLocal = false) {
56
+ const settingsPath = getClaudeSettingsPath(projectPath, isLocal);
57
+ const settingsDir = path.dirname(settingsPath);
58
+
59
+ if (!fs.existsSync(settingsDir)) {
60
+ fs.mkdirSync(settingsDir, { recursive: true });
61
+ }
62
+
63
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
64
+ }
65
+
66
+ // 全局 all-allow 状态(内存中)
67
+ let globalAllAllowEnabled = process.env.CLAUDE_ALL_ALLOW === 'true';
68
+
69
+ /**
70
+ * 获取项目的命令执行权限设置
71
+ * GET /api/permissions
72
+ * Query: projectPath - 项目路径, cliType - CLI 类型 (claude/codex/gemini)
73
+ */
74
+ router.get('/', (req, res) => {
75
+ try {
76
+ const { projectPath, cliType = 'claude' } = req.query;
77
+
78
+ if (!projectPath) {
79
+ return res.status(400).json({
80
+ success: false,
81
+ message: '缺少 projectPath 参数'
82
+ });
83
+ }
84
+
85
+ if (!fs.existsSync(projectPath)) {
86
+ return res.status(404).json({
87
+ success: false,
88
+ message: '项目路径不存在'
89
+ });
90
+ }
91
+
92
+ // 读取 Claude Code 配置(所有 CLI 工具共用此配置管理)
93
+ const settings = readClaudeSettings(projectPath);
94
+ const permissions = settings.permissions || {};
95
+
96
+ res.json({
97
+ success: true,
98
+ cliType,
99
+ settings: {
100
+ // Claude Code 格式
101
+ allow: permissions.allow || [],
102
+ deny: permissions.deny || [],
103
+ // 兼容旧格式
104
+ allowedCommands: permissions.allow || [],
105
+ denyCommands: permissions.deny || []
106
+ }
107
+ });
108
+ } catch (err) {
109
+ console.error('[Permissions API] Get permissions error:', err);
110
+ res.status(500).json({
111
+ success: false,
112
+ message: err.message
113
+ });
114
+ }
115
+ });
116
+
117
+ /**
118
+ * 保存项目的命令执行权限设置
119
+ * POST /api/permissions
120
+ * Body: { projectPath, settings: { allow, deny }, isLocal }
121
+ */
122
+ router.post('/', (req, res) => {
123
+ try {
124
+ const { projectPath, settings: newPermissions, isLocal = false } = req.body;
125
+
126
+ if (!projectPath) {
127
+ return res.status(400).json({
128
+ success: false,
129
+ message: '缺少 projectPath 参数'
130
+ });
131
+ }
132
+
133
+ if (!fs.existsSync(projectPath)) {
134
+ return res.status(404).json({
135
+ success: false,
136
+ message: '项目路径不存在'
137
+ });
138
+ }
139
+
140
+ // 读取现有设置
141
+ const settings = readClaudeSettings(projectPath, isLocal);
142
+
143
+ // 更新权限设置(使用 Claude Code 的标准格式)
144
+ settings.permissions = {
145
+ allow: newPermissions?.allow || newPermissions?.allowedCommands || [],
146
+ deny: newPermissions?.deny || newPermissions?.denyCommands || []
147
+ };
148
+
149
+ // 保存设置
150
+ saveClaudeSettings(projectPath, settings, isLocal);
151
+
152
+ res.json({
153
+ success: true,
154
+ message: '权限设置已保存',
155
+ savedTo: isLocal ? '.claude/settings.local.json' : '.claude/settings.json'
156
+ });
157
+ } catch (err) {
158
+ console.error('[Permissions API] Save permissions error:', err);
159
+ res.status(500).json({
160
+ success: false,
161
+ message: err.message
162
+ });
163
+ }
164
+ });
165
+
166
+ /**
167
+ * 获取全局 all-allow 模式状态
168
+ * GET /api/permissions/all-allow
169
+ */
170
+ router.get('/all-allow', (req, res) => {
171
+ try {
172
+ res.json({
173
+ success: true,
174
+ enabled: globalAllAllowEnabled,
175
+ note: '此设置通过启动命令参数控制,如 claude --dangerously-skip-permissions'
176
+ });
177
+ } catch (err) {
178
+ console.error('[Permissions API] Get all-allow status error:', err);
179
+ res.status(500).json({
180
+ success: false,
181
+ message: err.message
182
+ });
183
+ }
184
+ });
185
+
186
+ /**
187
+ * 设置全局 all-allow 模式(运行时)
188
+ * POST /api/permissions/all-allow
189
+ * Body: { enabled }
190
+ */
191
+ router.post('/all-allow', (req, res) => {
192
+ try {
193
+ const { enabled } = req.body;
194
+ globalAllAllowEnabled = !!enabled;
195
+
196
+ res.json({
197
+ success: true,
198
+ enabled: globalAllAllowEnabled,
199
+ message: enabled ? 'All-Allow 模式已启用' : 'All-Allow 模式已禁用'
200
+ });
201
+ } catch (err) {
202
+ console.error('[Permissions API] Set all-allow status error:', err);
203
+ res.status(500).json({
204
+ success: false,
205
+ message: err.message
206
+ });
207
+ }
208
+ });
209
+
210
+ /**
211
+ * 获取所有权限模版(内置 + 自定义)
212
+ * GET /api/permissions/templates
213
+ */
214
+ router.get('/templates', (req, res) => {
215
+ try {
216
+ const templates = permissionTemplatesService.getAllTemplates();
217
+ res.json({
218
+ success: true,
219
+ data: templates
220
+ });
221
+ } catch (err) {
222
+ console.error('[Permissions API] Get templates error:', err);
223
+ res.status(500).json({
224
+ success: false,
225
+ message: err.message
226
+ });
227
+ }
228
+ });
229
+
230
+ /**
231
+ * 获取单个权限模版
232
+ * GET /api/permissions/templates/:id
233
+ */
234
+ router.get('/templates/:id', (req, res) => {
235
+ try {
236
+ const template = permissionTemplatesService.getTemplateById(req.params.id);
237
+ if (!template) {
238
+ return res.status(404).json({
239
+ success: false,
240
+ message: '模版不存在'
241
+ });
242
+ }
243
+ res.json({
244
+ success: true,
245
+ data: template
246
+ });
247
+ } catch (err) {
248
+ console.error('[Permissions API] Get template error:', err);
249
+ res.status(500).json({
250
+ success: false,
251
+ message: err.message
252
+ });
253
+ }
254
+ });
255
+
256
+ /**
257
+ * 创建自定义权限模版
258
+ * POST /api/permissions/templates
259
+ */
260
+ router.post('/templates', (req, res) => {
261
+ try {
262
+ const template = permissionTemplatesService.createTemplate(req.body);
263
+ res.json({
264
+ success: true,
265
+ data: template,
266
+ message: '模版创建成功'
267
+ });
268
+ } catch (err) {
269
+ console.error('[Permissions API] Create template error:', err);
270
+ res.status(400).json({
271
+ success: false,
272
+ message: err.message
273
+ });
274
+ }
275
+ });
276
+
277
+ /**
278
+ * 更新自定义权限模版
279
+ * PUT /api/permissions/templates/:id
280
+ */
281
+ router.put('/templates/:id', (req, res) => {
282
+ try {
283
+ const template = permissionTemplatesService.updateTemplate(req.params.id, req.body);
284
+ res.json({
285
+ success: true,
286
+ data: template,
287
+ message: '模版更新成功'
288
+ });
289
+ } catch (err) {
290
+ console.error('[Permissions API] Update template error:', err);
291
+ res.status(400).json({
292
+ success: false,
293
+ message: err.message
294
+ });
295
+ }
296
+ });
297
+
298
+ /**
299
+ * 删除自定义权限模版
300
+ * DELETE /api/permissions/templates/:id
301
+ */
302
+ router.delete('/templates/:id', (req, res) => {
303
+ try {
304
+ permissionTemplatesService.deleteTemplate(req.params.id);
305
+ res.json({
306
+ success: true,
307
+ message: '模版删除成功'
308
+ });
309
+ } catch (err) {
310
+ console.error('[Permissions API] Delete template error:', err);
311
+ res.status(400).json({
312
+ success: false,
313
+ message: err.message
314
+ });
315
+ }
316
+ });
317
+
318
+ /**
319
+ * 获取各 CLI 工具的启动参数配置说明
320
+ * GET /api/permissions/cli-config
321
+ */
322
+ router.get('/cli-config', (req, res) => {
323
+ try {
324
+ const cliConfigs = {
325
+ claude: {
326
+ name: 'Claude Code',
327
+ configFile: '.claude/settings.json',
328
+ userConfigFile: '~/.claude/settings.json',
329
+ permissionFormat: {
330
+ example: {
331
+ permissions: {
332
+ allow: ['Bash(npm run:*)', 'Read(./src/**)'],
333
+ deny: ['Bash(rm:*)', 'Read(.env)']
334
+ }
335
+ }
336
+ },
337
+ allAllowFlag: '--dangerously-skip-permissions',
338
+ docs: 'https://docs.anthropic.com/en/docs/claude-code'
339
+ },
340
+ codex: {
341
+ name: 'Codex CLI',
342
+ sandboxModes: ['read-only', 'workspace-write'],
343
+ approvalModes: ['suggest', 'auto-edit'],
344
+ allAllowFlag: '--dangerously-bypass-approvals-and-sandbox',
345
+ example: 'codex --approval-mode auto-edit --sandbox workspace-write "task"',
346
+ docs: 'https://github.com/openai/codex'
347
+ },
348
+ gemini: {
349
+ name: 'Gemini CLI',
350
+ allowedToolsFlag: '--allowedTools "ShellTool(git status)"',
351
+ sandboxFlag: '--sandbox / -s',
352
+ allAllowFlag: '--yolo',
353
+ docs: 'https://github.com/google-gemini/gemini-cli'
354
+ }
355
+ };
356
+
357
+ res.json({
358
+ success: true,
359
+ cliConfigs
360
+ });
361
+ } catch (err) {
362
+ console.error('[Permissions API] Get CLI config error:', err);
363
+ res.status(500).json({
364
+ success: false,
365
+ message: err.message
366
+ });
367
+ }
368
+ });
369
+
370
+ module.exports = router;
@@ -268,4 +268,192 @@ router.delete('/:scope/*', (req, res) => {
268
268
  }
269
269
  });
270
270
 
271
+ // ==================== 仓库管理 API ====================
272
+
273
+ /**
274
+ * 获取所有规则(包括远程仓库)
275
+ * GET /api/rules/all
276
+ * Query: projectPath, refresh=1 强制刷新缓存
277
+ */
278
+ router.get('/all', async (req, res) => {
279
+ try {
280
+ const { projectPath, refresh } = req.query;
281
+ const forceRefresh = refresh === '1';
282
+ const result = await rulesService.listAllRules(projectPath || null, forceRefresh);
283
+
284
+ res.json({
285
+ success: true,
286
+ ...result
287
+ });
288
+ } catch (err) {
289
+ console.error('[Rules API] List all rules error:', err);
290
+ res.status(500).json({
291
+ success: false,
292
+ message: err.message
293
+ });
294
+ }
295
+ });
296
+
297
+ /**
298
+ * 获取仓库列表
299
+ * GET /api/rules/repos
300
+ */
301
+ router.get('/repos', (req, res) => {
302
+ try {
303
+ const repos = rulesService.getRepos();
304
+ res.json({
305
+ success: true,
306
+ repos
307
+ });
308
+ } catch (err) {
309
+ console.error('[Rules API] Get repos error:', err);
310
+ res.status(500).json({
311
+ success: false,
312
+ message: err.message
313
+ });
314
+ }
315
+ });
316
+
317
+ /**
318
+ * 添加仓库
319
+ * POST /api/rules/repos
320
+ * Body: { owner, name, branch, directory, enabled }
321
+ */
322
+ router.post('/repos', (req, res) => {
323
+ try {
324
+ const { owner, name, branch = 'main', directory = '', enabled = true } = req.body;
325
+
326
+ if (!owner || !name) {
327
+ return res.status(400).json({
328
+ success: false,
329
+ message: 'Missing owner or name'
330
+ });
331
+ }
332
+
333
+ const repos = rulesService.addRepo({ owner, name, branch, directory, enabled });
334
+
335
+ res.json({
336
+ success: true,
337
+ repos
338
+ });
339
+ } catch (err) {
340
+ console.error('[Rules API] Add repo error:', err);
341
+ res.status(500).json({
342
+ success: false,
343
+ message: err.message
344
+ });
345
+ }
346
+ });
347
+
348
+ /**
349
+ * 删除仓库
350
+ * DELETE /api/rules/repos/:owner/:name
351
+ * Query: directory - 可选,子目录路径
352
+ */
353
+ router.delete('/repos/:owner/:name', (req, res) => {
354
+ try {
355
+ const { owner, name } = req.params;
356
+ const { directory = '' } = req.query;
357
+ const repos = rulesService.removeRepo(owner, name, directory);
358
+
359
+ res.json({
360
+ success: true,
361
+ repos
362
+ });
363
+ } catch (err) {
364
+ console.error('[Rules API] Remove repo error:', err);
365
+ res.status(500).json({
366
+ success: false,
367
+ message: err.message
368
+ });
369
+ }
370
+ });
371
+
372
+ /**
373
+ * 切换仓库启用状态
374
+ * PUT /api/rules/repos/:owner/:name/toggle
375
+ * Body: { enabled, directory }
376
+ */
377
+ router.put('/repos/:owner/:name/toggle', (req, res) => {
378
+ try {
379
+ const { owner, name } = req.params;
380
+ const { enabled, directory = '' } = req.body;
381
+
382
+ const repos = rulesService.toggleRepo(owner, name, directory, enabled);
383
+
384
+ res.json({
385
+ success: true,
386
+ repos
387
+ });
388
+ } catch (err) {
389
+ console.error('[Rules API] Toggle repo error:', err);
390
+ res.status(500).json({
391
+ success: false,
392
+ message: err.message
393
+ });
394
+ }
395
+ });
396
+
397
+ /**
398
+ * 从远程仓库安装规则
399
+ * POST /api/rules/install
400
+ * Body: rule object from listAllRules
401
+ */
402
+ router.post('/install', async (req, res) => {
403
+ try {
404
+ const rule = req.body;
405
+
406
+ if (!rule || !rule.repoOwner || !rule.repoName) {
407
+ return res.status(400).json({
408
+ success: false,
409
+ message: 'Missing rule info or repo info'
410
+ });
411
+ }
412
+
413
+ const result = await rulesService.installFromRemote(rule);
414
+
415
+ res.json({
416
+ success: true,
417
+ ...result
418
+ });
419
+ } catch (err) {
420
+ console.error('[Rules API] Install rule error:', err);
421
+ res.status(500).json({
422
+ success: false,
423
+ message: err.message
424
+ });
425
+ }
426
+ });
427
+
428
+ /**
429
+ * 卸载规则
430
+ * POST /api/rules/uninstall
431
+ * Body: { path } - 规则的相对路径
432
+ */
433
+ router.post('/uninstall', (req, res) => {
434
+ try {
435
+ const { path } = req.body;
436
+
437
+ if (!path) {
438
+ return res.status(400).json({
439
+ success: false,
440
+ message: 'Missing path'
441
+ });
442
+ }
443
+
444
+ const result = rulesService.uninstallRule(path);
445
+
446
+ res.json({
447
+ success: true,
448
+ ...result
449
+ });
450
+ } catch (err) {
451
+ console.error('[Rules API] Uninstall rule error:', err);
452
+ res.status(500).json({
453
+ success: false,
454
+ message: err.message
455
+ });
456
+ }
457
+ });
458
+
271
459
  module.exports = router;