@ian2018cs/agenthub 0.1.69 → 0.1.71
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/dist/assets/index-CyYbCDk1.js +192 -0
- package/dist/assets/index-DQaPJRqa.css +32 -0
- package/dist/assets/{vendor-icons-DxBNDMja.js → vendor-icons-CFgKYN6c.js} +77 -72
- package/dist/index.html +3 -3
- package/package.json +2 -2
- package/server/claude-sdk.js +231 -15
- package/server/index.js +33 -1
- package/server/routes/agents.js +336 -5
- package/server/routes/mcp.js +122 -147
- package/server/routes/skills.js +341 -1
- package/server/services/system-mcp-repo.js +1 -1
- package/server/services/system-repo.js +1 -1
- package/dist/assets/index-HOTjBpXH.css +0 -32
- package/dist/assets/index-u5cEXvaS.js +0 -184
package/server/routes/agents.js
CHANGED
|
@@ -6,6 +6,8 @@ import { spawn } from 'child_process';
|
|
|
6
6
|
import AdmZip from 'adm-zip';
|
|
7
7
|
import { getUserPaths, getPublicPaths } from '../services/user-directories.js';
|
|
8
8
|
import { scanAgents, ensureAgentRepo, incrementPatchVersion, publishAgentToRepo } from '../services/system-agent-repo.js';
|
|
9
|
+
import { ensureSystemRepo, SYSTEM_REPO_URL } from '../services/system-repo.js';
|
|
10
|
+
import { ensureSystemMcpRepo, SYSTEM_MCP_REPO_URL } from '../services/system-mcp-repo.js';
|
|
9
11
|
import { addProjectManually, loadProjectConfig, saveProjectConfig } from '../projects.js';
|
|
10
12
|
import { agentSubmissionDb, userDb } from '../database/db.js';
|
|
11
13
|
import { chatCompletion } from '../services/llm.js';
|
|
@@ -77,6 +79,86 @@ function runGit(args, cwd = null) {
|
|
|
77
79
|
});
|
|
78
80
|
}
|
|
79
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Recursively copy all files and directories from src to dst.
|
|
84
|
+
*/
|
|
85
|
+
async function copyDirRecursive(src, dst) {
|
|
86
|
+
await fs.mkdir(dst, { recursive: true });
|
|
87
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
const srcPath = path.join(src, entry.name);
|
|
90
|
+
const dstPath = path.join(dst, entry.name);
|
|
91
|
+
if (entry.isDirectory()) {
|
|
92
|
+
await copyDirRecursive(srcPath, dstPath);
|
|
93
|
+
} else {
|
|
94
|
+
await fs.copyFile(srcPath, dstPath);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Recursively add all files under dirPath into a ZIP object under the given zipPrefix.
|
|
101
|
+
*/
|
|
102
|
+
async function addDirToZip(zip, dirPath, zipPrefix) {
|
|
103
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
106
|
+
const zipPath = zipPrefix ? `${zipPrefix}/${entry.name}` : entry.name;
|
|
107
|
+
if (entry.isDirectory()) {
|
|
108
|
+
await addDirToZip(zip, fullPath, zipPath);
|
|
109
|
+
} else {
|
|
110
|
+
const content = await fs.readFile(fullPath);
|
|
111
|
+
zip.addFile(zipPath, content);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Parse skills list from agent.yaml content string.
|
|
118
|
+
* Returns array of { name, repo }.
|
|
119
|
+
*/
|
|
120
|
+
function parseYamlSkills(yamlContent) {
|
|
121
|
+
const skills = [];
|
|
122
|
+
const section = yamlContent.match(/^skills:\s*\n((?:[ \t]+.+\n?)*)/m);
|
|
123
|
+
if (!section) return skills;
|
|
124
|
+
const lines = section[1].split('\n');
|
|
125
|
+
let current = null;
|
|
126
|
+
for (const line of lines) {
|
|
127
|
+
const nameMatch = line.match(/^\s+-\s+name:\s*["']?(.+?)["']?\s*$/);
|
|
128
|
+
if (nameMatch) {
|
|
129
|
+
if (current) skills.push(current);
|
|
130
|
+
current = { name: nameMatch[1].trim(), repo: '' };
|
|
131
|
+
}
|
|
132
|
+
const repoMatch = line.match(/^\s+repo:\s*["']?(.*?)["']?\s*$/);
|
|
133
|
+
if (repoMatch && current) current.repo = repoMatch[1].trim();
|
|
134
|
+
}
|
|
135
|
+
if (current) skills.push(current);
|
|
136
|
+
return skills;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Parse MCPs list from agent.yaml content string.
|
|
141
|
+
* Returns array of { name, repo }.
|
|
142
|
+
*/
|
|
143
|
+
function parseYamlMcps(yamlContent) {
|
|
144
|
+
const mcps = [];
|
|
145
|
+
const section = yamlContent.match(/^mcps:\s*\n((?:[ \t]+.+\n?)*)/m);
|
|
146
|
+
if (!section) return mcps;
|
|
147
|
+
const lines = section[1].split('\n');
|
|
148
|
+
let current = null;
|
|
149
|
+
for (const line of lines) {
|
|
150
|
+
const nameMatch = line.match(/^\s+-\s+name:\s*["']?(.+?)["']?\s*$/);
|
|
151
|
+
if (nameMatch) {
|
|
152
|
+
if (current) mcps.push(current);
|
|
153
|
+
current = { name: nameMatch[1].trim(), repo: '' };
|
|
154
|
+
}
|
|
155
|
+
const repoMatch = line.match(/^\s+repo:\s*["']?(.*?)["']?\s*$/);
|
|
156
|
+
if (repoMatch && current) current.repo = repoMatch[1].trim();
|
|
157
|
+
}
|
|
158
|
+
if (current) mcps.push(current);
|
|
159
|
+
return mcps;
|
|
160
|
+
}
|
|
161
|
+
|
|
80
162
|
/**
|
|
81
163
|
* Ensure a skill repo is available for the user. Clone if needed.
|
|
82
164
|
* Returns the local path to the skill directory inside the repo.
|
|
@@ -549,7 +631,15 @@ router.get('/preview', async (req, res) => {
|
|
|
549
631
|
if (name.startsWith('.')) continue;
|
|
550
632
|
const linkPath = path.join(userPaths.skillsDir, name);
|
|
551
633
|
const repo = await getSkillRepoUrl(linkPath, publicPaths.skillsRepoDir);
|
|
552
|
-
|
|
634
|
+
let localPath = '';
|
|
635
|
+
if (!repo) {
|
|
636
|
+
// Skill not from a git repo — check if it's a user-imported skill
|
|
637
|
+
try {
|
|
638
|
+
const realPath = await fs.realpath(linkPath);
|
|
639
|
+
if (realPath.includes('/skills-import/')) localPath = realPath;
|
|
640
|
+
} catch {}
|
|
641
|
+
}
|
|
642
|
+
skills.push({ name, repo, localPath });
|
|
553
643
|
}
|
|
554
644
|
} catch {}
|
|
555
645
|
|
|
@@ -558,9 +648,22 @@ router.get('/preview', async (req, res) => {
|
|
|
558
648
|
try {
|
|
559
649
|
const claudeJsonPath = path.join(userPaths.claudeDir, '.claude.json');
|
|
560
650
|
const claudeConfig = JSON.parse(await fs.readFile(claudeJsonPath, 'utf-8'));
|
|
651
|
+
|
|
652
|
+
// User-scoped MCPs (top-level mcpServers)
|
|
561
653
|
for (const name of Object.keys(claudeConfig.mcpServers || {})) {
|
|
562
654
|
const repo = await getMcpRepoUrl(name, publicPaths.mcpRepoDir);
|
|
563
|
-
|
|
655
|
+
const config = !repo ? (claudeConfig.mcpServers[name] || null) : null;
|
|
656
|
+
mcps.push({ name, repo, config, scope: 'user', mcpProjectPath: null });
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Project-scoped MCPs (projects[projectPath].mcpServers)
|
|
660
|
+
const projectMcpServers = claudeConfig.projects?.[projectPath]?.mcpServers || {};
|
|
661
|
+
for (const name of Object.keys(projectMcpServers)) {
|
|
662
|
+
// Skip if already listed as user-scoped
|
|
663
|
+
if (mcps.some(m => m.name === name)) continue;
|
|
664
|
+
const repo = await getMcpRepoUrl(name, publicPaths.mcpRepoDir);
|
|
665
|
+
const config = !repo ? (projectMcpServers[name] || null) : null;
|
|
666
|
+
mcps.push({ name, repo, config, scope: 'local', mcpProjectPath: projectPath });
|
|
564
667
|
}
|
|
565
668
|
} catch {}
|
|
566
669
|
|
|
@@ -708,15 +811,15 @@ router.post('/generate-description', async (req, res) => {
|
|
|
708
811
|
const skillList = skills.length > 0 ? skills.map(s => `- ${s}`).join('\n') : '(无)';
|
|
709
812
|
const mcpList = mcps.length > 0 ? mcps.map(m => `- ${m}`).join('\n') : '(无)';
|
|
710
813
|
|
|
711
|
-
const systemPrompt = `你是一个技术文档专家,专门为
|
|
814
|
+
const systemPrompt = `你是一个技术文档专家,专门为 共享项目 编写简洁、清晰的功能描述。
|
|
712
815
|
描述应该:
|
|
713
816
|
- 简明扼要,2-4 句话
|
|
714
|
-
-
|
|
817
|
+
- 说明项目的主要用途和能力
|
|
715
818
|
- 自然流畅,不使用模板化套话
|
|
716
819
|
- 使用中文
|
|
717
820
|
只输出描述内容,不要有前缀或标题。`;
|
|
718
821
|
|
|
719
|
-
const userPrompt =
|
|
822
|
+
const userPrompt = `请根据以下信息,为这个项目生成一段功能描述:
|
|
720
823
|
|
|
721
824
|
${claudeMdContent ? `## CLAUDE.md 内容\n${claudeMdContent.slice(0, 3000)}\n` : '## CLAUDE.md\n(未找到)\n'}
|
|
722
825
|
## 已集成的 Skills
|
|
@@ -792,6 +895,41 @@ router.post('/submit', async (req, res) => {
|
|
|
792
895
|
}
|
|
793
896
|
}
|
|
794
897
|
|
|
898
|
+
// Include local skill files for imported skills (repo is absolute path)
|
|
899
|
+
const parsedSkills = parseYamlSkills(agentYaml);
|
|
900
|
+
for (const skill of parsedSkills) {
|
|
901
|
+
if (!skill.repo.startsWith('/')) continue; // Only handle absolute local paths
|
|
902
|
+
try {
|
|
903
|
+
await addDirToZip(zip, skill.repo, `_skill_files/${skill.name}`);
|
|
904
|
+
console.log(`[AgentSubmit] Included local skill files for "${skill.name}" from ${skill.repo}`);
|
|
905
|
+
} catch (e) {
|
|
906
|
+
console.warn(`[AgentSubmit] Could not include skill files for "${skill.name}": ${e.message}`);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// Capture user-configured MCP configs (repo is empty)
|
|
911
|
+
const parsedMcps = parseYamlMcps(agentYaml);
|
|
912
|
+
if (parsedMcps.some(m => !m.repo)) {
|
|
913
|
+
try {
|
|
914
|
+
const userPaths = getUserPaths(userUuid);
|
|
915
|
+
const claudeJsonPath = path.join(userPaths.claudeDir, '.claude.json');
|
|
916
|
+
const claudeConfig = JSON.parse(await fs.readFile(claudeJsonPath, 'utf-8'));
|
|
917
|
+
const projectMcpServers = claudeConfig.projects?.[projectPath]?.mcpServers || {};
|
|
918
|
+
for (const mcp of parsedMcps) {
|
|
919
|
+
if (mcp.repo) continue; // Already has a repo URL
|
|
920
|
+
// Check user-scoped first, then project-scoped
|
|
921
|
+
const serverConfig = claudeConfig.mcpServers?.[mcp.name] ?? projectMcpServers[mcp.name];
|
|
922
|
+
if (serverConfig) {
|
|
923
|
+
const mcpConfig = { [mcp.name]: serverConfig };
|
|
924
|
+
zip.addFile(`_mcp_configs/${mcp.name}/mcp.json`, Buffer.from(JSON.stringify(mcpConfig, null, 2)));
|
|
925
|
+
console.log(`[AgentSubmit] Captured user-configured MCP config for "${mcp.name}"`);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
} catch (e) {
|
|
929
|
+
console.warn(`[AgentSubmit] Could not capture MCP configs: ${e.message}`);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
795
933
|
// Save ZIP to disk
|
|
796
934
|
const publicPaths = getPublicPaths();
|
|
797
935
|
const userSubmitDir = path.join(publicPaths.agentSubmissionsDir, userUuid);
|
|
@@ -946,6 +1084,199 @@ router.post('/submissions/:id/approve', async (req, res) => {
|
|
|
946
1084
|
// Get submitter name for commit message
|
|
947
1085
|
const submitter = submission.username || submission.email || `user-${submission.user_id}`;
|
|
948
1086
|
|
|
1087
|
+
// Get submitter's UUID for skill/MCP operations
|
|
1088
|
+
const allUsers = userDb.getAllUsers();
|
|
1089
|
+
const submitterUser = allUsers.find(u => u.id === submission.user_id);
|
|
1090
|
+
const submitterUuid = submitterUser?.uuid;
|
|
1091
|
+
|
|
1092
|
+
// Migrate local skills and user-configured MCPs before publishing
|
|
1093
|
+
const agentYamlPath = path.join(extractDir, 'agent.yaml');
|
|
1094
|
+
let agentYamlContent;
|
|
1095
|
+
try {
|
|
1096
|
+
agentYamlContent = await fs.readFile(agentYamlPath, 'utf-8');
|
|
1097
|
+
} catch {
|
|
1098
|
+
agentYamlContent = null;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
if (agentYamlContent && submitterUuid) {
|
|
1102
|
+
// === A. Migrate local skills (repo is absolute path starting with "/") ===
|
|
1103
|
+
const yamlSkills = parseYamlSkills(agentYamlContent);
|
|
1104
|
+
const localSkills = yamlSkills.filter(s => s.repo.startsWith('/'));
|
|
1105
|
+
|
|
1106
|
+
if (localSkills.length > 0) {
|
|
1107
|
+
let skillRepoPath;
|
|
1108
|
+
try {
|
|
1109
|
+
skillRepoPath = await ensureSystemRepo(); // git pull or clone
|
|
1110
|
+
} catch (e) {
|
|
1111
|
+
console.error('[AgentApprove] Failed to ensure system skill repo:', e.message);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if (skillRepoPath) {
|
|
1115
|
+
for (const skill of localSkills) {
|
|
1116
|
+
try {
|
|
1117
|
+
// Copy skill folder from extracted ZIP to system skill repo
|
|
1118
|
+
const srcSkillDir = path.join(extractDir, '_skill_files', skill.name);
|
|
1119
|
+
const destSkillDir = path.join(skillRepoPath, skill.name);
|
|
1120
|
+
|
|
1121
|
+
// Verify source exists in ZIP
|
|
1122
|
+
try {
|
|
1123
|
+
await fs.access(srcSkillDir);
|
|
1124
|
+
} catch {
|
|
1125
|
+
console.warn(`[AgentApprove] Skill files for "${skill.name}" not found in submission ZIP, skipping`);
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// Remove old version in repo if it exists
|
|
1130
|
+
await fs.rm(destSkillDir, { recursive: true, force: true });
|
|
1131
|
+
await copyDirRecursive(srcSkillDir, destSkillDir);
|
|
1132
|
+
console.log(`[AgentApprove] Copied skill "${skill.name}" to system skill repo`);
|
|
1133
|
+
|
|
1134
|
+
// Git: add, commit, push
|
|
1135
|
+
await runGit(['add', skill.name], skillRepoPath);
|
|
1136
|
+
await runGit(
|
|
1137
|
+
['commit', '-m', `feat: add skill ${skill.name} from approved agent submission #${submission.id}`],
|
|
1138
|
+
skillRepoPath
|
|
1139
|
+
);
|
|
1140
|
+
await runGit(['push'], skillRepoPath);
|
|
1141
|
+
console.log(`[AgentApprove] Pushed skill "${skill.name}" to remote`);
|
|
1142
|
+
|
|
1143
|
+
// Clean up submitter's skills-import directory for this skill
|
|
1144
|
+
const userPaths = getUserPaths(submitterUuid);
|
|
1145
|
+
try {
|
|
1146
|
+
await fs.rm(path.join(userPaths.skillsImportDir, skill.name), { recursive: true, force: true });
|
|
1147
|
+
} catch {}
|
|
1148
|
+
|
|
1149
|
+
// Re-install skill for submitter from the system repo
|
|
1150
|
+
await installSkill(skill.name, SYSTEM_REPO_URL, submitterUuid);
|
|
1151
|
+
console.log(`[AgentApprove] Re-installed skill "${skill.name}" for submitter from system repo`);
|
|
1152
|
+
|
|
1153
|
+
// Update agent.yaml: replace absolute path with system repo git URL
|
|
1154
|
+
const escapedName = skill.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1155
|
+
agentYamlContent = agentYamlContent.replace(
|
|
1156
|
+
new RegExp(`(- name: "${escapedName}"\\s*\\n\\s+repo: )"[^"]*"`, 'gm'),
|
|
1157
|
+
`$1"${SYSTEM_REPO_URL}"`
|
|
1158
|
+
);
|
|
1159
|
+
} catch (e) {
|
|
1160
|
+
console.error(`[AgentApprove] Failed to migrate skill "${skill.name}":`, e.message);
|
|
1161
|
+
// Continue with other skills even if one fails
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// === B. Migrate user-configured MCPs (repo is empty, config in _mcp_configs/) ===
|
|
1168
|
+
const yamlMcps = parseYamlMcps(agentYamlContent);
|
|
1169
|
+
const localMcps = yamlMcps.filter(m => !m.repo);
|
|
1170
|
+
|
|
1171
|
+
if (localMcps.length > 0) {
|
|
1172
|
+
let mcpRepoPath;
|
|
1173
|
+
try {
|
|
1174
|
+
mcpRepoPath = await ensureSystemMcpRepo(); // git pull or clone
|
|
1175
|
+
} catch (e) {
|
|
1176
|
+
console.error('[AgentApprove] Failed to ensure system MCP repo:', e.message);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
if (mcpRepoPath) {
|
|
1180
|
+
for (const mcp of localMcps) {
|
|
1181
|
+
try {
|
|
1182
|
+
// Read captured MCP config from ZIP extraction
|
|
1183
|
+
const mcpConfigPath = path.join(extractDir, '_mcp_configs', mcp.name, 'mcp.json');
|
|
1184
|
+
let mcpConfig;
|
|
1185
|
+
try {
|
|
1186
|
+
mcpConfig = JSON.parse(await fs.readFile(mcpConfigPath, 'utf-8'));
|
|
1187
|
+
} catch {
|
|
1188
|
+
console.warn(`[AgentApprove] No captured config for MCP "${mcp.name}", skipping`);
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// Create MCP folder in system MCP repo
|
|
1193
|
+
const mcpFolderPath = path.join(mcpRepoPath, mcp.name);
|
|
1194
|
+
await fs.mkdir(mcpFolderPath, { recursive: true });
|
|
1195
|
+
|
|
1196
|
+
// Write mcp.json
|
|
1197
|
+
await fs.writeFile(path.join(mcpFolderPath, 'mcp.json'), JSON.stringify(mcpConfig, null, 2));
|
|
1198
|
+
|
|
1199
|
+
// Write mcp.yaml with basic metadata
|
|
1200
|
+
const mcpYaml = `name: "${mcp.name}"\ndescription: "MCP service migrated from shared project '${submission.display_name}' (submission #${submission.id})"\n`;
|
|
1201
|
+
await fs.writeFile(path.join(mcpFolderPath, 'mcp.yaml'), mcpYaml);
|
|
1202
|
+
console.log(`[AgentApprove] Created MCP "${mcp.name}" in system MCP repo`);
|
|
1203
|
+
|
|
1204
|
+
// Git: add, commit, push
|
|
1205
|
+
await runGit(['add', mcp.name], mcpRepoPath);
|
|
1206
|
+
await runGit(
|
|
1207
|
+
['commit', '-m', `feat: add mcp ${mcp.name} from approved agent submission #${submission.id}`],
|
|
1208
|
+
mcpRepoPath
|
|
1209
|
+
);
|
|
1210
|
+
await runGit(['push'], mcpRepoPath);
|
|
1211
|
+
console.log(`[AgentApprove] Pushed MCP "${mcp.name}" to remote`);
|
|
1212
|
+
|
|
1213
|
+
// Remove MCP from submitter's .claude.json (user-scoped or project-scoped)
|
|
1214
|
+
const userPaths = getUserPaths(submitterUuid);
|
|
1215
|
+
const claudeJsonPath = path.join(userPaths.claudeDir, '.claude.json');
|
|
1216
|
+
try {
|
|
1217
|
+
const claudeConfig = JSON.parse(await fs.readFile(claudeJsonPath, 'utf-8'));
|
|
1218
|
+
let changed = false;
|
|
1219
|
+
// Remove from user-scoped
|
|
1220
|
+
if (claudeConfig.mcpServers) {
|
|
1221
|
+
for (const serverKey of Object.keys(mcpConfig)) {
|
|
1222
|
+
if (serverKey in claudeConfig.mcpServers) {
|
|
1223
|
+
delete claudeConfig.mcpServers[serverKey];
|
|
1224
|
+
changed = true;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
// Remove from all project-scoped entries
|
|
1229
|
+
if (claudeConfig.projects) {
|
|
1230
|
+
for (const projectConfig of Object.values(claudeConfig.projects)) {
|
|
1231
|
+
if (projectConfig?.mcpServers) {
|
|
1232
|
+
for (const serverKey of Object.keys(mcpConfig)) {
|
|
1233
|
+
if (serverKey in projectConfig.mcpServers) {
|
|
1234
|
+
delete projectConfig.mcpServers[serverKey];
|
|
1235
|
+
changed = true;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
if (changed) {
|
|
1242
|
+
await fs.writeFile(claudeJsonPath, JSON.stringify(claudeConfig, null, 2), 'utf-8');
|
|
1243
|
+
}
|
|
1244
|
+
} catch (e) {
|
|
1245
|
+
console.warn(`[AgentApprove] Could not remove MCP "${mcp.name}" from submitter .claude.json:`, e.message);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// Re-install MCP for submitter from system repo
|
|
1249
|
+
await installMcp(mcp.name, SYSTEM_MCP_REPO_URL, submitterUuid);
|
|
1250
|
+
console.log(`[AgentApprove] Re-installed MCP "${mcp.name}" for submitter from system repo`);
|
|
1251
|
+
|
|
1252
|
+
// Update agent.yaml: replace empty repo with system MCP repo git URL
|
|
1253
|
+
const escapedName = mcp.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1254
|
+
agentYamlContent = agentYamlContent.replace(
|
|
1255
|
+
new RegExp(`(- name: "${escapedName}"\\s*\\n\\s+repo: )""`, 'gm'),
|
|
1256
|
+
`$1"${SYSTEM_MCP_REPO_URL}"`
|
|
1257
|
+
);
|
|
1258
|
+
} catch (e) {
|
|
1259
|
+
console.error(`[AgentApprove] Failed to migrate MCP "${mcp.name}":`, e.message);
|
|
1260
|
+
// Continue with other MCPs even if one fails
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// Write updated agent.yaml back to extractDir so publishAgentToRepo uses correct repo URLs
|
|
1267
|
+
if (localSkills.length > 0 || localMcps.length > 0) {
|
|
1268
|
+
try {
|
|
1269
|
+
await fs.writeFile(agentYamlPath, agentYamlContent, 'utf-8');
|
|
1270
|
+
} catch (e) {
|
|
1271
|
+
console.error('[AgentApprove] Failed to write updated agent.yaml:', e.message);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// Remove temporary migration directories from extractDir before publishing to agent repo
|
|
1276
|
+
await fs.rm(path.join(extractDir, '_skill_files'), { recursive: true, force: true });
|
|
1277
|
+
await fs.rm(path.join(extractDir, '_mcp_configs'), { recursive: true, force: true });
|
|
1278
|
+
}
|
|
1279
|
+
|
|
949
1280
|
// Publish to git repo
|
|
950
1281
|
try {
|
|
951
1282
|
await publishAgentToRepo(submission.agent_name, extractDir, newVersion, submitter);
|