@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,7 +1,5 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const os = require('os');
4
- const readline = require('readline');
5
3
 
6
4
  /**
7
5
  * 健康检查:确保项目的 .claude/sessions 目录存在
@@ -81,319 +79,7 @@ function healthCheckAllProjects(projects) {
81
79
  };
82
80
  }
83
81
 
84
- /**
85
- * 从会话文件中提取 cwd
86
- * @param {string} sessionFilePath - 会话文件路径
87
- * @returns {string|null} cwd 或 null
88
- */
89
- function extractCwdFromSession(sessionFilePath) {
90
- try {
91
- const content = fs.readFileSync(sessionFilePath, 'utf8');
92
- const firstLine = content.split('\n')[0];
93
- if (firstLine) {
94
- const json = JSON.parse(firstLine);
95
- return json.cwd || null;
96
- }
97
- } catch (err) {
98
- // Ignore parse errors
99
- }
100
- return null;
101
- }
102
-
103
- /**
104
- * 扫描旧的全局目录中的会话文件
105
- * @returns {Object} 扫描结果
106
- */
107
- function scanLegacySessionFiles() {
108
- try {
109
- const legacyProjectsDir = path.join(os.homedir(), '.claude', 'projects');
110
-
111
- if (!fs.existsSync(legacyProjectsDir)) {
112
- return {
113
- found: false,
114
- message: 'Legacy directory not found (nothing to clean)',
115
- legacyDir: legacyProjectsDir
116
- };
117
- }
118
-
119
- const projects = [];
120
- const entries = fs.readdirSync(legacyProjectsDir, { withFileTypes: true });
121
-
122
- for (const entry of entries) {
123
- if (!entry.isDirectory()) continue;
124
-
125
- const projectName = entry.name;
126
- const projectDir = path.join(legacyProjectsDir, projectName);
127
- const files = fs.readdirSync(projectDir)
128
- .filter(f => f.endsWith('.jsonl') || f.endsWith('.json'));
129
-
130
- if (files.length > 0) {
131
- let totalSize = 0;
132
- for (const file of files) {
133
- const filePath = path.join(projectDir, file);
134
- const stats = fs.statSync(filePath);
135
- totalSize += stats.size;
136
- }
137
-
138
- projects.push({
139
- projectName,
140
- projectDir,
141
- fileCount: files.length,
142
- totalSize,
143
- files: files.slice(0, 5) // 只显示前5个文件名
144
- });
145
- }
146
- }
147
-
148
- return {
149
- found: true,
150
- legacyDir: legacyProjectsDir,
151
- projectCount: projects.length,
152
- projects
153
- };
154
- } catch (err) {
155
- return {
156
- found: false,
157
- error: err.message
158
- };
159
- }
160
- }
161
-
162
- /**
163
- * 迁移会话文件到正确的位置
164
- * @param {Object} options - 迁移选项
165
- * @returns {Object} 迁移结果
166
- */
167
- function migrateSessionFiles(options = {}) {
168
- const {
169
- dryRun = false, // 是否只是预演
170
- projectNames = null // 指定要迁移的项目
171
- } = options;
172
-
173
- try {
174
- const legacyProjectsDir = path.join(os.homedir(), '.claude', 'projects');
175
-
176
- if (!fs.existsSync(legacyProjectsDir)) {
177
- return {
178
- success: true,
179
- message: 'No legacy directory found',
180
- migrated: 0
181
- };
182
- }
183
-
184
- const results = {
185
- dryRun,
186
- migrated: [],
187
- skipped: [],
188
- errors: []
189
- };
190
-
191
- const entries = fs.readdirSync(legacyProjectsDir, { withFileTypes: true });
192
-
193
- for (const entry of entries) {
194
- if (!entry.isDirectory()) continue;
195
-
196
- const projectName = entry.name;
197
-
198
- // 如果指定了项目列表,只迁移列表中的项目
199
- if (projectNames && !projectNames.includes(projectName)) {
200
- continue;
201
- }
202
-
203
- const projectDir = path.join(legacyProjectsDir, projectName);
204
- const files = fs.readdirSync(projectDir)
205
- .filter(f => f.endsWith('.jsonl'));
206
-
207
- for (const file of files) {
208
- const oldPath = path.join(projectDir, file);
209
-
210
- try {
211
- // 从会话文件提取 cwd
212
- const cwd = extractCwdFromSession(oldPath);
213
-
214
- if (!cwd || !fs.existsSync(cwd)) {
215
- results.skipped.push({
216
- file,
217
- reason: 'Invalid or missing cwd',
218
- oldPath
219
- });
220
- continue;
221
- }
222
-
223
- // 目标路径: {cwd}/.claude/sessions/{file}
224
- const targetDir = path.join(cwd, '.claude', 'sessions');
225
- const newPath = path.join(targetDir, file);
226
-
227
- // 如果文件已存在,跳过
228
- if (fs.existsSync(newPath)) {
229
- results.skipped.push({
230
- file,
231
- reason: 'Already exists at target',
232
- oldPath,
233
- newPath
234
- });
235
- continue;
236
- }
237
-
238
- if (!dryRun) {
239
- // 确保目标目录存在
240
- if (!fs.existsSync(targetDir)) {
241
- fs.mkdirSync(targetDir, { recursive: true });
242
- }
243
-
244
- // 复制文件(保留原文件)
245
- fs.copyFileSync(oldPath, newPath);
246
- console.log(`[Migration] Migrated: ${file} -> ${newPath}`);
247
- }
248
-
249
- results.migrated.push({
250
- file,
251
- oldPath,
252
- newPath,
253
- cwd
254
- });
255
- } catch (err) {
256
- results.errors.push({
257
- file,
258
- oldPath,
259
- error: err.message
260
- });
261
- }
262
- }
263
- }
264
-
265
- return {
266
- success: true,
267
- dryRun,
268
- migratedCount: results.migrated.length,
269
- skippedCount: results.skipped.length,
270
- errorCount: results.errors.length,
271
- results
272
- };
273
- } catch (err) {
274
- return {
275
- success: false,
276
- error: err.message
277
- };
278
- }
279
- }
280
-
281
- /**
282
- * 清理旧的全局目录中的会话文件
283
- * @param {Object} options - 清理选项
284
- * @returns {Object} 清理结果
285
- */
286
- function cleanLegacySessionFiles(options = {}) {
287
- const {
288
- dryRun = false, // 是否只是预演,不实际删除
289
- projectNames = null // 指定要清理的项目名称列表,null 表示全部
290
- } = options;
291
-
292
- try {
293
- const legacyProjectsDir = path.join(os.homedir(), '.claude', 'projects');
294
-
295
- if (!fs.existsSync(legacyProjectsDir)) {
296
- return {
297
- success: true,
298
- message: 'No legacy directory found',
299
- deleted: 0
300
- };
301
- }
302
-
303
- const results = {
304
- dryRun,
305
- deleted: [],
306
- errors: [],
307
- totalSize: 0
308
- };
309
-
310
- const entries = fs.readdirSync(legacyProjectsDir, { withFileTypes: true });
311
-
312
- for (const entry of entries) {
313
- if (!entry.isDirectory()) continue;
314
-
315
- const projectName = entry.name;
316
-
317
- // 如果指定了项目列表,只清理列表中的项目
318
- if (projectNames && !projectNames.includes(projectName)) {
319
- continue;
320
- }
321
-
322
- const projectDir = path.join(legacyProjectsDir, projectName);
323
- const files = fs.readdirSync(projectDir);
324
-
325
- for (const file of files) {
326
- const filePath = path.join(projectDir, file);
327
-
328
- try {
329
- const stats = fs.statSync(filePath);
330
- results.totalSize += stats.size;
331
-
332
- if (!dryRun) {
333
- fs.unlinkSync(filePath);
334
- }
335
-
336
- results.deleted.push({
337
- projectName,
338
- file,
339
- size: stats.size
340
- });
341
- } catch (err) {
342
- results.errors.push({
343
- projectName,
344
- file,
345
- error: err.message
346
- });
347
- }
348
- }
349
-
350
- // 如果项目目录为空,删除目录
351
- if (!dryRun) {
352
- try {
353
- const remainingFiles = fs.readdirSync(projectDir);
354
- if (remainingFiles.length === 0) {
355
- fs.rmdirSync(projectDir);
356
- console.log(`[Cleanup] Removed empty directory: ${projectDir}`);
357
- }
358
- } catch (err) {
359
- console.warn(`[Cleanup] Failed to remove directory ${projectDir}:`, err.message);
360
- }
361
- }
362
- }
363
-
364
- // 如果 projects 目录为空,删除它
365
- if (!dryRun) {
366
- try {
367
- const remainingProjects = fs.readdirSync(legacyProjectsDir);
368
- if (remainingProjects.length === 0) {
369
- fs.rmdirSync(legacyProjectsDir);
370
- console.log(`[Cleanup] Removed empty legacy directory: ${legacyProjectsDir}`);
371
- }
372
- } catch (err) {
373
- console.warn(`[Cleanup] Failed to remove legacy directory:`, err.message);
374
- }
375
- }
376
-
377
- return {
378
- success: true,
379
- dryRun,
380
- deletedCount: results.deleted.length,
381
- errorCount: results.errors.length,
382
- totalSize: results.totalSize,
383
- results
384
- };
385
- } catch (err) {
386
- return {
387
- success: false,
388
- error: err.message
389
- };
390
- }
391
- }
392
-
393
82
  module.exports = {
394
83
  ensureProjectClaudeDir,
395
- healthCheckAllProjects,
396
- scanLegacySessionFiles,
397
- migrateSessionFiles,
398
- cleanLegacySessionFiles
84
+ healthCheckAllProjects
399
85
  };
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Permission Templates Service
3
+ *
4
+ * 管理权限配置模版的 CRUD 操作
5
+ * 支持内置模版和自定义模版
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const { PATHS } = require('../../config/paths');
11
+
12
+ // 权限模版存储文件
13
+ const TEMPLATES_FILE = path.join(PATHS.config, 'permission-templates.json');
14
+
15
+ // 内置权限模版
16
+ const BUILTIN_TEMPLATES = [
17
+ {
18
+ id: 'safe',
19
+ name: '安全模式',
20
+ description: '仅允许只读命令,危险操作需要确认',
21
+ permissions: {
22
+ allow: [
23
+ 'Bash(cat:*)',
24
+ 'Bash(ls:*)',
25
+ 'Bash(pwd)',
26
+ 'Bash(echo:*)',
27
+ 'Bash(head:*)',
28
+ 'Bash(tail:*)',
29
+ 'Bash(grep:*)',
30
+ 'Read(*)'
31
+ ],
32
+ deny: [
33
+ 'Bash(rm:*)',
34
+ 'Bash(sudo:*)',
35
+ 'Bash(git push:*)',
36
+ 'Bash(git reset --hard:*)',
37
+ 'Bash(chmod:*)',
38
+ 'Bash(chown:*)',
39
+ 'Edit(*)'
40
+ ]
41
+ },
42
+ isBuiltin: true
43
+ },
44
+ {
45
+ id: 'balanced',
46
+ name: '平衡模式',
47
+ description: '允许常用开发命令,危险操作需要确认',
48
+ permissions: {
49
+ allow: [
50
+ 'Bash(cat:*)',
51
+ 'Bash(ls:*)',
52
+ 'Bash(pwd)',
53
+ 'Bash(echo:*)',
54
+ 'Bash(head:*)',
55
+ 'Bash(tail:*)',
56
+ 'Bash(grep:*)',
57
+ 'Bash(find:*)',
58
+ 'Bash(git status)',
59
+ 'Bash(git diff:*)',
60
+ 'Bash(git log:*)',
61
+ 'Bash(npm run:*)',
62
+ 'Bash(pnpm:*)',
63
+ 'Bash(yarn:*)',
64
+ 'Read(*)',
65
+ 'Edit(*)'
66
+ ],
67
+ deny: [
68
+ 'Bash(rm -rf:*)',
69
+ 'Bash(sudo:*)',
70
+ 'Bash(git push --force:*)',
71
+ 'Bash(git reset --hard:*)'
72
+ ]
73
+ },
74
+ isBuiltin: true
75
+ },
76
+ {
77
+ id: 'permissive',
78
+ name: '宽松模式',
79
+ description: '允许大多数命令,仅阻止极度危险的操作',
80
+ permissions: {
81
+ allow: [
82
+ 'Bash(*)',
83
+ 'Read(*)',
84
+ 'Edit(*)'
85
+ ],
86
+ deny: [
87
+ 'Bash(rm -rf /*)',
88
+ 'Bash(sudo rm -rf:*)'
89
+ ]
90
+ },
91
+ isBuiltin: true
92
+ },
93
+ {
94
+ id: 'mcp-full',
95
+ name: 'MCP 全功能',
96
+ description: '允许所有 MCP 工具和常用命令,适合使用 MCP 服务器的项目',
97
+ permissions: {
98
+ allow: [
99
+ 'Read(*)',
100
+ 'Edit(*)',
101
+ 'Bash(cat:*)',
102
+ 'Bash(ls:*)',
103
+ 'Bash(find:*)',
104
+ 'Bash(grep:*)',
105
+ 'Bash(tree:*)',
106
+ 'Bash(git:*)',
107
+ 'Bash(npm:*)',
108
+ 'Bash(pnpm:*)',
109
+ 'Bash(yarn:*)',
110
+ 'WebSearch',
111
+ 'mcp__Serena__*',
112
+ 'mcp__fetch__fetch',
113
+ 'mcp__memory__*',
114
+ 'mcp__github__*',
115
+ 'mcp__context7__*'
116
+ ],
117
+ deny: [
118
+ 'Bash(rm -rf:*)',
119
+ 'Bash(sudo:*)'
120
+ ]
121
+ },
122
+ isBuiltin: true
123
+ }
124
+ ];
125
+
126
+ /**
127
+ * 确保配置目录存在
128
+ */
129
+ function ensureDir(dirPath) {
130
+ if (!fs.existsSync(dirPath)) {
131
+ fs.mkdirSync(dirPath, { recursive: true });
132
+ }
133
+ }
134
+
135
+ /**
136
+ * 加载所有模版(内置 + 自定义)
137
+ */
138
+ /**
139
+ * 加载所有模版(内置 + 自定义)
140
+ * 内置模版可被用户覆盖修改
141
+ */
142
+ /**
143
+ * 加载所有模版(内置 + 自定义)
144
+ * 内置模版可被用户覆盖修改或隐藏
145
+ */
146
+ function loadTemplates() {
147
+ try {
148
+ if (fs.existsSync(TEMPLATES_FILE)) {
149
+ const content = fs.readFileSync(TEMPLATES_FILE, 'utf8');
150
+ const data = JSON.parse(content);
151
+
152
+ // 分离自定义模版和隐藏标记
153
+ const customTemplates = (data.custom || []).filter(t => !t._hidden);
154
+ const hiddenIds = (data.custom || []).filter(t => t._hidden).map(t => t.id);
155
+
156
+ const customIds = customTemplates.map(t => t.id);
157
+
158
+ // 过滤掉已被自定义覆盖或隐藏的内置模版
159
+ const effectiveBuiltin = BUILTIN_TEMPLATES.filter(t =>
160
+ !customIds.includes(t.id) && !hiddenIds.includes(t.id)
161
+ );
162
+
163
+ return {
164
+ builtin: effectiveBuiltin,
165
+ custom: customTemplates,
166
+ all: [...customTemplates, ...effectiveBuiltin] // 自定义优先
167
+ };
168
+ }
169
+ } catch (error) {
170
+ console.error('[PermissionTemplates] 加载模版失败:', error.message);
171
+ }
172
+
173
+ return {
174
+ builtin: BUILTIN_TEMPLATES,
175
+ custom: [],
176
+ all: BUILTIN_TEMPLATES
177
+ };
178
+ }
179
+
180
+ /**
181
+ * 保存自定义模版
182
+ */
183
+ function saveCustomTemplates(customTemplates) {
184
+ ensureDir(PATHS.config);
185
+ fs.writeFileSync(TEMPLATES_FILE, JSON.stringify({ custom: customTemplates }, null, 2), 'utf8');
186
+ }
187
+
188
+ /**
189
+ * 获取所有模版
190
+ */
191
+ /**
192
+ * 获取所有模版(合并后的最终列表)
193
+ */
194
+ function getAllTemplates() {
195
+ const { all } = loadTemplates();
196
+ return all;
197
+ }
198
+
199
+ /**
200
+ * 根据 ID 获取模版
201
+ */
202
+ function getTemplateById(id) {
203
+ const templates = getAllTemplates();
204
+ return templates.find(t => t.id === id) || null;
205
+ }
206
+
207
+ /**
208
+ * 创建自定义模版
209
+ */
210
+ function createTemplate(template) {
211
+ const { custom } = loadTemplates();
212
+
213
+ // 验证必填字段
214
+ if (!template.name || !template.name.trim()) {
215
+ throw new Error('模版名称不能为空');
216
+ }
217
+
218
+ // 生成唯一 ID
219
+ const id = `custom-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
220
+
221
+ const newTemplate = {
222
+ id,
223
+ name: template.name.trim(),
224
+ description: template.description || '',
225
+ permissions: {
226
+ allow: template.permissions?.allow || [],
227
+ deny: template.permissions?.deny || []
228
+ },
229
+ isBuiltin: false,
230
+ createdAt: new Date().toISOString()
231
+ };
232
+
233
+ custom.push(newTemplate);
234
+ saveCustomTemplates(custom);
235
+
236
+ return newTemplate;
237
+ }
238
+
239
+ /**
240
+ * 更新自定义模版
241
+ */
242
+ /**
243
+ * 更新权限模版(包括内置模版)
244
+ * 编辑内置模版时,会在自定义列表中创建覆盖版本
245
+ */
246
+ function updateTemplate(id, updates) {
247
+ const { custom, all } = loadTemplates();
248
+
249
+ // 查找模版(先查自定义,再查所有)
250
+ const existingTemplate = all.find(t => t.id === id);
251
+ if (!existingTemplate) {
252
+ throw new Error('模版不存在');
253
+ }
254
+
255
+ // 验证名称
256
+ if (updates.name !== undefined && !updates.name.trim()) {
257
+ throw new Error('模版名称不能为空');
258
+ }
259
+
260
+ const updated = {
261
+ ...existingTemplate,
262
+ name: updates.name?.trim() ?? existingTemplate.name,
263
+ description: updates.description ?? existingTemplate.description,
264
+ permissions: updates.permissions ?? existingTemplate.permissions,
265
+ isBuiltin: false, // 编辑后标记为自定义(覆盖内置)
266
+ updatedAt: new Date().toISOString()
267
+ };
268
+
269
+ // 如果是编辑内置模版,则添加到自定义列表中(覆盖)
270
+ const customIndex = custom.findIndex(t => t.id === id);
271
+ if (customIndex !== -1) {
272
+ custom[customIndex] = updated;
273
+ } else {
274
+ custom.push(updated);
275
+ }
276
+
277
+ saveCustomTemplates(custom);
278
+ return updated;
279
+ }
280
+
281
+ /**
282
+ * 删除自定义模版
283
+ */
284
+ /**
285
+ * 删除权限模版(包括内置模版)
286
+ * 删除内置模版时,会在自定义列表中添加隐藏标记
287
+ */
288
+ /**
289
+ * 删除权限模版(包括内置模版)
290
+ * 删除内置模版时,会在自定义列表中添加隐藏标记
291
+ */
292
+ function deleteTemplate(id) {
293
+ try {
294
+ if (!fs.existsSync(TEMPLATES_FILE)) {
295
+ ensureDir(PATHS.config);
296
+ fs.writeFileSync(TEMPLATES_FILE, JSON.stringify({ custom: [] }, null, 2), 'utf8');
297
+ }
298
+
299
+ const content = fs.readFileSync(TEMPLATES_FILE, 'utf8');
300
+ const data = JSON.parse(content);
301
+ const customList = data.custom || [];
302
+
303
+ // 检查是否为内置模版
304
+ const isBuiltin = BUILTIN_TEMPLATES.some(t => t.id === id);
305
+
306
+ if (isBuiltin) {
307
+ // 内置模版:添加隐藏标记
308
+ const hideMarker = {
309
+ id,
310
+ _hidden: true,
311
+ deletedAt: new Date().toISOString()
312
+ };
313
+ customList.push(hideMarker);
314
+ fs.writeFileSync(TEMPLATES_FILE, JSON.stringify({ custom: customList }, null, 2), 'utf8');
315
+ } else {
316
+ // 自定义模版:直接删除
317
+ const index = customList.findIndex(t => t.id === id && !t._hidden);
318
+ if (index === -1) {
319
+ throw new Error('模版不存在');
320
+ }
321
+ customList.splice(index, 1);
322
+ fs.writeFileSync(TEMPLATES_FILE, JSON.stringify({ custom: customList }, null, 2), 'utf8');
323
+ }
324
+
325
+ return true;
326
+ } catch (error) {
327
+ if (error.message === '模版不存在') throw error;
328
+ throw new Error('删除模版失败: ' + error.message);
329
+ }
330
+ }
331
+
332
+ module.exports = {
333
+ getAllTemplates,
334
+ getTemplateById,
335
+ createTemplate,
336
+ updateTemplate,
337
+ deleteTemplate,
338
+ BUILTIN_TEMPLATES
339
+ };