apteva 0.4.56 → 0.7.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.
Files changed (142) hide show
  1. package/README.md +216 -54
  2. package/cli.js +35 -0
  3. package/install.js +92 -0
  4. package/package.json +12 -79
  5. package/LICENSE +0 -63
  6. package/bin/apteva.js +0 -196
  7. package/dist/ActivityPage.kxzzb4yc.js +0 -3
  8. package/dist/ApiDocsPage.zq998hbm.js +0 -4
  9. package/dist/App.55rea8mn.js +0 -61
  10. package/dist/App.5ywb23z4.js +0 -53
  11. package/dist/App.6thds120.js +0 -4
  12. package/dist/App.9tctxzqm.js +0 -8
  13. package/dist/App.a8r8ttaz.js +0 -4
  14. package/dist/App.agsv5bje.js +0 -4
  15. package/dist/App.cepapqmx.js +0 -4
  16. package/dist/App.dp041gb3.js +0 -221
  17. package/dist/App.fds72zb5.js +0 -4
  18. package/dist/App.fg9qj2dq.js +0 -4
  19. package/dist/App.ndfejbm9.js +0 -4
  20. package/dist/App.nxmfmq1h.js +0 -13
  21. package/dist/App.qdfyt8ba.js +0 -4
  22. package/dist/App.x2d0ygt6.js +0 -4
  23. package/dist/App.yt9p4nr3.js +0 -20
  24. package/dist/App.zn4mw16t.js +0 -1
  25. package/dist/ConnectionsPage.8r96ryw7.js +0 -3
  26. package/dist/McpPage.3cwh0gnd.js +0 -3
  27. package/dist/SettingsPage.ykgdh5ev.js +0 -3
  28. package/dist/SkillsPage.4np1s65b.js +0 -3
  29. package/dist/TasksPage.4g08t7p6.js +0 -3
  30. package/dist/TelemetryPage.72w9pwcp.js +0 -3
  31. package/dist/TestsPage.z4fk3r7r.js +0 -3
  32. package/dist/ThreadsPage.63tcajeh.js +0 -3
  33. package/dist/apteva-kit.css +0 -1
  34. package/dist/icon.png +0 -0
  35. package/dist/index.html +0 -16
  36. package/dist/styles.css +0 -1
  37. package/scripts/postinstall.mjs +0 -102
  38. package/src/auth/index.ts +0 -394
  39. package/src/auth/middleware.ts +0 -213
  40. package/src/binary.ts +0 -536
  41. package/src/channels/index.ts +0 -40
  42. package/src/channels/telegram.ts +0 -311
  43. package/src/crypto.ts +0 -301
  44. package/src/db-tests.ts +0 -174
  45. package/src/db.ts +0 -3133
  46. package/src/integrations/agentdojo.ts +0 -559
  47. package/src/integrations/composio.ts +0 -437
  48. package/src/integrations/index.ts +0 -87
  49. package/src/integrations/skillsmp.ts +0 -318
  50. package/src/mcp-client.ts +0 -605
  51. package/src/mcp-handler.ts +0 -394
  52. package/src/mcp-platform.ts +0 -2370
  53. package/src/openapi.ts +0 -2410
  54. package/src/providers.ts +0 -597
  55. package/src/routes/api/agent-utils.ts +0 -890
  56. package/src/routes/api/agents.ts +0 -916
  57. package/src/routes/api/api-keys.ts +0 -95
  58. package/src/routes/api/channels.ts +0 -182
  59. package/src/routes/api/helpers.ts +0 -12
  60. package/src/routes/api/integrations.ts +0 -639
  61. package/src/routes/api/mcp.ts +0 -574
  62. package/src/routes/api/meta-agent.ts +0 -195
  63. package/src/routes/api/projects.ts +0 -112
  64. package/src/routes/api/providers.ts +0 -424
  65. package/src/routes/api/skills.ts +0 -537
  66. package/src/routes/api/system.ts +0 -333
  67. package/src/routes/api/telemetry.ts +0 -203
  68. package/src/routes/api/tests.ts +0 -148
  69. package/src/routes/api/triggers.ts +0 -518
  70. package/src/routes/api/users.ts +0 -148
  71. package/src/routes/api/webhooks.ts +0 -171
  72. package/src/routes/api.ts +0 -53
  73. package/src/routes/auth.ts +0 -251
  74. package/src/routes/share.ts +0 -86
  75. package/src/routes/static.ts +0 -131
  76. package/src/server.ts +0 -642
  77. package/src/test-runner.ts +0 -598
  78. package/src/triggers/agentdojo.ts +0 -253
  79. package/src/triggers/composio.ts +0 -264
  80. package/src/triggers/index.ts +0 -71
  81. package/src/tui/AgentList.tsx +0 -145
  82. package/src/tui/App.tsx +0 -102
  83. package/src/tui/Login.tsx +0 -104
  84. package/src/tui/api.ts +0 -72
  85. package/src/tui/index.tsx +0 -7
  86. package/src/web/App.tsx +0 -455
  87. package/src/web/components/activity/ActivityPage.tsx +0 -314
  88. package/src/web/components/activity/index.ts +0 -1
  89. package/src/web/components/agents/AgentCard.tsx +0 -189
  90. package/src/web/components/agents/AgentPanel.tsx +0 -2244
  91. package/src/web/components/agents/AgentsView.tsx +0 -180
  92. package/src/web/components/agents/CreateAgentModal.tsx +0 -475
  93. package/src/web/components/agents/index.ts +0 -4
  94. package/src/web/components/api/ApiDocsPage.tsx +0 -842
  95. package/src/web/components/auth/CreateAccountStep.tsx +0 -176
  96. package/src/web/components/auth/LoginPage.tsx +0 -91
  97. package/src/web/components/auth/index.ts +0 -2
  98. package/src/web/components/common/Icons.tsx +0 -250
  99. package/src/web/components/common/LoadingSpinner.tsx +0 -44
  100. package/src/web/components/common/Modal.tsx +0 -199
  101. package/src/web/components/common/Select.tsx +0 -97
  102. package/src/web/components/common/index.ts +0 -20
  103. package/src/web/components/connections/ConnectionsPage.tsx +0 -54
  104. package/src/web/components/connections/IntegrationsTab.tsx +0 -170
  105. package/src/web/components/connections/OverviewTab.tsx +0 -137
  106. package/src/web/components/connections/TriggersTab.tsx +0 -1346
  107. package/src/web/components/dashboard/Dashboard.tsx +0 -572
  108. package/src/web/components/dashboard/index.ts +0 -1
  109. package/src/web/components/index.ts +0 -21
  110. package/src/web/components/layout/ErrorBanner.tsx +0 -18
  111. package/src/web/components/layout/Header.tsx +0 -332
  112. package/src/web/components/layout/Sidebar.tsx +0 -231
  113. package/src/web/components/layout/index.ts +0 -3
  114. package/src/web/components/mcp/IntegrationsPanel.tsx +0 -857
  115. package/src/web/components/mcp/McpPage.tsx +0 -2515
  116. package/src/web/components/mcp/index.ts +0 -1
  117. package/src/web/components/meta-agent/MetaAgent.tsx +0 -245
  118. package/src/web/components/onboarding/OnboardingWizard.tsx +0 -404
  119. package/src/web/components/onboarding/index.ts +0 -1
  120. package/src/web/components/settings/SettingsPage.tsx +0 -2776
  121. package/src/web/components/settings/index.ts +0 -1
  122. package/src/web/components/skills/SkillsPage.tsx +0 -1200
  123. package/src/web/components/tasks/TasksPage.tsx +0 -1116
  124. package/src/web/components/tasks/index.ts +0 -1
  125. package/src/web/components/telemetry/TelemetryPage.tsx +0 -1129
  126. package/src/web/components/tests/TestsPage.tsx +0 -594
  127. package/src/web/components/threads/ThreadsPage.tsx +0 -315
  128. package/src/web/context/AuthContext.tsx +0 -242
  129. package/src/web/context/ProjectContext.tsx +0 -214
  130. package/src/web/context/TelemetryContext.tsx +0 -299
  131. package/src/web/context/ThemeContext.tsx +0 -90
  132. package/src/web/context/UIModeContext.tsx +0 -49
  133. package/src/web/context/index.ts +0 -12
  134. package/src/web/hooks/index.ts +0 -3
  135. package/src/web/hooks/useAgents.ts +0 -115
  136. package/src/web/hooks/useOnboarding.ts +0 -20
  137. package/src/web/hooks/useProviders.ts +0 -75
  138. package/src/web/icon.png +0 -0
  139. package/src/web/index.html +0 -16
  140. package/src/web/styles.css +0 -118
  141. package/src/web/themes.ts +0 -162
  142. package/src/web/types.ts +0 -298
