@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.
- package/CHANGELOG.md +44 -0
- package/README.md +12 -14
- package/dist/web/assets/index-Bu1oPcKu.js +4009 -0
- package/dist/web/assets/index-XSok7-mN.css +41 -0
- package/dist/web/index.html +2 -2
- package/package.json +5 -4
- package/src/config/default.js +1 -1
- package/src/index.js +2 -2
- package/src/server/api/agents.js +188 -0
- package/src/server/api/commands.js +261 -0
- package/src/server/api/config-export.js +122 -0
- package/src/server/api/config-templates.js +26 -5
- package/src/server/api/health-check.js +1 -89
- package/src/server/api/permissions.js +370 -0
- package/src/server/api/rules.js +188 -0
- package/src/server/api/skills.js +66 -14
- package/src/server/api/workspaces.js +30 -55
- package/src/server/index.js +7 -11
- package/src/server/services/agents-service.js +179 -1
- package/src/server/services/commands-service.js +231 -47
- package/src/server/services/config-export-service.js +209 -0
- package/src/server/services/config-templates-service.js +481 -107
- package/src/server/services/format-converter.js +506 -0
- package/src/server/services/health-check.js +1 -315
- package/src/server/services/permission-templates-service.js +339 -0
- package/src/server/services/repo-scanner-base.js +678 -0
- package/src/server/services/rules-service.js +179 -1
- package/src/server/services/skill-service.js +114 -61
- package/src/server/services/workspace-service.js +52 -1
- package/dist/web/assets/index-D1AYlFLZ.js +0 -3220
- package/dist/web/assets/index-aL3cKxSK.css +0 -41
- package/docs/CHANGELOG.md +0 -582
- package/docs/DIRECTORY_MIGRATION.md +0 -112
- package/docs/PROJECT_STRUCTURE.md +0 -396
package/src/server/api/skills.js
CHANGED
|
@@ -83,11 +83,13 @@ router.get('/installed', (req, res) => {
|
|
|
83
83
|
/**
|
|
84
84
|
* 安装技能
|
|
85
85
|
* POST /api/skills/install
|
|
86
|
-
* Body: { directory, repo: { owner, name, branch } }
|
|
86
|
+
* Body: { directory, fullDirectory, repo: { owner, name, branch } }
|
|
87
|
+
* - directory: 本地安装目录(相对路径)
|
|
88
|
+
* - fullDirectory: 仓库中的完整路径(当指定了仓库子目录时使用)
|
|
87
89
|
*/
|
|
88
90
|
router.post('/install', async (req, res) => {
|
|
89
91
|
try {
|
|
90
|
-
const { directory, repo } = req.body;
|
|
92
|
+
const { directory, fullDirectory, repo } = req.body;
|
|
91
93
|
|
|
92
94
|
if (!directory) {
|
|
93
95
|
return res.status(400).json({
|
|
@@ -103,11 +105,15 @@ router.post('/install', async (req, res) => {
|
|
|
103
105
|
});
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
const result = await skillService.installSkill(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
const result = await skillService.installSkill(
|
|
109
|
+
directory,
|
|
110
|
+
{
|
|
111
|
+
owner: repo.owner,
|
|
112
|
+
name: repo.name,
|
|
113
|
+
branch: repo.branch || 'main'
|
|
114
|
+
},
|
|
115
|
+
fullDirectory || null // 传递 fullDirectory 用于从仓库子目录下载
|
|
116
|
+
);
|
|
111
117
|
|
|
112
118
|
res.json({
|
|
113
119
|
success: true,
|
|
@@ -227,11 +233,12 @@ router.get('/repos', (req, res) => {
|
|
|
227
233
|
/**
|
|
228
234
|
* 添加仓库
|
|
229
235
|
* POST /api/skills/repos
|
|
230
|
-
* Body: { owner, name, branch, enabled }
|
|
236
|
+
* Body: { owner, name, branch, directory, enabled }
|
|
237
|
+
* - directory: 可选,指定扫描的子目录路径
|
|
231
238
|
*/
|
|
232
239
|
router.post('/repos', (req, res) => {
|
|
233
240
|
try {
|
|
234
|
-
const { owner, name, branch = 'main', enabled = true } = req.body;
|
|
241
|
+
const { owner, name, branch = 'main', directory = '', enabled = true } = req.body;
|
|
235
242
|
|
|
236
243
|
if (!owner || !name) {
|
|
237
244
|
return res.status(400).json({
|
|
@@ -240,7 +247,7 @@ router.post('/repos', (req, res) => {
|
|
|
240
247
|
});
|
|
241
248
|
}
|
|
242
249
|
|
|
243
|
-
const repos = skillService.addRepo({ owner, name, branch, enabled });
|
|
250
|
+
const repos = skillService.addRepo({ owner, name, branch, directory, enabled });
|
|
244
251
|
|
|
245
252
|
res.json({
|
|
246
253
|
success: true,
|
|
@@ -258,11 +265,13 @@ router.post('/repos', (req, res) => {
|
|
|
258
265
|
/**
|
|
259
266
|
* 删除仓库
|
|
260
267
|
* DELETE /api/skills/repos/:owner/:name
|
|
268
|
+
* Query: directory - 可选,子目录路径
|
|
261
269
|
*/
|
|
262
270
|
router.delete('/repos/:owner/:name', (req, res) => {
|
|
263
271
|
try {
|
|
264
272
|
const { owner, name } = req.params;
|
|
265
|
-
const
|
|
273
|
+
const { directory = '' } = req.query;
|
|
274
|
+
const repos = skillService.removeRepo(owner, name, directory);
|
|
266
275
|
|
|
267
276
|
res.json({
|
|
268
277
|
success: true,
|
|
@@ -280,14 +289,15 @@ router.delete('/repos/:owner/:name', (req, res) => {
|
|
|
280
289
|
/**
|
|
281
290
|
* 切换仓库启用状态
|
|
282
291
|
* PUT /api/skills/repos/:owner/:name/toggle
|
|
283
|
-
* Body: { enabled }
|
|
292
|
+
* Body: { enabled, directory }
|
|
293
|
+
* - directory: 可选,子目录路径
|
|
284
294
|
*/
|
|
285
295
|
router.put('/repos/:owner/:name/toggle', (req, res) => {
|
|
286
296
|
try {
|
|
287
297
|
const { owner, name } = req.params;
|
|
288
|
-
const { enabled } = req.body;
|
|
298
|
+
const { enabled, directory = '' } = req.body;
|
|
289
299
|
|
|
290
|
-
const repos = skillService.toggleRepo(owner, name, enabled);
|
|
300
|
+
const repos = skillService.toggleRepo(owner, name, directory, enabled);
|
|
291
301
|
|
|
292
302
|
res.json({
|
|
293
303
|
success: true,
|
|
@@ -302,4 +312,46 @@ router.put('/repos/:owner/:name/toggle', (req, res) => {
|
|
|
302
312
|
}
|
|
303
313
|
});
|
|
304
314
|
|
|
315
|
+
// ==================== 格式转换 API ====================
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* 转换技能格式
|
|
319
|
+
* POST /api/skills/convert
|
|
320
|
+
* Body: { content, targetFormat }
|
|
321
|
+
* - content: 技能内容
|
|
322
|
+
* - targetFormat: 目标格式 ('claude' | 'codex')
|
|
323
|
+
*/
|
|
324
|
+
router.post('/convert', (req, res) => {
|
|
325
|
+
try {
|
|
326
|
+
const { content, targetFormat } = req.body;
|
|
327
|
+
|
|
328
|
+
if (!content) {
|
|
329
|
+
return res.status(400).json({
|
|
330
|
+
success: false,
|
|
331
|
+
message: '请提供技能内容'
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (!['claude', 'codex'].includes(targetFormat)) {
|
|
336
|
+
return res.status(400).json({
|
|
337
|
+
success: false,
|
|
338
|
+
message: '目标格式必须是 claude 或 codex'
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const result = skillService.convertSkillFormat(content, targetFormat);
|
|
343
|
+
|
|
344
|
+
res.json({
|
|
345
|
+
success: true,
|
|
346
|
+
...result
|
|
347
|
+
});
|
|
348
|
+
} catch (err) {
|
|
349
|
+
console.error('[Skills API] Convert skill error:', err);
|
|
350
|
+
res.status(500).json({
|
|
351
|
+
success: false,
|
|
352
|
+
message: err.message
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
305
357
|
module.exports = router;
|
|
@@ -105,12 +105,38 @@ router.get('/check-git/*', (req, res) => {
|
|
|
105
105
|
}
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* GET /api/workspaces/available-projects
|
|
110
|
+
* 获取所有渠道(Claude/Codex/Gemini)的项目并集
|
|
111
|
+
*/
|
|
112
|
+
router.get('/available-projects', (req, res) => {
|
|
113
|
+
try {
|
|
114
|
+
const projects = workspaceService.getAllAvailableProjects();
|
|
115
|
+
res.json({
|
|
116
|
+
success: true,
|
|
117
|
+
data: projects
|
|
118
|
+
});
|
|
119
|
+
} catch (error) {
|
|
120
|
+
res.status(500).json({
|
|
121
|
+
success: false,
|
|
122
|
+
message: error.message
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
108
127
|
/**
|
|
109
128
|
* GET /api/workspaces/:id
|
|
110
129
|
* 获取单个工作区详情
|
|
111
130
|
*/
|
|
112
|
-
|
|
131
|
+
// 注意:此路由需要放在所有静态子路由之后,避免把 /available-projects 等路径当成 id
|
|
132
|
+
router.get('/:id', (req, res, next) => {
|
|
113
133
|
try {
|
|
134
|
+
// 兜底:即便路由顺序被改动,也避免把保留路径当成工作区 ID
|
|
135
|
+
const reservedIds = new Set(['available-projects', 'read-file']);
|
|
136
|
+
if (reservedIds.has(req.params.id)) {
|
|
137
|
+
return next();
|
|
138
|
+
}
|
|
139
|
+
|
|
114
140
|
const workspace = workspaceService.getWorkspace(req.params.id);
|
|
115
141
|
if (!workspace) {
|
|
116
142
|
return res.status(404).json({
|
|
@@ -147,7 +173,7 @@ router.get('/:id', (req, res) => {
|
|
|
147
173
|
*/
|
|
148
174
|
router.post('/', (req, res) => {
|
|
149
175
|
try {
|
|
150
|
-
const { name, description, baseDir, projects, configTemplateId } = req.body;
|
|
176
|
+
const { name, description, baseDir, projects, configTemplateId, permissionTemplate } = req.body;
|
|
151
177
|
|
|
152
178
|
if (!name || !name.trim()) {
|
|
153
179
|
return res.status(400).json({
|
|
@@ -185,7 +211,8 @@ router.post('/', (req, res) => {
|
|
|
185
211
|
description,
|
|
186
212
|
baseDir,
|
|
187
213
|
projects,
|
|
188
|
-
configTemplateId
|
|
214
|
+
configTemplateId,
|
|
215
|
+
permissionTemplate
|
|
189
216
|
});
|
|
190
217
|
|
|
191
218
|
res.json({
|
|
@@ -322,25 +349,6 @@ router.delete('/:id/projects/:projectName', (req, res) => {
|
|
|
322
349
|
}
|
|
323
350
|
});
|
|
324
351
|
|
|
325
|
-
/**
|
|
326
|
-
* GET /api/workspaces/available-projects
|
|
327
|
-
* 获取所有渠道(Claude/Codex/Gemini)的项目并集
|
|
328
|
-
*/
|
|
329
|
-
router.get('/available-projects', (req, res) => {
|
|
330
|
-
try {
|
|
331
|
-
const projects = workspaceService.getAllAvailableProjects();
|
|
332
|
-
res.json({
|
|
333
|
-
success: true,
|
|
334
|
-
data: projects
|
|
335
|
-
});
|
|
336
|
-
} catch (error) {
|
|
337
|
-
res.status(500).json({
|
|
338
|
-
success: false,
|
|
339
|
-
message: error.message
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
|
|
344
352
|
/**
|
|
345
353
|
* POST /api/workspaces/:id/launch
|
|
346
354
|
* 获取在工作区启动 CLI 工具的命令
|
|
@@ -371,37 +379,4 @@ router.post('/:id/launch', (req, res) => {
|
|
|
371
379
|
}
|
|
372
380
|
});
|
|
373
381
|
|
|
374
|
-
/**
|
|
375
|
-
* GET /api/workspaces/check-git/:projectPath
|
|
376
|
-
* 检查项目是否是 git 仓库并获取 worktrees
|
|
377
|
-
*/
|
|
378
|
-
router.get('/check-git/*', (req, res) => {
|
|
379
|
-
try {
|
|
380
|
-
const projectPath = req.params[0];
|
|
381
|
-
|
|
382
|
-
if (!projectPath) {
|
|
383
|
-
return res.status(400).json({
|
|
384
|
-
success: false,
|
|
385
|
-
message: '项目路径不能为空'
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const isGit = workspaceService.isGitRepo(projectPath);
|
|
390
|
-
const worktrees = isGit ? workspaceService.getGitWorktrees(projectPath) : [];
|
|
391
|
-
|
|
392
|
-
res.json({
|
|
393
|
-
success: true,
|
|
394
|
-
data: {
|
|
395
|
-
isGitRepo: isGit,
|
|
396
|
-
worktrees
|
|
397
|
-
}
|
|
398
|
-
});
|
|
399
|
-
} catch (error) {
|
|
400
|
-
res.status(500).json({
|
|
401
|
-
success: false,
|
|
402
|
-
message: error.message
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
});
|
|
406
|
-
|
|
407
382
|
module.exports = router;
|
package/src/server/index.js
CHANGED
|
@@ -143,6 +143,12 @@ async function startServer(port) {
|
|
|
143
143
|
// 配置模板 API
|
|
144
144
|
app.use('/api/config-templates', require('./api/config-templates'));
|
|
145
145
|
|
|
146
|
+
// 命令执行权限 API
|
|
147
|
+
app.use('/api/permissions', require('./api/permissions'));
|
|
148
|
+
|
|
149
|
+
// 配置导出/导入 API
|
|
150
|
+
app.use('/api/config-export', require('./api/config-export'));
|
|
151
|
+
|
|
146
152
|
// 健康检查 API
|
|
147
153
|
app.use('/api/health-check', require('./api/health-check')(config));
|
|
148
154
|
|
|
@@ -256,7 +262,7 @@ function autoRestoreProxies() {
|
|
|
256
262
|
|
|
257
263
|
// 启动时执行健康检查
|
|
258
264
|
function performStartupHealthCheck() {
|
|
259
|
-
const { healthCheckAllProjects
|
|
265
|
+
const { healthCheckAllProjects } = require('./services/health-check');
|
|
260
266
|
const { getProjects } = require('./services/sessions');
|
|
261
267
|
|
|
262
268
|
try {
|
|
@@ -286,16 +292,6 @@ function performStartupHealthCheck() {
|
|
|
286
292
|
console.log(chalk.green(` ✓ 所有 ${healthResult.summary.healthy} 个项目状态正常`));
|
|
287
293
|
}
|
|
288
294
|
|
|
289
|
-
// 扫描旧文件
|
|
290
|
-
const legacyResult = scanLegacySessionFiles();
|
|
291
|
-
|
|
292
|
-
if (legacyResult.found && legacyResult.projectCount > 0) {
|
|
293
|
-
console.log(chalk.yellow(`\n ⚠ 发现 ${legacyResult.projectCount} 个项目的旧会话文件在全局目录`));
|
|
294
|
-
console.log(chalk.gray(' 💡 提示: 可通过 Web UI 或 API 清理这些文件'));
|
|
295
|
-
console.log(chalk.gray(` - Web UI: 设置 -> 系统维护 -> 清理旧文件`));
|
|
296
|
-
console.log(chalk.gray(` - API: POST /api/health-check/clean-legacy`));
|
|
297
|
-
}
|
|
298
|
-
|
|
299
295
|
console.log('');
|
|
300
296
|
} catch (err) {
|
|
301
297
|
console.error(chalk.red(' ✗ 健康检查失败:'), err.message);
|
|
@@ -5,15 +5,21 @@
|
|
|
5
5
|
* 代理目录:
|
|
6
6
|
* - 用户级: ~/.claude/agents/
|
|
7
7
|
* - 项目级: .claude/agents/
|
|
8
|
+
*
|
|
9
|
+
* 支持从 GitHub 仓库扫描和安装代理
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
const fs = require('fs');
|
|
11
13
|
const path = require('path');
|
|
12
14
|
const os = require('os');
|
|
15
|
+
const { RepoScannerBase } = require('./repo-scanner-base');
|
|
13
16
|
|
|
14
17
|
// 代理目录路径
|
|
15
18
|
const USER_AGENTS_DIR = path.join(os.homedir(), '.claude', 'agents');
|
|
16
19
|
|
|
20
|
+
// 默认仓库源
|
|
21
|
+
const DEFAULT_REPOS = [];
|
|
22
|
+
|
|
17
23
|
/**
|
|
18
24
|
* 确保目录存在
|
|
19
25
|
*/
|
|
@@ -154,12 +160,97 @@ function scanAgentsDir(dir, basePath, scope) {
|
|
|
154
160
|
return agents;
|
|
155
161
|
}
|
|
156
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Agents 仓库扫描器
|
|
165
|
+
*/
|
|
166
|
+
class AgentsRepoScanner extends RepoScannerBase {
|
|
167
|
+
constructor() {
|
|
168
|
+
super({
|
|
169
|
+
type: 'agents',
|
|
170
|
+
installDir: USER_AGENTS_DIR,
|
|
171
|
+
markerFile: null, // 直接扫描 .md 文件
|
|
172
|
+
fileExtension: '.md',
|
|
173
|
+
defaultRepos: DEFAULT_REPOS
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 获取并解析单个代理文件
|
|
179
|
+
*/
|
|
180
|
+
async fetchAndParseItem(file, repo, baseDir) {
|
|
181
|
+
try {
|
|
182
|
+
// 计算相对路径
|
|
183
|
+
const relativePath = baseDir ? file.path.slice(baseDir.length + 1) : file.path;
|
|
184
|
+
const fileName = path.basename(file.path, '.md');
|
|
185
|
+
|
|
186
|
+
// 获取文件内容
|
|
187
|
+
const content = await this.fetchRawContent(repo, file.path);
|
|
188
|
+
const { frontmatter, body } = this.parseFrontmatter(content);
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
key: `${repo.owner}/${repo.name}:${relativePath}`,
|
|
192
|
+
name: frontmatter.name || fileName,
|
|
193
|
+
fileName,
|
|
194
|
+
scope: 'remote',
|
|
195
|
+
path: relativePath,
|
|
196
|
+
repoPath: file.path,
|
|
197
|
+
description: frontmatter.description || '',
|
|
198
|
+
tools: frontmatter.tools || '',
|
|
199
|
+
model: frontmatter.model || '',
|
|
200
|
+
permissionMode: frontmatter.permissionMode || '',
|
|
201
|
+
skills: frontmatter.skills || '',
|
|
202
|
+
systemPrompt: body,
|
|
203
|
+
fullContent: content,
|
|
204
|
+
installed: this.isInstalled(fileName),
|
|
205
|
+
readmeUrl: `https://github.com/${repo.owner}/${repo.name}/blob/${repo.branch}/${file.path}`,
|
|
206
|
+
repoOwner: repo.owner,
|
|
207
|
+
repoName: repo.name,
|
|
208
|
+
repoBranch: repo.branch,
|
|
209
|
+
repoDirectory: repo.directory || ''
|
|
210
|
+
};
|
|
211
|
+
} catch (err) {
|
|
212
|
+
console.warn(`[AgentsRepoScanner] Parse agent ${file.path} error:`, err.message);
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 检查代理是否已安装
|
|
219
|
+
*/
|
|
220
|
+
isInstalled(fileName) {
|
|
221
|
+
const fullPath = path.join(this.installDir, `${fileName}.md`);
|
|
222
|
+
return fs.existsSync(fullPath);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 获取去重 key
|
|
227
|
+
*/
|
|
228
|
+
getDedupeKey(item) {
|
|
229
|
+
return item.fileName.toLowerCase();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 安装代理
|
|
234
|
+
*/
|
|
235
|
+
async installAgent(item) {
|
|
236
|
+
const repo = {
|
|
237
|
+
owner: item.repoOwner,
|
|
238
|
+
name: item.repoName,
|
|
239
|
+
branch: item.repoBranch
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// 代理安装到根目录,使用文件名
|
|
243
|
+
return this.installFromRepo(item.repoPath, repo, `${item.fileName}.md`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
157
247
|
/**
|
|
158
248
|
* Agents 服务类
|
|
159
249
|
*/
|
|
160
250
|
class AgentsService {
|
|
161
251
|
constructor() {
|
|
162
252
|
this.userAgentsDir = USER_AGENTS_DIR;
|
|
253
|
+
this.repoScanner = new AgentsRepoScanner();
|
|
163
254
|
ensureDir(this.userAgentsDir);
|
|
164
255
|
}
|
|
165
256
|
|
|
@@ -192,6 +283,48 @@ class AgentsService {
|
|
|
192
283
|
};
|
|
193
284
|
}
|
|
194
285
|
|
|
286
|
+
/**
|
|
287
|
+
* 获取所有代理(包括远程仓库)
|
|
288
|
+
*/
|
|
289
|
+
async listAllAgents(projectPath = null, forceRefresh = false) {
|
|
290
|
+
// 获取本地代理
|
|
291
|
+
const { agents: localAgents, userCount, projectCount } = this.listAgents(projectPath);
|
|
292
|
+
|
|
293
|
+
// 获取远程代理
|
|
294
|
+
let remoteAgents = [];
|
|
295
|
+
try {
|
|
296
|
+
remoteAgents = await this.repoScanner.listRemoteItems(forceRefresh);
|
|
297
|
+
|
|
298
|
+
// 更新安装状态
|
|
299
|
+
for (const agent of remoteAgents) {
|
|
300
|
+
agent.installed = this.repoScanner.isInstalled(agent.fileName);
|
|
301
|
+
}
|
|
302
|
+
} catch (err) {
|
|
303
|
+
console.warn('[AgentsService] Failed to fetch remote agents:', err.message);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 合并列表(本地优先)
|
|
307
|
+
const allAgents = [...localAgents];
|
|
308
|
+
const localKeys = new Set(localAgents.map(a => a.fileName.toLowerCase()));
|
|
309
|
+
|
|
310
|
+
for (const remote of remoteAgents) {
|
|
311
|
+
if (!localKeys.has(remote.fileName.toLowerCase())) {
|
|
312
|
+
allAgents.push(remote);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// 排序
|
|
317
|
+
allAgents.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
agents: allAgents,
|
|
321
|
+
total: allAgents.length,
|
|
322
|
+
userCount,
|
|
323
|
+
projectCount,
|
|
324
|
+
remoteCount: remoteAgents.length
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
195
328
|
/**
|
|
196
329
|
* 获取单个代理详情
|
|
197
330
|
*/
|
|
@@ -347,8 +480,53 @@ class AgentsService {
|
|
|
347
480
|
models
|
|
348
481
|
};
|
|
349
482
|
}
|
|
483
|
+
|
|
484
|
+
// ==================== 仓库管理 ====================
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* 获取仓库列表
|
|
488
|
+
*/
|
|
489
|
+
getRepos() {
|
|
490
|
+
return this.repoScanner.loadRepos();
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* 添加仓库
|
|
495
|
+
*/
|
|
496
|
+
addRepo(repo) {
|
|
497
|
+
return this.repoScanner.addRepo(repo);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* 删除仓库
|
|
502
|
+
*/
|
|
503
|
+
removeRepo(owner, name, directory = '') {
|
|
504
|
+
return this.repoScanner.removeRepo(owner, name, directory);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* 切换仓库启用状态
|
|
509
|
+
*/
|
|
510
|
+
toggleRepo(owner, name, directory = '', enabled) {
|
|
511
|
+
return this.repoScanner.toggleRepo(owner, name, directory, enabled);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* 从远程仓库安装代理
|
|
516
|
+
*/
|
|
517
|
+
async installFromRemote(agent) {
|
|
518
|
+
return this.repoScanner.installAgent(agent);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* 卸载代理
|
|
523
|
+
*/
|
|
524
|
+
uninstallAgent(fileName) {
|
|
525
|
+
return this.repoScanner.uninstall(`${fileName}.md`);
|
|
526
|
+
}
|
|
350
527
|
}
|
|
351
528
|
|
|
352
529
|
module.exports = {
|
|
353
|
-
AgentsService
|
|
530
|
+
AgentsService,
|
|
531
|
+
DEFAULT_REPOS
|
|
354
532
|
};
|