@adversity/coding-tool-x 3.1.0 → 3.1.2
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 +39 -18
- package/README.md +8 -8
- package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
- package/dist/web/assets/ConfigTemplates-DvcbKKdS.js +1 -0
- package/dist/web/assets/Home-BJKPCBuk.css +1 -0
- package/dist/web/assets/Home-Cw-F_Wnu.js +1 -0
- package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
- package/dist/web/assets/PluginManager-jy_4GVxI.js +1 -0
- package/dist/web/assets/ProjectList-C1fQb9OW.css +1 -0
- package/dist/web/assets/ProjectList-Df1-NcNr.js +1 -0
- package/dist/web/assets/SessionList-BGJWyneI.css +1 -0
- package/dist/web/assets/SessionList-UWcZtC2r.js +1 -0
- package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
- package/dist/web/assets/SkillManager-IRdseMKB.js +1 -0
- package/dist/web/assets/Terminal-BasTyDut.js +1 -0
- package/dist/web/assets/Terminal-DGNJeVtc.css +1 -0
- package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
- package/dist/web/assets/WorkspaceManager-D-D2kK1V.js +1 -0
- package/dist/web/assets/icons-kcfLIMBB.js +1 -0
- package/dist/web/assets/index-CoB3zF0K.css +1 -0
- package/dist/web/assets/index-CryrSLv8.js +2 -0
- package/dist/web/assets/markdown-BfC0goYb.css +10 -0
- package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
- package/dist/web/assets/naive-ui-CSrLusZZ.js +1 -0
- package/dist/web/assets/{vendors-D2HHw_aW.js → vendors-CO3Upi1d.js} +2 -2
- package/dist/web/assets/vue-vendor-DqyWIXEb.js +45 -0
- package/dist/web/assets/xterm-6GBZ9nXN.css +32 -0
- package/dist/web/assets/xterm-BJzAjXCH.js +13 -0
- package/dist/web/index.html +8 -6
- package/package.json +4 -2
- package/src/commands/channels.js +48 -1
- package/src/commands/cli-type.js +4 -2
- package/src/commands/daemon.js +81 -12
- package/src/commands/doctor.js +10 -9
- package/src/commands/list.js +1 -1
- package/src/commands/logs.js +6 -4
- package/src/commands/port-config.js +24 -4
- package/src/commands/proxy-control.js +12 -6
- package/src/commands/search.js +1 -1
- package/src/commands/security.js +3 -2
- package/src/commands/stats.js +226 -52
- package/src/commands/switch.js +1 -1
- package/src/commands/toggle-proxy.js +31 -6
- package/src/commands/update.js +97 -0
- package/src/commands/workspace.js +1 -1
- package/src/config/default.js +41 -2
- package/src/config/loader.js +74 -8
- package/src/config/model-metadata.js +415 -0
- package/src/config/model-pricing.js +23 -93
- package/src/config/paths.js +105 -33
- package/src/index.js +64 -3
- package/src/plugins/constants.js +3 -2
- package/src/plugins/plugin-api.js +1 -1
- package/src/reset-config.js +4 -2
- package/src/server/api/agents.js +57 -14
- package/src/server/api/channels.js +112 -33
- package/src/server/api/codex-channels.js +111 -18
- package/src/server/api/codex-proxy.js +14 -8
- package/src/server/api/commands.js +71 -18
- package/src/server/api/config-export.js +0 -6
- package/src/server/api/config-registry.js +11 -3
- package/src/server/api/config.js +376 -5
- package/src/server/api/convert.js +133 -0
- package/src/server/api/dashboard.js +22 -6
- package/src/server/api/gemini-channels.js +107 -18
- package/src/server/api/gemini-proxy.js +14 -8
- package/src/server/api/gemini-sessions.js +1 -1
- package/src/server/api/health-check.js +4 -3
- package/src/server/api/mcp.js +3 -3
- package/src/server/api/opencode-channels.js +497 -0
- package/src/server/api/opencode-projects.js +99 -0
- package/src/server/api/opencode-proxy.js +207 -0
- package/src/server/api/opencode-sessions.js +345 -0
- package/src/server/api/opencode-statistics.js +57 -0
- package/src/server/api/plugins.js +66 -19
- package/src/server/api/prompts.js +2 -2
- package/src/server/api/proxy.js +7 -4
- package/src/server/api/sessions.js +3 -0
- package/src/server/api/settings.js +111 -0
- package/src/server/api/skills.js +69 -18
- package/src/server/api/workspaces.js +78 -6
- package/src/server/codex-proxy-server.js +36 -22
- package/src/server/dev-server.js +1 -1
- package/src/server/gemini-proxy-server.js +21 -7
- package/src/server/index.js +174 -58
- package/src/server/opencode-proxy-server.js +5486 -0
- package/src/server/proxy-server.js +33 -22
- package/src/server/services/agents-service.js +61 -24
- package/src/server/services/channel-scheduler.js +9 -5
- package/src/server/services/channels.js +64 -37
- package/src/server/services/codex-channels.js +56 -43
- package/src/server/services/codex-sessions.js +105 -6
- package/src/server/services/codex-settings-manager.js +271 -49
- package/src/server/services/codex-statistics-service.js +2 -2
- package/src/server/services/commands-service.js +84 -25
- package/src/server/services/config-export-service.js +7 -45
- package/src/server/services/config-registry-service.js +63 -17
- package/src/server/services/config-sync-manager.js +160 -7
- package/src/server/services/config-templates-service.js +204 -51
- package/src/server/services/env-checker.js +50 -13
- package/src/server/services/env-manager.js +155 -19
- package/src/server/services/favorites.js +5 -3
- package/src/server/services/gemini-channels.js +33 -44
- package/src/server/services/gemini-statistics-service.js +2 -2
- package/src/server/services/mcp-service.js +350 -9
- package/src/server/services/model-detector.js +707 -221
- package/src/server/services/network-access.js +80 -0
- package/src/server/services/opencode-channels.js +208 -0
- package/src/server/services/opencode-gateway-converter.js +639 -0
- package/src/server/services/opencode-sessions.js +931 -0
- package/src/server/services/opencode-settings-manager.js +478 -0
- package/src/server/services/opencode-statistics-service.js +255 -0
- package/src/server/services/plugins-service.js +479 -22
- package/src/server/services/prompts-service.js +53 -11
- package/src/server/services/proxy-runtime.js +1 -1
- package/src/server/services/repo-scanner-base.js +1 -1
- package/src/server/services/response-decoder.js +21 -0
- package/src/server/services/security-config.js +1 -1
- package/src/server/services/session-cache.js +1 -1
- package/src/server/services/skill-service.js +300 -46
- package/src/server/services/speed-test.js +464 -186
- package/src/server/services/statistics-service.js +2 -2
- package/src/server/services/terminal-commands.js +10 -3
- package/src/server/services/terminal-config.js +1 -1
- package/src/server/services/ui-config.js +1 -1
- package/src/server/services/workspace-service.js +57 -100
- package/src/server/websocket-server.js +156 -8
- package/src/ui/menu.js +49 -40
- package/src/utils/port-helper.js +22 -8
- package/src/utils/session.js +5 -4
- package/dist/web/assets/icons-CO_2OFES.js +0 -1
- package/dist/web/assets/index-DI8QOi-E.js +0 -14
- package/dist/web/assets/index-uLHGdeZh.css +0 -41
- package/dist/web/assets/naive-ui-B1re3c-e.js +0 -1
- package/dist/web/assets/vue-vendor-6JaYHOiI.js +0 -44
- package/src/server/api/oauth.js +0 -294
- package/src/server/api/permissions.js +0 -385
- package/src/server/config/oauth-providers.js +0 -68
- package/src/server/services/oauth-callback-server.js +0 -284
- package/src/server/services/oauth-service.js +0 -378
- package/src/server/services/oauth-token-storage.js +0 -135
- package/src/server/services/permission-templates-service.js +0 -308
|
@@ -22,7 +22,7 @@ const pluginsService = new PluginsService();
|
|
|
22
22
|
const TEMPLATES_FILE = path.join(PATHS.config, 'config-templates.json');
|
|
23
23
|
|
|
24
24
|
// 内置配置模板
|
|
25
|
-
// aiConfigs 结构: { claude: { enabled, content }, codex: { enabled, content }, gemini: { enabled, content } }
|
|
25
|
+
// aiConfigs 结构: { claude: { enabled, content }, codex: { enabled, content }, gemini: { enabled, content }, opencode: { enabled, content } }
|
|
26
26
|
const BUILTIN_TEMPLATES = [
|
|
27
27
|
{
|
|
28
28
|
id: 'full-stack',
|
|
@@ -492,6 +492,57 @@ function ensureDir(dirPath) {
|
|
|
492
492
|
}
|
|
493
493
|
}
|
|
494
494
|
|
|
495
|
+
function normalizeAiConfigs(aiConfigs = {}, claudeMd = null) {
|
|
496
|
+
const normalized = {
|
|
497
|
+
claude: { enabled: false, content: '' },
|
|
498
|
+
codex: { enabled: false, content: '' },
|
|
499
|
+
gemini: { enabled: false, content: '' },
|
|
500
|
+
opencode: { enabled: false, content: '' }
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
for (const key of Object.keys(normalized)) {
|
|
504
|
+
const cfg = aiConfigs?.[key];
|
|
505
|
+
if (cfg && typeof cfg === 'object') {
|
|
506
|
+
normalized[key] = {
|
|
507
|
+
enabled: !!cfg.enabled,
|
|
508
|
+
content: cfg.content || ''
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (claudeMd?.enabled && claudeMd?.content && !normalized.claude.content) {
|
|
514
|
+
normalized.claude = {
|
|
515
|
+
enabled: true,
|
|
516
|
+
content: claudeMd.content
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// OpenCode defaults to Codex profile if not explicitly configured.
|
|
521
|
+
if (!normalized.opencode.content) {
|
|
522
|
+
const fallback = normalized.codex.content ? normalized.codex : normalized.claude;
|
|
523
|
+
normalized.opencode = {
|
|
524
|
+
enabled: !!fallback.enabled,
|
|
525
|
+
content: fallback.content || ''
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return normalized;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function normalizeTemplate(template) {
|
|
533
|
+
if (!template || typeof template !== 'object') {
|
|
534
|
+
return template;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const normalized = { ...template };
|
|
538
|
+
normalized.aiConfigs = normalizeAiConfigs(template.aiConfigs, template.claudeMd);
|
|
539
|
+
if (!normalized.claudeMd) {
|
|
540
|
+
normalized.claudeMd = { enabled: false, content: '' };
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return normalized;
|
|
544
|
+
}
|
|
545
|
+
|
|
495
546
|
/**
|
|
496
547
|
* 加载配置模板
|
|
497
548
|
*/
|
|
@@ -502,8 +553,8 @@ function loadTemplates() {
|
|
|
502
553
|
const data = JSON.parse(content);
|
|
503
554
|
// 合并内置模板和用户模板
|
|
504
555
|
return {
|
|
505
|
-
builtin: BUILTIN_TEMPLATES,
|
|
506
|
-
custom: data.custom || []
|
|
556
|
+
builtin: BUILTIN_TEMPLATES.map(normalizeTemplate),
|
|
557
|
+
custom: (data.custom || []).map(normalizeTemplate)
|
|
507
558
|
};
|
|
508
559
|
}
|
|
509
560
|
} catch (error) {
|
|
@@ -511,7 +562,7 @@ function loadTemplates() {
|
|
|
511
562
|
}
|
|
512
563
|
|
|
513
564
|
return {
|
|
514
|
-
builtin: BUILTIN_TEMPLATES,
|
|
565
|
+
builtin: BUILTIN_TEMPLATES.map(normalizeTemplate),
|
|
515
566
|
custom: []
|
|
516
567
|
};
|
|
517
568
|
}
|
|
@@ -569,6 +620,7 @@ function createCustomTemplate(template) {
|
|
|
569
620
|
name: template.name,
|
|
570
621
|
description: template.description || '',
|
|
571
622
|
claudeMd: template.claudeMd || { enabled: false, content: '' },
|
|
623
|
+
aiConfigs: normalizeAiConfigs(template.aiConfigs, template.claudeMd),
|
|
572
624
|
skills: template.skills || [],
|
|
573
625
|
rules: template.rules || [],
|
|
574
626
|
commands: template.commands || [],
|
|
@@ -579,10 +631,10 @@ function createCustomTemplate(template) {
|
|
|
579
631
|
createdAt: new Date().toISOString()
|
|
580
632
|
};
|
|
581
633
|
|
|
582
|
-
custom.push(newTemplate);
|
|
634
|
+
custom.push(normalizeTemplate(newTemplate));
|
|
583
635
|
saveCustomTemplates(custom);
|
|
584
636
|
|
|
585
|
-
return newTemplate;
|
|
637
|
+
return normalizeTemplate(newTemplate);
|
|
586
638
|
}
|
|
587
639
|
|
|
588
640
|
/**
|
|
@@ -599,6 +651,7 @@ function updateCustomTemplate(id, updates) {
|
|
|
599
651
|
custom[index] = {
|
|
600
652
|
...custom[index],
|
|
601
653
|
...updates,
|
|
654
|
+
aiConfigs: normalizeAiConfigs(updates.aiConfigs || custom[index].aiConfigs, updates.claudeMd || custom[index].claudeMd),
|
|
602
655
|
id: custom[index].id, // 保持 ID 不变
|
|
603
656
|
isBuiltin: false,
|
|
604
657
|
updatedAt: new Date().toISOString()
|
|
@@ -817,12 +870,51 @@ function generateRuleContent(rule) {
|
|
|
817
870
|
return content + (rule.body || '');
|
|
818
871
|
}
|
|
819
872
|
|
|
873
|
+
/**
|
|
874
|
+
* 转换为 OpenCode MCP 结构(local/remote)
|
|
875
|
+
*/
|
|
876
|
+
function convertToOpenCodeMcpSpec(spec = {}) {
|
|
877
|
+
const type = spec.type || 'stdio';
|
|
878
|
+
|
|
879
|
+
if (type === 'local' || type === 'remote') {
|
|
880
|
+
return { ...spec };
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
if (type === 'stdio') {
|
|
884
|
+
const command = [];
|
|
885
|
+
if (spec.command) command.push(spec.command);
|
|
886
|
+
if (Array.isArray(spec.args)) command.push(...spec.args);
|
|
887
|
+
|
|
888
|
+
const result = {
|
|
889
|
+
type: 'local',
|
|
890
|
+
command
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
if (spec.env && typeof spec.env === 'object') {
|
|
894
|
+
result.environment = spec.env;
|
|
895
|
+
}
|
|
896
|
+
if (spec.cwd) {
|
|
897
|
+
result.cwd = spec.cwd;
|
|
898
|
+
}
|
|
899
|
+
return result;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
const result = {
|
|
903
|
+
type: 'remote',
|
|
904
|
+
url: spec.url || ''
|
|
905
|
+
};
|
|
906
|
+
if (spec.headers && typeof spec.headers === 'object') {
|
|
907
|
+
result.headers = spec.headers;
|
|
908
|
+
}
|
|
909
|
+
return result;
|
|
910
|
+
}
|
|
911
|
+
|
|
820
912
|
/**
|
|
821
913
|
* 应用模板到项目目录(完整应用,写入实际文件)
|
|
822
914
|
* @param {string} targetDir - 目标项目目录
|
|
823
915
|
* @param {string} templateId - 模板 ID
|
|
824
916
|
* @param {object} options - 可选配置
|
|
825
|
-
* @param {string|string[]} options.aiConfigTypes - 选择的 AI 配置类型数组: ['claude', 'codex', 'gemini']
|
|
917
|
+
* @param {string|string[]} options.aiConfigTypes - 选择的 AI 配置类型数组: ['claude', 'codex', 'gemini', 'opencode']
|
|
826
918
|
* @param {string} options.aiConfigType - (兼容旧版) 单个 AI 配置类型
|
|
827
919
|
*/
|
|
828
920
|
function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
@@ -856,7 +948,8 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
|
856
948
|
const aiConfigMap = {
|
|
857
949
|
claude: { fileName: 'CLAUDE.md', name: 'Claude' },
|
|
858
950
|
codex: { fileName: 'AGENTS.md', name: 'Codex' },
|
|
859
|
-
gemini: { fileName: 'GEMINI.md', name: 'Gemini' }
|
|
951
|
+
gemini: { fileName: 'GEMINI.md', name: 'Gemini' },
|
|
952
|
+
opencode: { fileName: '.opencode/AGENTS.md', name: 'OpenCode' }
|
|
860
953
|
};
|
|
861
954
|
|
|
862
955
|
// 遍历所有选中的 AI 配置类型
|
|
@@ -872,6 +965,7 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
|
872
965
|
if (aiConfig?.enabled && aiConfig?.content) {
|
|
873
966
|
const configInfo = aiConfigMap[aiConfigType];
|
|
874
967
|
const configPath = path.join(targetDir, configInfo.fileName);
|
|
968
|
+
ensureDir(path.dirname(configPath));
|
|
875
969
|
fs.writeFileSync(configPath, aiConfig.content, 'utf-8');
|
|
876
970
|
results.aiConfigs.push({ applied: true, path: configInfo.fileName, type: configInfo.name, key: aiConfigType });
|
|
877
971
|
}
|
|
@@ -879,34 +973,54 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
|
879
973
|
|
|
880
974
|
// 2. 写入 Agents
|
|
881
975
|
if (template.agents?.length > 0) {
|
|
882
|
-
const
|
|
883
|
-
|
|
976
|
+
const agentTargets = [
|
|
977
|
+
{ baseDir: path.join(targetDir, '.claude', 'agents'), prefix: '.claude/agents' },
|
|
978
|
+
{ baseDir: path.join(targetDir, '.opencode', 'agents'), prefix: '.opencode/agents' }
|
|
979
|
+
];
|
|
980
|
+
|
|
981
|
+
for (const target of agentTargets) {
|
|
982
|
+
ensureDir(target.baseDir);
|
|
983
|
+
}
|
|
984
|
+
|
|
884
985
|
for (const agent of template.agents) {
|
|
885
986
|
const content = generateAgentContent(agent);
|
|
886
987
|
const fileName = agent.fileName || agent.name.toLowerCase().replace(/\s+/g, '-');
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
|
|
988
|
+
for (const target of agentTargets) {
|
|
989
|
+
const filePath = path.join(target.baseDir, `${fileName}.md`);
|
|
990
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
991
|
+
results.agents.files.push(`${target.prefix}/${fileName}.md`);
|
|
992
|
+
}
|
|
890
993
|
results.agents.applied++;
|
|
891
994
|
}
|
|
892
995
|
}
|
|
893
996
|
|
|
894
997
|
// 3. 写入 Commands
|
|
895
998
|
if (template.commands?.length > 0) {
|
|
896
|
-
const
|
|
897
|
-
|
|
999
|
+
const commandTargets = [
|
|
1000
|
+
{ baseDir: path.join(targetDir, '.claude', 'commands'), prefix: '.claude/commands' },
|
|
1001
|
+
{ baseDir: path.join(targetDir, '.opencode', 'commands'), prefix: '.opencode/commands' }
|
|
1002
|
+
];
|
|
1003
|
+
|
|
1004
|
+
for (const target of commandTargets) {
|
|
1005
|
+
ensureDir(target.baseDir);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
898
1008
|
for (const command of template.commands) {
|
|
899
1009
|
const content = generateCommandContent(command);
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1010
|
+
|
|
1011
|
+
for (const target of commandTargets) {
|
|
1012
|
+
const targetCmdDir = command.namespace
|
|
1013
|
+
? path.join(target.baseDir, command.namespace)
|
|
1014
|
+
: target.baseDir;
|
|
1015
|
+
ensureDir(targetCmdDir);
|
|
1016
|
+
const filePath = path.join(targetCmdDir, `${command.name}.md`);
|
|
1017
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
1018
|
+
const relativePath = command.namespace
|
|
1019
|
+
? `${target.prefix}/${command.namespace}/${command.name}.md`
|
|
1020
|
+
: `${target.prefix}/${command.name}.md`;
|
|
1021
|
+
results.commands.files.push(relativePath);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
910
1024
|
results.commands.applied++;
|
|
911
1025
|
}
|
|
912
1026
|
}
|
|
@@ -938,13 +1052,16 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
|
938
1052
|
results.plugins.items = template.plugins.map(p => p.name);
|
|
939
1053
|
}
|
|
940
1054
|
|
|
941
|
-
// 6. 写入 MCP
|
|
942
|
-
|
|
1055
|
+
// 6. 写入 MCP/OpenCode 配置
|
|
1056
|
+
const hasMcp = template.mcpServers?.length > 0;
|
|
1057
|
+
const hasPlugins = template.plugins?.length > 0;
|
|
1058
|
+
if (hasMcp || hasPlugins) {
|
|
943
1059
|
const mcpConfig = { mcpServers: {} };
|
|
1060
|
+
const opencodeConfig = { mcp: {}, plugin: [] };
|
|
944
1061
|
const allServers = mcpService.getAllServers();
|
|
945
1062
|
const presets = mcpService.getPresets();
|
|
946
1063
|
|
|
947
|
-
for (const serverId of template.mcpServers) {
|
|
1064
|
+
for (const serverId of template.mcpServers || []) {
|
|
948
1065
|
// 先从已配置的服务器中查找
|
|
949
1066
|
let serverSpec = allServers[serverId]?.server;
|
|
950
1067
|
// 如果没有,从预设中查找
|
|
@@ -956,6 +1073,7 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
|
956
1073
|
}
|
|
957
1074
|
if (serverSpec) {
|
|
958
1075
|
mcpConfig.mcpServers[serverId] = serverSpec;
|
|
1076
|
+
opencodeConfig.mcp[serverId] = convertToOpenCodeMcpSpec(serverSpec);
|
|
959
1077
|
results.mcpServers.applied++;
|
|
960
1078
|
}
|
|
961
1079
|
}
|
|
@@ -964,6 +1082,16 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
|
964
1082
|
const mcpPath = path.join(targetDir, '.mcp.json');
|
|
965
1083
|
fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2), 'utf-8');
|
|
966
1084
|
}
|
|
1085
|
+
|
|
1086
|
+
if (hasPlugins) {
|
|
1087
|
+
opencodeConfig.plugin = (template.plugins || []).map(p => p.name).filter(Boolean);
|
|
1088
|
+
}
|
|
1089
|
+
if (Object.keys(opencodeConfig.mcp).length > 0 || opencodeConfig.plugin.length > 0) {
|
|
1090
|
+
const opencodeDir = path.join(targetDir, '.opencode');
|
|
1091
|
+
ensureDir(opencodeDir);
|
|
1092
|
+
const opencodePath = path.join(opencodeDir, 'opencode.json');
|
|
1093
|
+
fs.writeFileSync(opencodePath, JSON.stringify(opencodeConfig, null, 2), 'utf-8');
|
|
1094
|
+
}
|
|
967
1095
|
}
|
|
968
1096
|
|
|
969
1097
|
// 7. 创建配置记录文件
|
|
@@ -995,7 +1123,7 @@ function applyTemplateToProject(targetDir, templateId, options = {}) {
|
|
|
995
1123
|
* @param {string} targetDir - 目标项目目录
|
|
996
1124
|
* @param {string} templateId - 模板 ID
|
|
997
1125
|
* @param {object} options - 可选配置
|
|
998
|
-
* @param {string|string[]} options.aiConfigTypes - 选择的 AI 配置类型数组: ['claude', 'codex', 'gemini']
|
|
1126
|
+
* @param {string|string[]} options.aiConfigTypes - 选择的 AI 配置类型数组: ['claude', 'codex', 'gemini', 'opencode']
|
|
999
1127
|
* @param {string} options.aiConfigType - (兼容旧版) 单个 AI 配置类型
|
|
1000
1128
|
*/
|
|
1001
1129
|
function previewTemplateApplication(targetDir, templateId, options = {}) {
|
|
@@ -1031,7 +1159,8 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
|
|
|
1031
1159
|
const aiConfigMap = {
|
|
1032
1160
|
claude: { fileName: 'CLAUDE.md', name: 'Claude' },
|
|
1033
1161
|
codex: { fileName: 'AGENTS.md', name: 'Codex' },
|
|
1034
|
-
gemini: { fileName: 'GEMINI.md', name: 'Gemini' }
|
|
1162
|
+
gemini: { fileName: 'GEMINI.md', name: 'Gemini' },
|
|
1163
|
+
opencode: { fileName: '.opencode/AGENTS.md', name: 'OpenCode' }
|
|
1035
1164
|
};
|
|
1036
1165
|
|
|
1037
1166
|
// 遍历所有选中的 AI 配置类型
|
|
@@ -1064,12 +1193,18 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
|
|
|
1064
1193
|
if (template.agents?.length > 0) {
|
|
1065
1194
|
for (const agent of template.agents) {
|
|
1066
1195
|
const fileName = agent.fileName || agent.name.toLowerCase().replace(/\s+/g, '-');
|
|
1067
|
-
const
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1196
|
+
const relativePaths = [
|
|
1197
|
+
`.claude/agents/${fileName}.md`,
|
|
1198
|
+
`.opencode/agents/${fileName}.md`
|
|
1199
|
+
];
|
|
1200
|
+
|
|
1201
|
+
for (const relativePath of relativePaths) {
|
|
1202
|
+
const fullPath = path.join(targetDir, relativePath);
|
|
1203
|
+
if (fs.existsSync(fullPath)) {
|
|
1204
|
+
preview.willOverwrite.push(relativePath);
|
|
1205
|
+
} else {
|
|
1206
|
+
preview.willCreate.push(relativePath);
|
|
1207
|
+
}
|
|
1073
1208
|
}
|
|
1074
1209
|
preview.summary.agents++;
|
|
1075
1210
|
}
|
|
@@ -1078,14 +1213,22 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
|
|
|
1078
1213
|
// 检查 Commands
|
|
1079
1214
|
if (template.commands?.length > 0) {
|
|
1080
1215
|
for (const command of template.commands) {
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1216
|
+
const relativePaths = [
|
|
1217
|
+
command.namespace
|
|
1218
|
+
? `.claude/commands/${command.namespace}/${command.name}.md`
|
|
1219
|
+
: `.claude/commands/${command.name}.md`,
|
|
1220
|
+
command.namespace
|
|
1221
|
+
? `.opencode/commands/${command.namespace}/${command.name}.md`
|
|
1222
|
+
: `.opencode/commands/${command.name}.md`
|
|
1223
|
+
];
|
|
1224
|
+
|
|
1225
|
+
for (const relativePath of relativePaths) {
|
|
1226
|
+
const fullPath = path.join(targetDir, relativePath);
|
|
1227
|
+
if (fs.existsSync(fullPath)) {
|
|
1228
|
+
preview.willOverwrite.push(relativePath);
|
|
1229
|
+
} else {
|
|
1230
|
+
preview.willCreate.push(relativePath);
|
|
1231
|
+
}
|
|
1089
1232
|
}
|
|
1090
1233
|
preview.summary.commands++;
|
|
1091
1234
|
}
|
|
@@ -1107,15 +1250,25 @@ function previewTemplateApplication(targetDir, templateId, options = {}) {
|
|
|
1107
1250
|
}
|
|
1108
1251
|
}
|
|
1109
1252
|
|
|
1110
|
-
// 检查 MCP
|
|
1111
|
-
if (template.mcpServers?.length > 0) {
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1253
|
+
// 检查 MCP / OpenCode 配置
|
|
1254
|
+
if (template.mcpServers?.length > 0 || template.plugins?.length > 0) {
|
|
1255
|
+
if (template.mcpServers?.length > 0) {
|
|
1256
|
+
const mcpPath = path.join(targetDir, '.mcp.json');
|
|
1257
|
+
if (fs.existsSync(mcpPath)) {
|
|
1258
|
+
preview.willOverwrite.push('.mcp.json');
|
|
1259
|
+
} else {
|
|
1260
|
+
preview.willCreate.push('.mcp.json');
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
const opencodeConfigPath = path.join(targetDir, '.opencode/opencode.json');
|
|
1265
|
+
if (fs.existsSync(opencodeConfigPath)) {
|
|
1266
|
+
preview.willOverwrite.push('.opencode/opencode.json');
|
|
1115
1267
|
} else {
|
|
1116
|
-
preview.willCreate.push('.
|
|
1268
|
+
preview.willCreate.push('.opencode/opencode.json');
|
|
1117
1269
|
}
|
|
1118
|
-
|
|
1270
|
+
|
|
1271
|
+
preview.summary.mcpServers = template.mcpServers?.length || 0;
|
|
1119
1272
|
}
|
|
1120
1273
|
|
|
1121
1274
|
// 统计 Plugins(插件不写入文件,只记录数量)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 环境变量检测服务
|
|
3
3
|
*
|
|
4
4
|
* 检测系统中可能导致 API 配置冲突的环境变量
|
|
5
|
-
* 支持 macOS/Linux 的 shell 配置文件检测
|
|
5
|
+
* 支持 macOS/Linux/Windows 的 shell 配置文件检测
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const fs = require('fs');
|
|
@@ -43,13 +43,29 @@ const EXACT_SENSITIVE_VARS = [
|
|
|
43
43
|
];
|
|
44
44
|
|
|
45
45
|
// 需要检测的 shell 配置文件
|
|
46
|
-
const SHELL_CONFIG_FILES =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
const SHELL_CONFIG_FILES = process.platform === 'win32'
|
|
47
|
+
? [
|
|
48
|
+
'.bashrc',
|
|
49
|
+
'.bash_profile',
|
|
50
|
+
'.bash_login',
|
|
51
|
+
'.zshrc',
|
|
52
|
+
'.zshenv',
|
|
53
|
+
'.zprofile',
|
|
54
|
+
'.zlogin',
|
|
55
|
+
'.profile',
|
|
56
|
+
path.join('Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1'),
|
|
57
|
+
path.join('Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1')
|
|
58
|
+
]
|
|
59
|
+
: [
|
|
60
|
+
'.bashrc',
|
|
61
|
+
'.bash_profile',
|
|
62
|
+
'.bash_login',
|
|
63
|
+
'.zshrc',
|
|
64
|
+
'.zshenv',
|
|
65
|
+
'.zprofile',
|
|
66
|
+
'.zlogin',
|
|
67
|
+
'.profile'
|
|
68
|
+
];
|
|
53
69
|
|
|
54
70
|
// 系统级配置文件
|
|
55
71
|
const SYSTEM_CONFIG_FILES = [
|
|
@@ -98,6 +114,10 @@ function checkProcessEnv(keywords) {
|
|
|
98
114
|
const conflicts = [];
|
|
99
115
|
|
|
100
116
|
for (const [key, value] of Object.entries(process.env)) {
|
|
117
|
+
if (!hasNonEmptyValue(value)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
101
121
|
if (matchesKeywords(key, keywords)) {
|
|
102
122
|
conflicts.push({
|
|
103
123
|
varName: key,
|
|
@@ -120,7 +140,7 @@ function checkShellConfigs(keywords) {
|
|
|
120
140
|
const homeDir = os.homedir();
|
|
121
141
|
|
|
122
142
|
for (const fileName of SHELL_CONFIG_FILES) {
|
|
123
|
-
const filePath = path.join(homeDir, fileName);
|
|
143
|
+
const filePath = path.isAbsolute(fileName) ? fileName : path.join(homeDir, fileName);
|
|
124
144
|
const fileConflicts = parseConfigFile(filePath, keywords);
|
|
125
145
|
conflicts.push(...fileConflicts);
|
|
126
146
|
}
|
|
@@ -165,15 +185,25 @@ function parseConfigFile(filePath, keywords) {
|
|
|
165
185
|
continue;
|
|
166
186
|
}
|
|
167
187
|
|
|
168
|
-
// 匹配 export VAR=value 或 VAR=value
|
|
188
|
+
// 匹配 sh/bash/zsh 的 export VAR=value 或 VAR=value
|
|
169
189
|
const exportMatch = trimmed.match(/^(?:export\s+)?([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
170
|
-
|
|
171
|
-
|
|
190
|
+
// 匹配 PowerShell 的 $env:VAR = value
|
|
191
|
+
const psMatch = trimmed.match(/^\$env:([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$/i);
|
|
192
|
+
const matched = exportMatch || psMatch;
|
|
193
|
+
|
|
194
|
+
if (matched) {
|
|
195
|
+
const [, varName, varValue] = matched;
|
|
196
|
+
const normalizedValue = cleanValue(varValue);
|
|
197
|
+
|
|
198
|
+
// 空值通常是占位或已清理状态,不再视为冲突
|
|
199
|
+
if (!hasNonEmptyValue(normalizedValue)) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
172
202
|
|
|
173
203
|
if (matchesKeywords(varName, keywords)) {
|
|
174
204
|
conflicts.push({
|
|
175
205
|
varName,
|
|
176
|
-
varValue: maskSensitiveValue(
|
|
206
|
+
varValue: maskSensitiveValue(normalizedValue),
|
|
177
207
|
sourceType: 'file',
|
|
178
208
|
sourcePath: `${filePath}:${i + 1}`,
|
|
179
209
|
filePath,
|
|
@@ -252,6 +282,13 @@ function cleanValue(value) {
|
|
|
252
282
|
return cleaned;
|
|
253
283
|
}
|
|
254
284
|
|
|
285
|
+
/**
|
|
286
|
+
* 判断值是否为非空
|
|
287
|
+
*/
|
|
288
|
+
function hasNonEmptyValue(value) {
|
|
289
|
+
return typeof value === 'string' && value.trim() !== '';
|
|
290
|
+
}
|
|
291
|
+
|
|
255
292
|
/**
|
|
256
293
|
* 遮蔽敏感值
|
|
257
294
|
*/
|