@@ -1,537 +0,0 @@
1
- import { json } from "./helpers";
2
- import { AgentDB, SkillDB, type Skill } from "../../db";
3
- import { ProviderKeys } from "../../providers";
4
- import { SkillsmpProvider, parseSkillMd } from "../../integrations/skillsmp";
5
- import { buildAgentConfig, pushConfigToAgent, pushSkillsToAgent } from "./agent-utils";
6
-
7
- export async function handleSkillRoutes(
8
- req: Request,
9
- path: string,
10
- method: string,
11
- ): Promise<Response | null> {
12
- // ============ Skills CRUD ============
13
-
14
- // GET /api/skills - List skills (optionally filtered by project)
15
- if (path === "/api/skills" && method === "GET") {
16
- const url = new URL(req.url);
17
- const projectFilter = url.searchParams.get("project"); // "all", "global", or project ID
18
- const forAgent = url.searchParams.get("forAgent"); // agent's project ID (shows global + project)
19
-
20
- let skills;
21
- if (forAgent !== null) {
22
- // Get skills available for an agent (global + agent's project)
23
- skills = SkillDB.findForAgent(forAgent || null);
24
- } else if (projectFilter === "global") {
25
- skills = SkillDB.findGlobal();
26
- } else if (projectFilter && projectFilter !== "all") {
27
- skills = SkillDB.findByProject(projectFilter);
28
- } else {
29
- skills = SkillDB.findAll();
30
- }
31
- return json({ skills });
32
- }
33
-
34
- // POST /api/skills - Create a new skill
35
- if (path === "/api/skills" && method === "POST") {
36
- try {
37
- const body = await req.json();
38
- const { name, description, content, version, license, compatibility, metadata, allowed_tools, source, source_url, enabled, project_id } = body;
39
-
40
- if (!name || !description || !content) {
41
- return json({ error: "name, description, and content are required" }, 400);
42
- }
43
-
44
- // Validate name format (lowercase, hyphens only)
45
- if (!/^[a-z][a-z0-9-]*[a-z0-9]$|^[a-z]$/.test(name)) {
46
- return json({ error: "name must be lowercase letters, numbers, and hyphens only" }, 400);
47
- }
48
-
49
- if (SkillDB.exists(name)) {
50
- return json({ error: "A skill with this name already exists" }, 400);
51
- }
52
-
53
- const skill = SkillDB.create({
54
- name,
55
- description,
56
- content,
57
- version: version || "1.0.0",
58
- license: license || null,
59
- compatibility: compatibility || null,
60
- metadata: metadata || {},
61
- allowed_tools: allowed_tools || [],
62
- source: source || "local",
63
- source_url: source_url || null,
64
- enabled: enabled !== false,
65
- project_id: project_id || null,
66
- });
67
-
68
- return json({ skill }, 201);
69
- } catch (err) {
70
- console.error("Failed to create skill:", err);
71
- return json({ error: `Failed to create skill: ${err}` }, 500);
72
- }
73
- }
74
-
75
- // POST /api/skills/import - Import a skill from SKILL.md content
76
- if (path === "/api/skills/import" && method === "POST") {
77
- try {
78
- const body = await req.json();
79
- const { content, source, source_url } = body;
80
-
81
- if (!content) {
82
- return json({ error: "content is required" }, 400);
83
- }
84
-
85
- const parsed = parseSkillMd(content);
86
- if (!parsed) {
87
- return json({ error: "Invalid SKILL.md format. Must have YAML frontmatter with name and description." }, 400);
88
- }
89
-
90
- if (SkillDB.exists(parsed.name)) {
91
- return json({ error: `A skill named "${parsed.name}" already exists` }, 400);
92
- }
93
-
94
- const skill = SkillDB.create({
95
- name: parsed.name,
96
- description: parsed.description,
97
- content: content, // Store full content including frontmatter
98
- version: (parsed as any).version || "1.0.0",
99
- license: parsed.license || null,
100
- compatibility: parsed.compatibility || null,
101
- metadata: parsed.metadata || {},
102
- allowed_tools: parsed.allowedTools || [],
103
- source: source || "import",
104
- source_url: source_url || null,
105
- enabled: true,
106
- project_id: null,
107
- });
108
-
109
- return json({ skill }, 201);
110
- } catch (err) {
111
- console.error("Failed to import skill:", err);
112
- return json({ error: `Failed to import skill: ${err}` }, 500);
113
- }
114
- }
115
-
116
- // GET /api/skills/:id - Get a skill
117
- const skillMatch = path.match(/^\/api\/skills\/([^/]+)$/);
118
-
119
- // GET /api/skills/:id/export - Export a skill as SKILL.md
120
- const skillExportMatch = path.match(/^\/api\/skills\/([^/]+)\/export$/);
121
- if (skillExportMatch && method === "GET") {
122
- const skill = SkillDB.findById(skillExportMatch[1]);
123
- if (!skill) {
124
- return json({ error: "Skill not found" }, 404);
125
- }
126
-
127
- // Return the raw content
128
- return new Response(skill.content, {
129
- headers: {
130
- "Content-Type": "text/markdown",
131
- "Content-Disposition": `attachment; filename="${skill.name}-SKILL.md"`,
132
- },
133
- });
134
- }
135
-
136
- // POST /api/skills/:id/toggle - Toggle skill enabled/disabled
137
- const skillToggleMatch = path.match(/^\/api\/skills\/([^/]+)\/toggle$/);
138
- if (skillToggleMatch && method === "POST") {
139
- const skill = SkillDB.findById(skillToggleMatch[1]);
140
- if (!skill) {
141
- return json({ error: "Skill not found" }, 404);
142
- }
143
-
144
- const updated = SkillDB.setEnabled(skillToggleMatch[1], !skill.enabled);
145
- return json({ skill: updated });
146
- }
147
-
148
- // ============ SkillsMP Marketplace ============
149
-
150
- // GET /api/skills/marketplace/search - Search skills marketplace
151
- if (path === "/api/skills/marketplace/search" && method === "GET") {
152
- const url = new URL(req.url);
153
- const query = url.searchParams.get("q") || "";
154
- const page = parseInt(url.searchParams.get("page") || "1", 10);
155
-
156
- // Get SkillsMP API key if configured
157
- const skillsmpKey = ProviderKeys.getDecrypted("skillsmp");
158
-
159
- const result = await SkillsmpProvider.search(skillsmpKey || "", query, page);
160
- return json(result);
161
- }
162
-
163
- // GET /api/skills/marketplace/featured - Get featured skills
164
- if (path === "/api/skills/marketplace/featured" && method === "GET") {
165
- const skillsmpKey = ProviderKeys.getDecrypted("skillsmp");
166
- const skills = await SkillsmpProvider.getFeatured(skillsmpKey || "");
167
- return json({ skills });
168
- }
169
-
170
- // GET /api/skills/marketplace/:id - Get skill details from marketplace
171
- const marketplaceSkillMatch = path.match(/^\/api\/skills\/marketplace\/([^/]+)$/);
172
- if (marketplaceSkillMatch && method === "GET") {
173
- const skillsmpKey = ProviderKeys.getDecrypted("skillsmp");
174
- const skill = await SkillsmpProvider.getSkill(skillsmpKey || "", marketplaceSkillMatch[1]);
175
- if (!skill) {
176
- return json({ error: "Skill not found in marketplace" }, 404);
177
- }
178
- return json({ skill });
179
- }
180
-
181
- // POST /api/skills/marketplace/:id/install - Install a skill from marketplace
182
- const marketplaceInstallMatch = path.match(/^\/api\/skills\/marketplace\/([^/]+)\/install$/);
183
- if (marketplaceInstallMatch && method === "POST") {
184
- const skillsmpKey = ProviderKeys.getDecrypted("skillsmp");
185
- const marketplaceSkill = await SkillsmpProvider.getSkill(skillsmpKey || "", marketplaceInstallMatch[1]);
186
-
187
- if (!marketplaceSkill) {
188
- return json({ error: "Skill not found in marketplace" }, 404);
189
- }
190
-
191
- if (SkillDB.exists(marketplaceSkill.name)) {
192
- return json({ error: `A skill named "${marketplaceSkill.name}" already exists` }, 400);
193
- }
194
-
195
- const skill = SkillDB.create({
196
- name: marketplaceSkill.name,
197
- description: marketplaceSkill.description,
198
- content: marketplaceSkill.content,
199
- version: marketplaceSkill.version || "1.0.0",
200
- license: marketplaceSkill.license,
201
- compatibility: marketplaceSkill.compatibility,
202
- metadata: {
203
- author: marketplaceSkill.author,
204
- version: marketplaceSkill.version,
205
- ...(marketplaceSkill.repository ? { repository: marketplaceSkill.repository } : {}),
206
- },
207
- allowed_tools: [],
208
- source: "skillsmp",
209
- project_id: null,
210
- source_url: marketplaceSkill.repository || `https://skillsmp.com/skills/${marketplaceSkill.id}`,
211
- enabled: true,
212
- });
213
-
214
- return json({ skill }, 201);
215
- }
216
-
217
- // ============ GitHub Skills ============
218
-
219
- // GET /api/skills/github/:owner/:repo - List skills from a GitHub repo
220
- const githubRepoMatch = path.match(/^\/api\/skills\/github\/([^/]+)\/([^/]+)$/);
221
- if (githubRepoMatch && method === "GET") {
222
- const [, owner, repo] = githubRepoMatch;
223
-
224
- const githubHeaders = {
225
- "Accept": "application/vnd.github.v3+json",
226
- "User-Agent": "Apteva-Skills-Browser",
227
- };
228
-
229
- // Helper to fetch directory contents
230
- const fetchDir = async (dirPath: string) => {
231
- const url = `https://api.github.com/repos/${owner}/${repo}/contents/${dirPath}`;
232
- const res = await fetch(url, { headers: githubHeaders });
233
- if (!res.ok) return [];
234
- return await res.json() as Array<{
235
- name: string;
236
- path: string;
237
- type: "file" | "dir";
238
- size?: number;
239
- download_url?: string;
240
- }>;
241
- };
242
-
243
- // Helper to find skills in a directory (looks for subdirs with SKILL.md)
244
- const findSkillsInDir = async (basePath: string) => {
245
- const skills: Array<{
246
- name: string;
247
- description: string;
248
- path: string;
249
- size: number;
250
- downloadUrl: string;
251
- }> = [];
252
-
253
- const contents = await fetchDir(basePath);
254
- const skillDirs = contents.filter(item => item.type === "dir");
255
-
256
- for (const dir of skillDirs) {
257
- try {
258
- const dirContents = await fetchDir(dir.path);
259
- const skillFile = dirContents.find(
260
- f => f.type === "file" && f.name.toLowerCase() === "skill.md"
261
- );
262
-
263
- if (skillFile && skillFile.download_url) {
264
- const skillResponse = await fetch(skillFile.download_url);
265
- if (skillResponse.ok) {
266
- const content = await skillResponse.text();
267
-
268
- // Parse frontmatter for description
269
- let description = "";
270
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
271
- if (frontmatterMatch) {
272
- const descMatch = frontmatterMatch[1].match(/description:\s*["']?([^"'\n]+)["']?/);
273
- if (descMatch) {
274
- description = descMatch[1].trim();
275
- }
276
- }
277
-
278
- // If no frontmatter description, try to get first paragraph
279
- if (!description) {
280
- const contentWithoutFrontmatter = content.replace(/^---\n[\s\S]*?\n---\n?/, "").trim();
281
- const firstPara = contentWithoutFrontmatter.split("\n\n")[0];
282
- if (firstPara && !firstPara.startsWith("#")) {
283
- description = firstPara.slice(0, 200);
284
- }
285
- }
286
-
287
- skills.push({
288
- name: dir.name,
289
- description: description || `Skill from ${dir.name}`,
290
- path: skillFile.path,
291
- size: skillFile.size || 0,
292
- downloadUrl: skillFile.download_url,
293
- });
294
- }
295
- }
296
- } catch (e) {
297
- // Skip this directory on error
298
- }
299
- }
300
-
301
- return skills;
302
- };
303
-
304
- try {
305
- // Fetch root contents
306
- const rootContents = await fetchDir("");
307
-
308
- if (rootContents.length === 0) {
309
- return json({ error: "Repository not found or empty" }, 404);
310
- }
311
-
312
- let skills: Array<{
313
- name: string;
314
- description: string;
315
- path: string;
316
- size: number;
317
- downloadUrl: string;
318
- }> = [];
319
-
320
- // Check common skill directory patterns: skills/, src/skills/, .claude/skills/
321
- const skillsDirs = ["skills", "src/skills", ".claude/skills"];
322
- for (const skillsDir of skillsDirs) {
323
- const dirExists = rootContents.find(
324
- item => item.type === "dir" && item.name === skillsDir.split("/")[0]
325
- );
326
- if (dirExists || skillsDir.includes("/")) {
327
- const foundSkills = await findSkillsInDir(skillsDir);
328
- if (foundSkills.length > 0) {
329
- skills = foundSkills;
330
- break;
331
- }
332
- }
333
- }
334
-
335
- // If no skills found in common dirs, check root level subdirectories
336
- if (skills.length === 0) {
337
- skills = await findSkillsInDir("");
338
- }
339
-
340
- // Also check for SKILL.md in root (single-skill repo)
341
- const rootSkillFile = rootContents.find(
342
- f => f.type === "file" && f.name.toLowerCase() === "skill.md"
343
- );
344
- if (rootSkillFile && rootSkillFile.download_url) {
345
- const skillResponse = await fetch(rootSkillFile.download_url);
346
- if (skillResponse.ok) {
347
- const content = await skillResponse.text();
348
- let name = repo;
349
- let description = "";
350
-
351
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
352
- if (frontmatterMatch) {
353
- const nameMatch = frontmatterMatch[1].match(/name:\s*["']?([^"'\n]+)["']?/);
354
- const descMatch = frontmatterMatch[1].match(/description:\s*["']?([^"'\n]+)["']?/);
355
- if (nameMatch) name = nameMatch[1].trim();
356
- if (descMatch) description = descMatch[1].trim();
357
- }
358
-
359
- skills.unshift({
360
- name,
361
- description: description || `Skill from ${repo}`,
362
- path: rootSkillFile.path,
363
- size: rootSkillFile.size || 0,
364
- downloadUrl: rootSkillFile.download_url,
365
- });
366
- }
367
- }
368
-
369
- return json({
370
- skills,
371
- repo: { owner, repo, url: `https://github.com/${owner}/${repo}` }
372
- });
373
- } catch (e) {
374
- console.error("GitHub API error:", e);
375
- return json({ error: "Failed to fetch from GitHub" }, 500);
376
- }
377
- }
378
-
379
- // POST /api/skills/github/install - Install a skill from GitHub
380
- if (path === "/api/skills/github/install" && method === "POST") {
381
- try {
382
- const body = await req.json() as {
383
- owner: string;
384
- repo: string;
385
- skillName: string;
386
- downloadUrl: string;
387
- projectId?: string | null;
388
- };
389
-
390
- const { owner, repo, skillName, downloadUrl, projectId } = body;
391
-
392
- if (!owner || !repo || !skillName || !downloadUrl) {
393
- return json({ error: "owner, repo, skillName, and downloadUrl are required" }, 400);
394
- }
395
-
396
- // Check if skill already exists
397
- if (SkillDB.exists(skillName)) {
398
- return json({ error: `A skill named "${skillName}" already exists` }, 400);
399
- }
400
-
401
- // Fetch the skill content
402
- const response = await fetch(downloadUrl);
403
- if (!response.ok) {
404
- return json({ error: "Failed to fetch skill content" }, 500);
405
- }
406
-
407
- const content = await response.text();
408
-
409
- // Parse frontmatter
410
- let name = skillName;
411
- let description = "";
412
- let version = "1.0.0";
413
- let license = null;
414
- let compatibility = null;
415
- let skillContent = content;
416
-
417
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
418
- if (frontmatterMatch) {
419
- const frontmatter = frontmatterMatch[1];
420
- skillContent = frontmatterMatch[2].trim();
421
-
422
- const nameMatch = frontmatter.match(/name:\s*["']?([^"'\n]+)["']?/);
423
- const descMatch = frontmatter.match(/description:\s*["']?([^"'\n]+)["']?/);
424
- const versionMatch = frontmatter.match(/version:\s*["']?([^"'\n]+)["']?/);
425
- const licenseMatch = frontmatter.match(/license:\s*["']?([^"'\n]+)["']?/);
426
- const compatMatch = frontmatter.match(/compatibility:\s*["']?([^"'\n]+)["']?/);
427
-
428
- if (nameMatch) name = nameMatch[1].trim();
429
- if (descMatch) description = descMatch[1].trim();
430
- if (versionMatch) version = versionMatch[1].trim();
431
- if (licenseMatch) license = licenseMatch[1].trim();
432
- if (compatMatch) compatibility = compatMatch[1].trim();
433
- }
434
-
435
- // Create the skill in DB
436
- const skill = SkillDB.create({
437
- name,
438
- description: description || `Skill from ${owner}/${repo}`,
439
- content: skillContent,
440
- version,
441
- license,
442
- compatibility,
443
- metadata: { owner, repo, originalName: skillName },
444
- allowed_tools: [],
445
- source: "github",
446
- source_url: `https://github.com/${owner}/${repo}/blob/main/${skillName}/SKILL.md`,
447
- enabled: true,
448
- project_id: projectId || null,
449
- });
450
-
451
- return json({ skill }, 201);
452
- } catch (e) {
453
- console.error("GitHub install error:", e);
454
- return json({ error: "Failed to install skill from GitHub" }, 500);
455
- }
456
- }
457
-
458
- // Skill CRUD by ID (must come after more specific routes like /toggle, /export, /marketplace, /github)
459
- if (skillMatch && method === "GET") {
460
- const skill = SkillDB.findById(skillMatch[1]);
461
- if (!skill) {
462
- return json({ error: "Skill not found" }, 404);
463
- }
464
- return json({ skill });
465
- }
466
-
467
- // PUT /api/skills/:id - Update a skill
468
- if (skillMatch && method === "PUT") {
469
- const skill = SkillDB.findById(skillMatch[1]);
470
- if (!skill) {
471
- return json({ error: "Skill not found" }, 404);
472
- }
473
-
474
- try {
475
- const body = await req.json();
476
- const updates: Partial<Skill> = {};
477
-
478
- if (body.name !== undefined) updates.name = body.name;
479
- if (body.description !== undefined) updates.description = body.description;
480
- if (body.content !== undefined) updates.content = body.content;
481
- if (body.license !== undefined) updates.license = body.license;
482
- if (body.compatibility !== undefined) updates.compatibility = body.compatibility;
483
- if (body.metadata !== undefined) updates.metadata = body.metadata;
484
- if (body.allowed_tools !== undefined) updates.allowed_tools = body.allowed_tools;
485
- if (body.enabled !== undefined) updates.enabled = body.enabled;
486
- if (body.project_id !== undefined) updates.project_id = body.project_id;
487
-
488
- // Auto-increment version if content changed
489
- if (body.content !== undefined && body.content !== skill.content) {
490
- const [major, minor, patch] = (skill.version || "1.0.0").split(".").map(Number);
491
- updates.version = `${major}.${minor}.${patch + 1}`;
492
- } else if (body.version !== undefined) {
493
- updates.version = body.version;
494
- }
495
-
496
- const updated = SkillDB.update(skillMatch[1], updates);
497
-
498
- // Push updated skill to all running agents that have it
499
- const agentsWithSkill = AgentDB.findBySkill(skillMatch[1]);
500
- const runningAgents = agentsWithSkill.filter(a => a.status === "running" && a.port);
501
-
502
- await Promise.allSettled(runningAgents.map(async (agent) => {
503
- try {
504
- const providerKey = ProviderKeys.getDecrypted(agent.provider);
505
- if (providerKey) {
506
- const config = buildAgentConfig(agent, providerKey);
507
- await pushConfigToAgent(agent.id, agent.port!, config);
508
- if (config.skills?.definitions?.length > 0) {
509
- await pushSkillsToAgent(agent.id, agent.port!, config.skills.definitions);
510
- }
511
- console.log(`Pushed skill update to agent ${agent.name}`);
512
- }
513
- } catch (err) {
514
- console.error(`Failed to push skill update to agent ${agent.name}:`, err);
515
- }
516
- }));
517
-
518
- return json({ skill: updated, agents_updated: runningAgents.length });
519
- } catch (err) {
520
- console.error("Failed to update skill:", err);
521
- return json({ error: `Failed to update skill: ${err}` }, 500);
522
- }
523
- }
524
-
525
- // DELETE /api/skills/:id - Delete a skill
526
- if (skillMatch && method === "DELETE") {
527
- const skill = SkillDB.findById(skillMatch[1]);
528
- if (!skill) {
529
- return json({ error: "Skill not found" }, 404);
530
- }
531
-
532
- SkillDB.delete(skillMatch[1]);
533
- return json({ success: true });
534
- }
535
-
536
- return null;
537
- }