@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
|
@@ -5,15 +5,28 @@
|
|
|
5
5
|
* 命令目录:
|
|
6
6
|
* - 用户级: ~/.claude/commands/
|
|
7
7
|
* - 项目级: .claude/commands/
|
|
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');
|
|
16
|
+
const {
|
|
17
|
+
parseCommandContent,
|
|
18
|
+
detectCommandFormat,
|
|
19
|
+
convertCommandToCodex,
|
|
20
|
+
convertCommandToClaude,
|
|
21
|
+
parseFrontmatter
|
|
22
|
+
} = require('./format-converter');
|
|
13
23
|
|
|
14
24
|
// 命令目录路径
|
|
15
25
|
const USER_COMMANDS_DIR = path.join(os.homedir(), '.claude', 'commands');
|
|
16
26
|
|
|
27
|
+
// 默认仓库源
|
|
28
|
+
const DEFAULT_REPOS = [];
|
|
29
|
+
|
|
17
30
|
/**
|
|
18
31
|
* 确保目录存在
|
|
19
32
|
*/
|
|
@@ -24,51 +37,9 @@ function ensureDir(dirPath) {
|
|
|
24
37
|
}
|
|
25
38
|
|
|
26
39
|
/**
|
|
27
|
-
*
|
|
40
|
+
* 生成 frontmatter 字符串(用于命令创建/更新)
|
|
28
41
|
*/
|
|
29
|
-
function
|
|
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) {
|
|
42
|
+
function generateCommandFrontmatter(data) {
|
|
72
43
|
const lines = ['---'];
|
|
73
44
|
|
|
74
45
|
if (data.description) {
|
|
@@ -80,6 +51,15 @@ function generateFrontmatter(data) {
|
|
|
80
51
|
if (data['argument-hint']) {
|
|
81
52
|
lines.push(`argument-hint: ${data['argument-hint']}`);
|
|
82
53
|
}
|
|
54
|
+
if (data.model) {
|
|
55
|
+
lines.push(`model: ${data.model}`);
|
|
56
|
+
}
|
|
57
|
+
if (data.context) {
|
|
58
|
+
lines.push(`context: ${data.context}`);
|
|
59
|
+
}
|
|
60
|
+
if (data.agent) {
|
|
61
|
+
lines.push(`agent: ${data.agent}`);
|
|
62
|
+
}
|
|
83
63
|
|
|
84
64
|
lines.push('---');
|
|
85
65
|
return lines.join('\n');
|
|
@@ -141,12 +121,96 @@ function scanCommandsDir(dir, basePath, scope) {
|
|
|
141
121
|
return commands;
|
|
142
122
|
}
|
|
143
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Commands 仓库扫描器
|
|
126
|
+
*/
|
|
127
|
+
class CommandsRepoScanner extends RepoScannerBase {
|
|
128
|
+
constructor() {
|
|
129
|
+
super({
|
|
130
|
+
type: 'commands',
|
|
131
|
+
installDir: USER_COMMANDS_DIR,
|
|
132
|
+
markerFile: null, // 直接扫描 .md 文件
|
|
133
|
+
fileExtension: '.md',
|
|
134
|
+
defaultRepos: DEFAULT_REPOS
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 获取并解析单个命令文件
|
|
140
|
+
*/
|
|
141
|
+
async fetchAndParseItem(file, repo, baseDir) {
|
|
142
|
+
try {
|
|
143
|
+
// 计算相对路径
|
|
144
|
+
const relativePath = baseDir ? file.path.slice(baseDir.length + 1) : file.path;
|
|
145
|
+
const fileName = path.basename(file.path, '.md');
|
|
146
|
+
const namespace = path.dirname(relativePath);
|
|
147
|
+
|
|
148
|
+
// 获取文件内容
|
|
149
|
+
const content = await this.fetchRawContent(repo, file.path);
|
|
150
|
+
const { frontmatter, body } = this.parseFrontmatter(content);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
key: `${repo.owner}/${repo.name}:${relativePath}`,
|
|
154
|
+
name: fileName,
|
|
155
|
+
namespace: namespace === '.' ? null : namespace,
|
|
156
|
+
scope: 'remote',
|
|
157
|
+
path: relativePath,
|
|
158
|
+
repoPath: file.path,
|
|
159
|
+
description: frontmatter.description || '',
|
|
160
|
+
allowedTools: frontmatter['allowed-tools'] || '',
|
|
161
|
+
argumentHint: frontmatter['argument-hint'] || '',
|
|
162
|
+
body,
|
|
163
|
+
fullContent: content,
|
|
164
|
+
installed: this.isInstalled(relativePath),
|
|
165
|
+
readmeUrl: `https://github.com/${repo.owner}/${repo.name}/blob/${repo.branch}/${file.path}`,
|
|
166
|
+
repoOwner: repo.owner,
|
|
167
|
+
repoName: repo.name,
|
|
168
|
+
repoBranch: repo.branch,
|
|
169
|
+
repoDirectory: repo.directory || ''
|
|
170
|
+
};
|
|
171
|
+
} catch (err) {
|
|
172
|
+
console.warn(`[CommandsRepoScanner] Parse command ${file.path} error:`, err.message);
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 检查命令是否已安装
|
|
179
|
+
*/
|
|
180
|
+
isInstalled(relativePath) {
|
|
181
|
+
const fullPath = path.join(this.installDir, relativePath);
|
|
182
|
+
return fs.existsSync(fullPath);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 获取去重 key
|
|
187
|
+
*/
|
|
188
|
+
getDedupeKey(item) {
|
|
189
|
+
// 使用 namespace/name 作为去重 key
|
|
190
|
+
return item.namespace ? `${item.namespace}/${item.name}`.toLowerCase() : item.name.toLowerCase();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* 安装命令
|
|
195
|
+
*/
|
|
196
|
+
async installCommand(item) {
|
|
197
|
+
const repo = {
|
|
198
|
+
owner: item.repoOwner,
|
|
199
|
+
name: item.repoName,
|
|
200
|
+
branch: item.repoBranch
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
return this.installFromRepo(item.repoPath, repo, item.path);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
144
207
|
/**
|
|
145
208
|
* Commands 服务类
|
|
146
209
|
*/
|
|
147
210
|
class CommandsService {
|
|
148
211
|
constructor() {
|
|
149
212
|
this.userCommandsDir = USER_COMMANDS_DIR;
|
|
213
|
+
this.repoScanner = new CommandsRepoScanner();
|
|
150
214
|
ensureDir(this.userCommandsDir);
|
|
151
215
|
}
|
|
152
216
|
|
|
@@ -179,6 +243,52 @@ class CommandsService {
|
|
|
179
243
|
};
|
|
180
244
|
}
|
|
181
245
|
|
|
246
|
+
/**
|
|
247
|
+
* 获取所有命令(包括远程仓库)
|
|
248
|
+
* @param {boolean} forceRefresh - 强制刷新远程缓存
|
|
249
|
+
*/
|
|
250
|
+
async listAllCommands(projectPath = null, forceRefresh = false) {
|
|
251
|
+
// 获取本地命令
|
|
252
|
+
const { commands: localCommands, userCount, projectCount } = this.listCommands(projectPath);
|
|
253
|
+
|
|
254
|
+
// 获取远程命令
|
|
255
|
+
let remoteCommands = [];
|
|
256
|
+
try {
|
|
257
|
+
remoteCommands = await this.repoScanner.listRemoteItems(forceRefresh);
|
|
258
|
+
|
|
259
|
+
// 更新安装状态
|
|
260
|
+
for (const cmd of remoteCommands) {
|
|
261
|
+
cmd.installed = this.repoScanner.isInstalled(cmd.path);
|
|
262
|
+
}
|
|
263
|
+
} catch (err) {
|
|
264
|
+
console.warn('[CommandsService] Failed to fetch remote commands:', err.message);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 合并列表(本地优先)
|
|
268
|
+
const allCommands = [...localCommands];
|
|
269
|
+
const localKeys = new Set(localCommands.map(c =>
|
|
270
|
+
c.namespace ? `${c.namespace}/${c.name}`.toLowerCase() : c.name.toLowerCase()
|
|
271
|
+
));
|
|
272
|
+
|
|
273
|
+
for (const remote of remoteCommands) {
|
|
274
|
+
const key = remote.namespace ? `${remote.namespace}/${remote.name}`.toLowerCase() : remote.name.toLowerCase();
|
|
275
|
+
if (!localKeys.has(key)) {
|
|
276
|
+
allCommands.push(remote);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 排序
|
|
281
|
+
allCommands.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
commands: allCommands,
|
|
285
|
+
total: allCommands.length,
|
|
286
|
+
userCount,
|
|
287
|
+
projectCount,
|
|
288
|
+
remoteCount: remoteCommands.length
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
182
292
|
/**
|
|
183
293
|
* 获取单个命令详情
|
|
184
294
|
*/
|
|
@@ -250,7 +360,7 @@ class CommandsService {
|
|
|
250
360
|
|
|
251
361
|
let content = '';
|
|
252
362
|
if (Object.keys(frontmatterData).length > 0) {
|
|
253
|
-
content =
|
|
363
|
+
content = generateCommandFrontmatter(frontmatterData) + '\n\n';
|
|
254
364
|
}
|
|
255
365
|
content += body || '';
|
|
256
366
|
|
|
@@ -285,7 +395,7 @@ class CommandsService {
|
|
|
285
395
|
|
|
286
396
|
let content = '';
|
|
287
397
|
if (Object.keys(frontmatterData).length > 0) {
|
|
288
|
-
content =
|
|
398
|
+
content = generateCommandFrontmatter(frontmatterData) + '\n\n';
|
|
289
399
|
}
|
|
290
400
|
content += body || '';
|
|
291
401
|
|
|
@@ -353,8 +463,82 @@ class CommandsService {
|
|
|
353
463
|
namespaces
|
|
354
464
|
};
|
|
355
465
|
}
|
|
466
|
+
|
|
467
|
+
// ==================== 仓库管理 ====================
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* 获取仓库列表
|
|
471
|
+
*/
|
|
472
|
+
getRepos() {
|
|
473
|
+
return this.repoScanner.loadRepos();
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* 添加仓库
|
|
478
|
+
*/
|
|
479
|
+
addRepo(repo) {
|
|
480
|
+
return this.repoScanner.addRepo(repo);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* 删除仓库
|
|
485
|
+
*/
|
|
486
|
+
removeRepo(owner, name, directory = '') {
|
|
487
|
+
return this.repoScanner.removeRepo(owner, name, directory);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* 切换仓库启用状态
|
|
492
|
+
*/
|
|
493
|
+
toggleRepo(owner, name, directory = '', enabled) {
|
|
494
|
+
return this.repoScanner.toggleRepo(owner, name, directory, enabled);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* 从远程仓库安装命令
|
|
499
|
+
*/
|
|
500
|
+
async installFromRemote(command) {
|
|
501
|
+
return this.repoScanner.installCommand(command);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* 卸载命令
|
|
506
|
+
*/
|
|
507
|
+
uninstallCommand(relativePath) {
|
|
508
|
+
return this.repoScanner.uninstall(relativePath);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ==================== 格式转换 ====================
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* 转换命令格式
|
|
515
|
+
* @param {string} content - 命令内容
|
|
516
|
+
* @param {string} targetFormat - 目标格式 ('claude' | 'codex')
|
|
517
|
+
*/
|
|
518
|
+
convertCommandFormat(content, targetFormat) {
|
|
519
|
+
const sourceFormat = detectCommandFormat(content);
|
|
520
|
+
|
|
521
|
+
if (sourceFormat === targetFormat) {
|
|
522
|
+
return { content, warnings: [], format: targetFormat };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (targetFormat === 'codex') {
|
|
526
|
+
return convertCommandToCodex(content);
|
|
527
|
+
} else {
|
|
528
|
+
return convertCommandToClaude(content);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* 检测命令格式
|
|
534
|
+
* @param {string} content - 命令内容
|
|
535
|
+
*/
|
|
536
|
+
detectFormat(content) {
|
|
537
|
+
return detectCommandFormat(content);
|
|
538
|
+
}
|
|
356
539
|
}
|
|
357
540
|
|
|
358
541
|
module.exports = {
|
|
359
|
-
CommandsService
|
|
542
|
+
CommandsService,
|
|
543
|
+
DEFAULT_REPOS
|
|
360
544
|
};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置导出/导入服务
|
|
3
|
+
* 支持权限模板、配置模板、频道配置的导出与导入
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const permissionTemplatesService = require('./permission-templates-service');
|
|
9
|
+
const configTemplatesService = require('./config-templates-service');
|
|
10
|
+
const channelsService = require('./channels');
|
|
11
|
+
|
|
12
|
+
const CONFIG_VERSION = '1.0.0';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 导出所有配置为JSON
|
|
16
|
+
* @returns {Object} 配置导出对象
|
|
17
|
+
*/
|
|
18
|
+
function exportAllConfigs() {
|
|
19
|
+
try {
|
|
20
|
+
// 获取所有权限模板(只导出自定义模板)
|
|
21
|
+
const allPermissionTemplates = permissionTemplatesService.getAllTemplates();
|
|
22
|
+
const customPermissionTemplates = allPermissionTemplates.filter(t => !t.isBuiltin);
|
|
23
|
+
|
|
24
|
+
// 获取所有配置模板(只导出自定义模板)
|
|
25
|
+
const allConfigTemplates = configTemplatesService.getAllTemplates();
|
|
26
|
+
const customConfigTemplates = allConfigTemplates.filter(t => !t.isBuiltin);
|
|
27
|
+
|
|
28
|
+
// 获取所有频道配置
|
|
29
|
+
const channelsData = channelsService.getAllChannels();
|
|
30
|
+
const channels = channelsData?.channels || [];
|
|
31
|
+
|
|
32
|
+
const exportData = {
|
|
33
|
+
version: CONFIG_VERSION,
|
|
34
|
+
exportedAt: new Date().toISOString(),
|
|
35
|
+
data: {
|
|
36
|
+
permissionTemplates: customPermissionTemplates,
|
|
37
|
+
configTemplates: customConfigTemplates,
|
|
38
|
+
channels: channels || []
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
success: true,
|
|
44
|
+
data: exportData
|
|
45
|
+
};
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('[ConfigExport] 导出配置失败:', error);
|
|
48
|
+
return {
|
|
49
|
+
success: false,
|
|
50
|
+
message: error.message
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 导入配置
|
|
57
|
+
* @param {Object} importData - 导入的配置对象
|
|
58
|
+
* @param {Object} options - 导入选项 { overwrite: boolean }
|
|
59
|
+
* @returns {Object} 导入结果
|
|
60
|
+
*/
|
|
61
|
+
function importConfigs(importData, options = {}) {
|
|
62
|
+
const { overwrite = false } = options;
|
|
63
|
+
const results = {
|
|
64
|
+
permissionTemplates: { success: 0, failed: 0, skipped: 0 },
|
|
65
|
+
configTemplates: { success: 0, failed: 0, skipped: 0 },
|
|
66
|
+
channels: { success: 0, failed: 0, skipped: 0 }
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
// 验证导入数据格式
|
|
71
|
+
if (!importData || !importData.data) {
|
|
72
|
+
throw new Error('无效的导入数据格式');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const { permissionTemplates = [], configTemplates = [], channels = [] } = importData.data;
|
|
76
|
+
|
|
77
|
+
// 导入权限模板
|
|
78
|
+
for (const template of permissionTemplates) {
|
|
79
|
+
try {
|
|
80
|
+
const existing = permissionTemplatesService.getTemplateById(template.id);
|
|
81
|
+
|
|
82
|
+
if (existing && !overwrite) {
|
|
83
|
+
results.permissionTemplates.skipped++;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (existing && overwrite) {
|
|
88
|
+
permissionTemplatesService.updateTemplate(template.id, template);
|
|
89
|
+
} else {
|
|
90
|
+
// 创建新模板(使用原ID)
|
|
91
|
+
const newTemplate = {
|
|
92
|
+
...template,
|
|
93
|
+
isBuiltin: false,
|
|
94
|
+
importedAt: new Date().toISOString()
|
|
95
|
+
};
|
|
96
|
+
permissionTemplatesService.createTemplate(newTemplate);
|
|
97
|
+
}
|
|
98
|
+
results.permissionTemplates.success++;
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.error(`[ConfigImport] 导入权限模板失败: ${template.name}`, err);
|
|
101
|
+
results.permissionTemplates.failed++;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 导入配置模板
|
|
106
|
+
for (const template of configTemplates) {
|
|
107
|
+
try {
|
|
108
|
+
const existing = configTemplatesService.getTemplateById(template.id);
|
|
109
|
+
|
|
110
|
+
if (existing && !overwrite) {
|
|
111
|
+
results.configTemplates.skipped++;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (existing && overwrite) {
|
|
116
|
+
configTemplatesService.updateTemplate(template.id, template);
|
|
117
|
+
} else {
|
|
118
|
+
const newTemplate = {
|
|
119
|
+
...template,
|
|
120
|
+
isBuiltin: false,
|
|
121
|
+
importedAt: new Date().toISOString()
|
|
122
|
+
};
|
|
123
|
+
configTemplatesService.createTemplate(newTemplate);
|
|
124
|
+
}
|
|
125
|
+
results.configTemplates.success++;
|
|
126
|
+
} catch (err) {
|
|
127
|
+
console.error(`[ConfigImport] 导入配置模板失败: ${template.name}`, err);
|
|
128
|
+
results.configTemplates.failed++;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 导入频道配置
|
|
133
|
+
for (const channel of channels) {
|
|
134
|
+
try {
|
|
135
|
+
const channelsData = channelsService.getAllChannels();
|
|
136
|
+
const existingChannels = channelsData?.channels || [];
|
|
137
|
+
const existing = existingChannels.find(c => c.id === channel.id);
|
|
138
|
+
|
|
139
|
+
if (existing && !overwrite) {
|
|
140
|
+
results.channels.skipped++;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (existing && overwrite) {
|
|
145
|
+
channelsService.updateChannel(channel.id, channel);
|
|
146
|
+
} else {
|
|
147
|
+
// createChannel 需要单独的参数,不是一个对象
|
|
148
|
+
const { name, baseUrl, apiKey, websiteUrl, ...extraConfig } = channel;
|
|
149
|
+
channelsService.createChannel(name, baseUrl, apiKey, websiteUrl, extraConfig);
|
|
150
|
+
}
|
|
151
|
+
results.channels.success++;
|
|
152
|
+
} catch (err) {
|
|
153
|
+
console.error(`[ConfigImport] 导入频道失败: ${channel.name}`, err);
|
|
154
|
+
results.channels.failed++;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
success: true,
|
|
160
|
+
results,
|
|
161
|
+
message: generateImportSummary(results)
|
|
162
|
+
};
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error('[ConfigImport] 导入配置失败:', error);
|
|
165
|
+
return {
|
|
166
|
+
success: false,
|
|
167
|
+
message: error.message,
|
|
168
|
+
results
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 生成导入摘要消息
|
|
175
|
+
*/
|
|
176
|
+
function generateImportSummary(results) {
|
|
177
|
+
const parts = [];
|
|
178
|
+
|
|
179
|
+
if (results.permissionTemplates.success > 0) {
|
|
180
|
+
parts.push(`权限模板: ${results.permissionTemplates.success}成功`);
|
|
181
|
+
}
|
|
182
|
+
if (results.configTemplates.success > 0) {
|
|
183
|
+
parts.push(`配置模板: ${results.configTemplates.success}成功`);
|
|
184
|
+
}
|
|
185
|
+
if (results.channels.success > 0) {
|
|
186
|
+
parts.push(`频道: ${results.channels.success}成功`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const totalSkipped = results.permissionTemplates.skipped +
|
|
190
|
+
results.configTemplates.skipped +
|
|
191
|
+
results.channels.skipped;
|
|
192
|
+
if (totalSkipped > 0) {
|
|
193
|
+
parts.push(`${totalSkipped}项已跳过`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const totalFailed = results.permissionTemplates.failed +
|
|
197
|
+
results.configTemplates.failed +
|
|
198
|
+
results.channels.failed;
|
|
199
|
+
if (totalFailed > 0) {
|
|
200
|
+
parts.push(`${totalFailed}项失败`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return parts.join(', ');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
module.exports = {
|
|
207
|
+
exportAllConfigs,
|
|
208
|
+
importConfigs
|
|
209
|
+
};
|