@adversity/coding-tool-x 3.0.6 → 3.1.1
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 +38 -18
- package/README.md +8 -8
- package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
- package/dist/web/assets/ConfigTemplates-ZrK_s7ma.js +1 -0
- package/dist/web/assets/Home-B8YfhZ3c.js +1 -0
- package/dist/web/assets/Home-Di2qsylF.css +1 -0
- package/dist/web/assets/PluginManager-BD7QUZbU.js +1 -0
- package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
- package/dist/web/assets/ProjectList-C1fQb9OW.css +1 -0
- package/dist/web/assets/ProjectList-DRb1DuHV.js +1 -0
- package/dist/web/assets/SessionList-BGJWyneI.css +1 -0
- package/dist/web/assets/SessionList-lZ0LKzfT.js +1 -0
- package/dist/web/assets/SkillManager-C1xG5B4Q.js +1 -0
- package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
- package/dist/web/assets/Terminal-DGNJeVtc.css +1 -0
- package/dist/web/assets/Terminal-DksBo_lM.js +1 -0
- package/dist/web/assets/WorkspaceManager-Burx7XOo.js +1 -0
- package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
- package/dist/web/assets/icons-kcfLIMBB.js +1 -0
- package/dist/web/assets/index-Ufv5rCa5.css +1 -0
- package/dist/web/assets/index-lAkrRC3h.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 +92 -13
- 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/ui.js +8 -1
- package/src/commands/update.js +97 -0
- package/src/commands/workspace.js +1 -1
- package/src/config/default.js +39 -2
- package/src/config/loader.js +74 -8
- package/src/config/paths.js +105 -33
- package/src/index.js +67 -4
- 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 +419 -0
- package/src/server/api/opencode-projects.js +99 -0
- package/src/server/api/opencode-proxy.js +198 -0
- package/src/server/api/opencode-sessions.js +403 -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/skills.js +69 -18
- package/src/server/api/workspaces.js +78 -6
- package/src/server/codex-proxy-server.js +32 -19
- package/src/server/dev-server.js +1 -1
- package/src/server/gemini-proxy-server.js +17 -3
- package/src/server/index.js +164 -48
- package/src/server/opencode-proxy-server.js +4375 -0
- package/src/server/proxy-server.js +30 -19
- package/src/server/services/agents-service.js +61 -24
- package/src/server/services/channel-scheduler.js +9 -5
- package/src/server/services/channels.js +70 -12
- package/src/server/services/codex-channels.js +61 -23
- 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 +26 -12
- package/src/server/services/env-manager.js +126 -18
- package/src/server/services/favorites.js +5 -3
- package/src/server/services/gemini-channels.js +37 -15
- 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 +206 -0
- package/src/server/services/opencode-gateway-converter.js +639 -0
- package/src/server/services/opencode-sessions.js +663 -0
- package/src/server/services/opencode-settings-manager.js +342 -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/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 +132 -3
- 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-BxudHPiX.js +0 -1
- package/dist/web/assets/index-D2VfwJBa.js +0 -14
- package/dist/web/assets/index-oXBzu0bd.css +0 -41
- package/dist/web/assets/naive-ui-DT-Uur8K.js +0 -1
- package/dist/web/assets/vue-vendor-6JaYHOiI.js +0 -44
- package/src/server/api/permissions.js +0 -385
- 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,23 @@ 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
|
+
'.zshrc',
|
|
51
|
+
'.zprofile',
|
|
52
|
+
'.profile',
|
|
53
|
+
path.join('Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1'),
|
|
54
|
+
path.join('Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1')
|
|
55
|
+
]
|
|
56
|
+
: [
|
|
57
|
+
'.bashrc',
|
|
58
|
+
'.bash_profile',
|
|
59
|
+
'.zshrc',
|
|
60
|
+
'.zprofile',
|
|
61
|
+
'.profile'
|
|
62
|
+
];
|
|
53
63
|
|
|
54
64
|
// 系统级配置文件
|
|
55
65
|
const SYSTEM_CONFIG_FILES = [
|
|
@@ -120,7 +130,7 @@ function checkShellConfigs(keywords) {
|
|
|
120
130
|
const homeDir = os.homedir();
|
|
121
131
|
|
|
122
132
|
for (const fileName of SHELL_CONFIG_FILES) {
|
|
123
|
-
const filePath = path.join(homeDir, fileName);
|
|
133
|
+
const filePath = path.isAbsolute(fileName) ? fileName : path.join(homeDir, fileName);
|
|
124
134
|
const fileConflicts = parseConfigFile(filePath, keywords);
|
|
125
135
|
conflicts.push(...fileConflicts);
|
|
126
136
|
}
|
|
@@ -165,10 +175,14 @@ function parseConfigFile(filePath, keywords) {
|
|
|
165
175
|
continue;
|
|
166
176
|
}
|
|
167
177
|
|
|
168
|
-
// 匹配 export VAR=value 或 VAR=value
|
|
178
|
+
// 匹配 sh/bash/zsh 的 export VAR=value 或 VAR=value
|
|
169
179
|
const exportMatch = trimmed.match(/^(?:export\s+)?([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
170
|
-
|
|
171
|
-
|
|
180
|
+
// 匹配 PowerShell 的 $env:VAR = value
|
|
181
|
+
const psMatch = trimmed.match(/^\$env:([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$/i);
|
|
182
|
+
const matched = exportMatch || psMatch;
|
|
183
|
+
|
|
184
|
+
if (matched) {
|
|
185
|
+
const [, varName, varValue] = matched;
|
|
172
186
|
|
|
173
187
|
if (matchesKeywords(varName, keywords)) {
|
|
174
188
|
conflicts.push({
|
|
@@ -6,15 +6,77 @@
|
|
|
6
6
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
-
const
|
|
9
|
+
const { PATHS, ensureStorageDirMigrated } = require('../../config/paths');
|
|
10
10
|
|
|
11
11
|
// 备份目录
|
|
12
|
-
const BACKUP_DIR =
|
|
12
|
+
const BACKUP_DIR = PATHS.envBackups;
|
|
13
|
+
|
|
14
|
+
function ensureParentDir(filePath) {
|
|
15
|
+
const dirPath = path.dirname(filePath);
|
|
16
|
+
if (!fs.existsSync(dirPath)) {
|
|
17
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function writeFileAtomic(filePath, content) {
|
|
22
|
+
ensureParentDir(filePath);
|
|
23
|
+
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
fs.writeFileSync(tempPath, content, 'utf-8');
|
|
27
|
+
fs.renameSync(tempPath, filePath);
|
|
28
|
+
} finally {
|
|
29
|
+
if (fs.existsSync(tempPath)) {
|
|
30
|
+
try {
|
|
31
|
+
fs.unlinkSync(tempPath);
|
|
32
|
+
} catch (cleanupErr) {
|
|
33
|
+
// ignore cleanup errors
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function escapeRegex(value) {
|
|
40
|
+
return String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isPowerShellProfile(filePath) {
|
|
44
|
+
return String(filePath || '').toLowerCase().endsWith('.ps1');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function matchesVarAssignment(line, varName, usePowerShell) {
|
|
48
|
+
if (!line || !varName) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const escaped = escapeRegex(varName);
|
|
53
|
+
const regex = usePowerShell
|
|
54
|
+
? new RegExp(`^\\s*\\$env:${escaped}\\s*=`, 'i')
|
|
55
|
+
: new RegExp(`^\\s*(?:export\\s+)?${escaped}=`);
|
|
56
|
+
return regex.test(line.trim());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function formatExportLine(varName, value, usePowerShell) {
|
|
60
|
+
if (usePowerShell) {
|
|
61
|
+
const escapedValue = String(value ?? '')
|
|
62
|
+
.replace(/`/g, '``')
|
|
63
|
+
.replace(/"/g, '`"');
|
|
64
|
+
return `$env:${varName} = "${escapedValue}"`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const escapedValue = String(value ?? '')
|
|
68
|
+
.replace(/\\/g, '\\\\')
|
|
69
|
+
.replace(/"/g, '\\"')
|
|
70
|
+
.replace(/\$/g, '\\$')
|
|
71
|
+
.replace(/`/g, '\\`');
|
|
72
|
+
return `export ${varName}="${escapedValue}"`;
|
|
73
|
+
}
|
|
13
74
|
|
|
14
75
|
/**
|
|
15
76
|
* 确保备份目录存在
|
|
16
77
|
*/
|
|
17
78
|
function ensureBackupDir() {
|
|
79
|
+
ensureStorageDirMigrated();
|
|
18
80
|
if (!fs.existsSync(BACKUP_DIR)) {
|
|
19
81
|
fs.mkdirSync(BACKUP_DIR, { recursive: true });
|
|
20
82
|
}
|
|
@@ -112,9 +174,14 @@ function getOriginalValue(conflict) {
|
|
|
112
174
|
|
|
113
175
|
if (line) {
|
|
114
176
|
const match = line.match(/^(?:export\s+)?[A-Z_][A-Z0-9_]*=(.*)$/);
|
|
115
|
-
if (match) {
|
|
177
|
+
if (match && match[1] !== undefined) {
|
|
116
178
|
return cleanValue(match[1]);
|
|
117
179
|
}
|
|
180
|
+
|
|
181
|
+
const psMatch = line.match(/^\s*\$env:[A-Z_][A-Z0-9_]*\s*=\s*(.*)$/i);
|
|
182
|
+
if (psMatch && psMatch[1] !== undefined) {
|
|
183
|
+
return cleanValue(psMatch[1]);
|
|
184
|
+
}
|
|
118
185
|
}
|
|
119
186
|
} catch (err) {
|
|
120
187
|
// 忽略
|
|
@@ -162,18 +229,44 @@ function removeVarsFromFile(filePath, vars) {
|
|
|
162
229
|
}
|
|
163
230
|
|
|
164
231
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
165
|
-
const lines = content.split(
|
|
232
|
+
const lines = content.split(/\r?\n/);
|
|
233
|
+
const usePowerShell = isPowerShellProfile(filePath);
|
|
234
|
+
|
|
235
|
+
// 收集要删除的行号(优先使用行号,行号不匹配时按变量名回退)
|
|
236
|
+
const lineNumbersToRemove = new Set();
|
|
237
|
+
for (const item of vars) {
|
|
238
|
+
const varName = String(item.varName || '').trim();
|
|
239
|
+
if (!varName) continue;
|
|
240
|
+
|
|
241
|
+
const lineIndex = Number(item.lineNumber) - 1;
|
|
242
|
+
if (
|
|
243
|
+
Number.isInteger(lineIndex) &&
|
|
244
|
+
lineIndex >= 0 &&
|
|
245
|
+
lineIndex < lines.length &&
|
|
246
|
+
matchesVarAssignment(lines[lineIndex], varName, usePowerShell)
|
|
247
|
+
) {
|
|
248
|
+
lineNumbersToRemove.add(lineIndex);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
166
251
|
|
|
167
|
-
|
|
168
|
-
|
|
252
|
+
const fallbackIndex = lines.findIndex(line => matchesVarAssignment(line, varName, usePowerShell));
|
|
253
|
+
if (fallbackIndex >= 0) {
|
|
254
|
+
lineNumbersToRemove.add(fallbackIndex);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
169
257
|
|
|
170
|
-
|
|
171
|
-
|
|
258
|
+
if (lineNumbersToRemove.size === 0) {
|
|
259
|
+
console.log(`[EnvManager] No matching vars found in ${filePath}, skip writing`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
172
262
|
|
|
173
|
-
|
|
174
|
-
|
|
263
|
+
const newLines = lines.filter((_, index) => !lineNumbersToRemove.has(index));
|
|
264
|
+
const nextContent = newLines.join('\n');
|
|
265
|
+
if (nextContent !== content) {
|
|
266
|
+
writeFileAtomic(filePath, nextContent);
|
|
267
|
+
}
|
|
175
268
|
|
|
176
|
-
console.log(`[EnvManager] Removed ${
|
|
269
|
+
console.log(`[EnvManager] Removed ${lineNumbersToRemove.size} var(s) from ${filePath}`);
|
|
177
270
|
}
|
|
178
271
|
|
|
179
272
|
/**
|
|
@@ -255,21 +348,36 @@ function restoreFromBackup(backupPath) {
|
|
|
255
348
|
* 恢复环境变量到文件
|
|
256
349
|
*/
|
|
257
350
|
function restoreVarToFile(filePath, varName, value) {
|
|
351
|
+
const usePowerShell = isPowerShellProfile(filePath);
|
|
352
|
+
const exportLine = formatExportLine(varName, value, usePowerShell);
|
|
258
353
|
let content = '';
|
|
259
354
|
|
|
260
355
|
if (fs.existsSync(filePath)) {
|
|
261
356
|
content = fs.readFileSync(filePath, 'utf-8');
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const lines = content ? content.split(/\r?\n/) : [];
|
|
360
|
+
let replaced = false;
|
|
361
|
+
|
|
362
|
+
for (let i = 0; i < lines.length; i++) {
|
|
363
|
+
if (matchesVarAssignment(lines[i], varName, usePowerShell)) {
|
|
364
|
+
lines[i] = exportLine;
|
|
365
|
+
replaced = true;
|
|
265
366
|
}
|
|
266
367
|
}
|
|
267
368
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
369
|
+
if (!replaced) {
|
|
370
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === '') {
|
|
371
|
+
lines.pop();
|
|
372
|
+
}
|
|
373
|
+
if (lines.length > 0) {
|
|
374
|
+
lines.push('');
|
|
375
|
+
}
|
|
376
|
+
lines.push(exportLine);
|
|
377
|
+
}
|
|
271
378
|
|
|
272
|
-
|
|
379
|
+
const nextContent = `${lines.join('\n')}\n`;
|
|
380
|
+
writeFileAtomic(filePath, nextContent);
|
|
273
381
|
|
|
274
382
|
console.log(`[EnvManager] Restored ${varName} to ${filePath}`);
|
|
275
383
|
}
|