apteva 0.4.4 → 0.4.5
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/App.y11xqt9m.js +227 -0
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/db.ts +93 -19
- package/src/integrations/agentdojo.ts +350 -0
- package/src/openapi.ts +195 -0
- package/src/providers.ts +78 -7
- package/src/routes/api/agent-utils.ts +638 -0
- package/src/routes/api/agents.ts +743 -0
- package/src/routes/api/helpers.ts +12 -0
- package/src/routes/api/integrations.ts +608 -0
- package/src/routes/api/mcp.ts +377 -0
- package/src/routes/api/meta-agent.ts +145 -0
- package/src/routes/api/projects.ts +95 -0
- package/src/routes/api/providers.ts +269 -0
- package/src/routes/api/skills.ts +538 -0
- package/src/routes/api/system.ts +215 -0
- package/src/routes/api/telemetry.ts +142 -0
- package/src/routes/api/users.ts +148 -0
- package/src/routes/api.ts +32 -3477
- package/src/server.ts +1 -1
- package/src/web/components/api/ApiDocsPage.tsx +259 -0
- package/src/web/components/mcp/IntegrationsPanel.tsx +15 -8
- package/src/web/components/mcp/McpPage.tsx +458 -174
- package/src/web/components/settings/SettingsPage.tsx +275 -36
- package/src/web/components/skills/SkillsPage.tsx +330 -1
- package/src/web/components/tasks/TasksPage.tsx +187 -58
- package/src/web/context/TelemetryContext.tsx +14 -1
- package/src/web/hooks/useAgents.ts +9 -0
- package/src/web/types.ts +22 -4
- package/dist/App.mbp9atpm.js +0 -227
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
// AgentDojo Integration Provider
|
|
2
|
+
// Connects to our hosted MCP API
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
IntegrationProvider,
|
|
6
|
+
IntegrationApp,
|
|
7
|
+
ConnectedAccount,
|
|
8
|
+
ConnectionRequest,
|
|
9
|
+
ConnectionCredentials,
|
|
10
|
+
} from "./index";
|
|
11
|
+
|
|
12
|
+
// AgentDojo MCP API base URL
|
|
13
|
+
const AGENTDOJO_API_BASE = process.env.AGENTDOJO_API_BASE || "https://api.agentdojo.dev";
|
|
14
|
+
|
|
15
|
+
export const AgentDojoProvider: IntegrationProvider = {
|
|
16
|
+
id: "agentdojo",
|
|
17
|
+
name: "AgentDojo",
|
|
18
|
+
|
|
19
|
+
// List available toolkits as "apps"
|
|
20
|
+
async listApps(apiKey: string): Promise<IntegrationApp[]> {
|
|
21
|
+
const res = await fetch(`${AGENTDOJO_API_BASE}/toolkits?include_tools=true`, {
|
|
22
|
+
headers: {
|
|
23
|
+
"X-API-Key": apiKey,
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
console.error("AgentDojo listApps error:", res.status, await res.text());
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const data = await res.json();
|
|
34
|
+
const toolkits = data.toolkits || [];
|
|
35
|
+
|
|
36
|
+
return toolkits.map((toolkit: any) => ({
|
|
37
|
+
id: toolkit.id,
|
|
38
|
+
name: toolkit.display_name || toolkit.name,
|
|
39
|
+
slug: toolkit.name,
|
|
40
|
+
description: toolkit.description || null,
|
|
41
|
+
logo: toolkit.icon_url || null,
|
|
42
|
+
categories: [],
|
|
43
|
+
// If toolkit requires auth, it needs API_KEY connection
|
|
44
|
+
authSchemes: toolkit.requires_auth ? ["API_KEY"] : ["NONE"],
|
|
45
|
+
toolsCount: toolkit.tools_count || toolkit.tools?.length || 0,
|
|
46
|
+
tools: toolkit.tools || [],
|
|
47
|
+
}));
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
// List user's credentials (stored locally, validated against toolkits)
|
|
51
|
+
async listConnectedAccounts(apiKey: string, userId: string): Promise<ConnectedAccount[]> {
|
|
52
|
+
// Get list of credentials from our credentials API
|
|
53
|
+
const res = await fetch(`${AGENTDOJO_API_BASE}/credentials`, {
|
|
54
|
+
headers: {
|
|
55
|
+
"X-API-Key": apiKey,
|
|
56
|
+
"Content-Type": "application/json",
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (!res.ok) {
|
|
61
|
+
console.error("AgentDojo listConnectedAccounts error:", res.status, await res.text());
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const data = await res.json();
|
|
66
|
+
const credentials = data.data || data.credentials || [];
|
|
67
|
+
|
|
68
|
+
return credentials.map((cred: any) => ({
|
|
69
|
+
id: cred.id,
|
|
70
|
+
appId: cred.provider_id || cred.toolkit_id || cred.provider_name,
|
|
71
|
+
appName: cred.provider_name || cred.toolkit_name || cred.provider_id,
|
|
72
|
+
status: cred.is_valid !== false ? "active" : "failed",
|
|
73
|
+
createdAt: cred.created_at || new Date().toISOString(),
|
|
74
|
+
metadata: {
|
|
75
|
+
keyHint: cred.key_hint,
|
|
76
|
+
},
|
|
77
|
+
}));
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// Store credentials for a toolkit
|
|
81
|
+
async initiateConnection(
|
|
82
|
+
apiKey: string,
|
|
83
|
+
userId: string,
|
|
84
|
+
appSlug: string,
|
|
85
|
+
redirectUrl: string,
|
|
86
|
+
credentials?: ConnectionCredentials
|
|
87
|
+
): Promise<ConnectionRequest> {
|
|
88
|
+
if (!credentials?.apiKey) {
|
|
89
|
+
throw new Error("API key is required for AgentDojo connections");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Store the credential
|
|
93
|
+
const res = await fetch(`${AGENTDOJO_API_BASE}/credentials`, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: {
|
|
96
|
+
"X-API-Key": apiKey,
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
provider_id: appSlug,
|
|
101
|
+
provider_name: appSlug,
|
|
102
|
+
api_key: credentials.apiKey,
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
const text = await res.text();
|
|
108
|
+
console.error("AgentDojo initiateConnection error:", res.status, text);
|
|
109
|
+
throw new Error(`Failed to store credentials: ${text}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const data = await res.json();
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
redirectUrl: null, // No OAuth redirect
|
|
116
|
+
connectionId: data.data?.id || data.id,
|
|
117
|
+
status: "active", // Credentials are immediately active
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
async getConnectionStatus(apiKey: string, connectionId: string): Promise<ConnectedAccount | null> {
|
|
122
|
+
const res = await fetch(`${AGENTDOJO_API_BASE}/credentials`, {
|
|
123
|
+
headers: {
|
|
124
|
+
"X-API-Key": apiKey,
|
|
125
|
+
"Content-Type": "application/json",
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (!res.ok) return null;
|
|
130
|
+
|
|
131
|
+
const data = await res.json();
|
|
132
|
+
const credentials = data.data || data.credentials || [];
|
|
133
|
+
const cred = credentials.find((c: any) => c.id === connectionId);
|
|
134
|
+
|
|
135
|
+
if (!cred) return null;
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
id: cred.id,
|
|
139
|
+
appId: cred.provider_id || cred.toolkit_id,
|
|
140
|
+
appName: cred.provider_name || cred.toolkit_name,
|
|
141
|
+
status: "active",
|
|
142
|
+
createdAt: cred.created_at || new Date().toISOString(),
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
async disconnect(apiKey: string, connectionId: string): Promise<boolean> {
|
|
147
|
+
const res = await fetch(`${AGENTDOJO_API_BASE}/credentials/${connectionId}`, {
|
|
148
|
+
method: "DELETE",
|
|
149
|
+
headers: {
|
|
150
|
+
"X-API-Key": apiKey,
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return res.ok;
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// ============ AgentDojo MCP Server Management ============
|
|
160
|
+
|
|
161
|
+
export interface AgentDojoServer {
|
|
162
|
+
id: string;
|
|
163
|
+
slug: string;
|
|
164
|
+
name: string;
|
|
165
|
+
description: string | null;
|
|
166
|
+
url: string;
|
|
167
|
+
tools: Array<{
|
|
168
|
+
id: string;
|
|
169
|
+
name: string;
|
|
170
|
+
description: string;
|
|
171
|
+
}>;
|
|
172
|
+
isActive: boolean;
|
|
173
|
+
createdAt: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface AgentDojoToolkit {
|
|
177
|
+
id: string;
|
|
178
|
+
name: string;
|
|
179
|
+
displayName: string;
|
|
180
|
+
description: string | null;
|
|
181
|
+
iconUrl: string | null;
|
|
182
|
+
toolsCount: number;
|
|
183
|
+
tools?: Array<{
|
|
184
|
+
id: string;
|
|
185
|
+
name: string;
|
|
186
|
+
displayName: string;
|
|
187
|
+
description: string;
|
|
188
|
+
}>;
|
|
189
|
+
status: string;
|
|
190
|
+
requiresAuth: boolean;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// List available toolkits
|
|
194
|
+
export async function listToolkits(apiKey: string, includeTools = false): Promise<AgentDojoToolkit[]> {
|
|
195
|
+
const res = await fetch(
|
|
196
|
+
`${AGENTDOJO_API_BASE}/toolkits?include_tools=${includeTools}`,
|
|
197
|
+
{
|
|
198
|
+
headers: {
|
|
199
|
+
"X-API-Key": apiKey,
|
|
200
|
+
"Content-Type": "application/json",
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
if (!res.ok) {
|
|
206
|
+
console.error("Failed to list AgentDojo toolkits:", await res.text());
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const data = await res.json();
|
|
211
|
+
const toolkits = data.toolkits || [];
|
|
212
|
+
|
|
213
|
+
return toolkits.map((t: any) => ({
|
|
214
|
+
id: t.id,
|
|
215
|
+
name: t.name,
|
|
216
|
+
displayName: t.display_name || t.name,
|
|
217
|
+
description: t.description,
|
|
218
|
+
iconUrl: t.icon_url,
|
|
219
|
+
toolsCount: t.tools_count || t.tools?.length || 0,
|
|
220
|
+
tools: t.tools?.map((tool: any) => ({
|
|
221
|
+
id: tool.id,
|
|
222
|
+
name: tool.name,
|
|
223
|
+
displayName: tool.display_name || tool.name,
|
|
224
|
+
description: tool.description,
|
|
225
|
+
})),
|
|
226
|
+
status: t.status || "active",
|
|
227
|
+
requiresAuth: t.requires_auth || false,
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// List user's MCP servers (configs)
|
|
232
|
+
export async function listServers(apiKey: string, includeTools = false): Promise<AgentDojoServer[]> {
|
|
233
|
+
const res = await fetch(
|
|
234
|
+
`${AGENTDOJO_API_BASE}/servers?include_tools=${includeTools}`,
|
|
235
|
+
{
|
|
236
|
+
headers: {
|
|
237
|
+
"X-API-Key": apiKey,
|
|
238
|
+
"Content-Type": "application/json",
|
|
239
|
+
},
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
if (!res.ok) {
|
|
244
|
+
console.error("Failed to list AgentDojo servers:", await res.text());
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const data = await res.json();
|
|
249
|
+
const servers = data.data || [];
|
|
250
|
+
|
|
251
|
+
return servers.map((s: any) => ({
|
|
252
|
+
id: s.id,
|
|
253
|
+
slug: s.slug,
|
|
254
|
+
name: s.name,
|
|
255
|
+
description: s.description,
|
|
256
|
+
url: s.url,
|
|
257
|
+
tools: s.tools || [],
|
|
258
|
+
isActive: s.is_active,
|
|
259
|
+
createdAt: s.created_at,
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Create a new MCP server with selected toolkits
|
|
264
|
+
export async function createServer(
|
|
265
|
+
apiKey: string,
|
|
266
|
+
name: string,
|
|
267
|
+
toolkits: string[],
|
|
268
|
+
description?: string
|
|
269
|
+
): Promise<AgentDojoServer | null> {
|
|
270
|
+
const res = await fetch(`${AGENTDOJO_API_BASE}/servers`, {
|
|
271
|
+
method: "POST",
|
|
272
|
+
headers: {
|
|
273
|
+
"X-API-Key": apiKey,
|
|
274
|
+
"Content-Type": "application/json",
|
|
275
|
+
},
|
|
276
|
+
body: JSON.stringify({
|
|
277
|
+
name,
|
|
278
|
+
toolkits,
|
|
279
|
+
description,
|
|
280
|
+
}),
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (!res.ok) {
|
|
284
|
+
const errText = await res.text();
|
|
285
|
+
console.error("Failed to create AgentDojo server:", errText);
|
|
286
|
+
throw new Error(`Failed to create server: ${errText}`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const data = await res.json();
|
|
290
|
+
const server = data.data || data;
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
id: server.id,
|
|
294
|
+
slug: server.slug,
|
|
295
|
+
name: server.name,
|
|
296
|
+
description: server.description,
|
|
297
|
+
url: server.url,
|
|
298
|
+
tools: server.tools || [],
|
|
299
|
+
isActive: server.is_active ?? true,
|
|
300
|
+
createdAt: server.created_at,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Get a specific server
|
|
305
|
+
export async function getServer(apiKey: string, serverId: string): Promise<AgentDojoServer | null> {
|
|
306
|
+
const res = await fetch(`${AGENTDOJO_API_BASE}/servers/${serverId}`, {
|
|
307
|
+
headers: {
|
|
308
|
+
"X-API-Key": apiKey,
|
|
309
|
+
"Content-Type": "application/json",
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
if (!res.ok) {
|
|
314
|
+
if (res.status === 404) return null;
|
|
315
|
+
console.error("Failed to get AgentDojo server:", await res.text());
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const data = await res.json();
|
|
320
|
+
const server = data.data || data;
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
id: server.id,
|
|
324
|
+
slug: server.slug,
|
|
325
|
+
name: server.name,
|
|
326
|
+
description: server.description,
|
|
327
|
+
url: server.url,
|
|
328
|
+
tools: server.tools || [],
|
|
329
|
+
isActive: server.is_active ?? true,
|
|
330
|
+
createdAt: server.created_at,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Delete a server
|
|
335
|
+
export async function deleteServer(apiKey: string, serverId: string): Promise<boolean> {
|
|
336
|
+
const res = await fetch(`${AGENTDOJO_API_BASE}/servers/${serverId}`, {
|
|
337
|
+
method: "DELETE",
|
|
338
|
+
headers: {
|
|
339
|
+
"X-API-Key": apiKey,
|
|
340
|
+
"Content-Type": "application/json",
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
return res.ok;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Get the base URL for constructing MCP server URLs
|
|
348
|
+
export function getBaseUrl(): string {
|
|
349
|
+
return AGENTDOJO_API_BASE;
|
|
350
|
+
}
|
package/src/openapi.ts
CHANGED
|
@@ -69,6 +69,7 @@ The new access token will be returned and a new refresh token cookie will be set
|
|
|
69
69
|
{ name: "MCP", description: "Model Context Protocol servers" },
|
|
70
70
|
{ name: "Providers", description: "LLM providers and API keys" },
|
|
71
71
|
{ name: "Projects", description: "Agent grouping" },
|
|
72
|
+
{ name: "Skills", description: "Agent skills management" },
|
|
72
73
|
{ name: "System", description: "Health and version info" },
|
|
73
74
|
],
|
|
74
75
|
security: [{ BearerAuth: [] }],
|
|
@@ -1297,6 +1298,169 @@ eventSource.onmessage = (event) => {
|
|
|
1297
1298
|
},
|
|
1298
1299
|
},
|
|
1299
1300
|
},
|
|
1301
|
+
// Skills endpoints
|
|
1302
|
+
"/skills": {
|
|
1303
|
+
get: {
|
|
1304
|
+
tags: ["Skills"],
|
|
1305
|
+
summary: "List all skills",
|
|
1306
|
+
description: "Returns all installed skills. Can filter by project.",
|
|
1307
|
+
parameters: [
|
|
1308
|
+
{ name: "project", in: "query", schema: { type: "string" }, description: "Filter: 'all', 'global', or project ID" },
|
|
1309
|
+
{ name: "forAgent", in: "query", schema: { type: "string" }, description: "Agent's project ID (shows global + project)" },
|
|
1310
|
+
],
|
|
1311
|
+
responses: {
|
|
1312
|
+
"200": {
|
|
1313
|
+
description: "List of skills",
|
|
1314
|
+
content: {
|
|
1315
|
+
"application/json": {
|
|
1316
|
+
schema: {
|
|
1317
|
+
type: "object",
|
|
1318
|
+
properties: {
|
|
1319
|
+
skills: { type: "array", items: { $ref: "#/components/schemas/Skill" } },
|
|
1320
|
+
},
|
|
1321
|
+
},
|
|
1322
|
+
},
|
|
1323
|
+
},
|
|
1324
|
+
},
|
|
1325
|
+
},
|
|
1326
|
+
},
|
|
1327
|
+
post: {
|
|
1328
|
+
tags: ["Skills"],
|
|
1329
|
+
summary: "Create a new skill",
|
|
1330
|
+
requestBody: {
|
|
1331
|
+
required: true,
|
|
1332
|
+
content: {
|
|
1333
|
+
"application/json": {
|
|
1334
|
+
schema: { $ref: "#/components/schemas/SkillCreate" },
|
|
1335
|
+
},
|
|
1336
|
+
},
|
|
1337
|
+
},
|
|
1338
|
+
responses: {
|
|
1339
|
+
"201": {
|
|
1340
|
+
description: "Skill created",
|
|
1341
|
+
content: {
|
|
1342
|
+
"application/json": {
|
|
1343
|
+
schema: { $ref: "#/components/schemas/Skill" },
|
|
1344
|
+
},
|
|
1345
|
+
},
|
|
1346
|
+
},
|
|
1347
|
+
},
|
|
1348
|
+
},
|
|
1349
|
+
},
|
|
1350
|
+
"/skills/github/{owner}/{repo}": {
|
|
1351
|
+
get: {
|
|
1352
|
+
tags: ["Skills"],
|
|
1353
|
+
summary: "List skills from GitHub repo",
|
|
1354
|
+
description: "Fetches and lists all skills from a GitHub repository. Skills should be in subdirectories with SKILL.md files.",
|
|
1355
|
+
parameters: [
|
|
1356
|
+
{ name: "owner", in: "path", required: true, schema: { type: "string" }, description: "GitHub repo owner" },
|
|
1357
|
+
{ name: "repo", in: "path", required: true, schema: { type: "string" }, description: "GitHub repo name" },
|
|
1358
|
+
],
|
|
1359
|
+
responses: {
|
|
1360
|
+
"200": {
|
|
1361
|
+
description: "List of skills from repo",
|
|
1362
|
+
content: {
|
|
1363
|
+
"application/json": {
|
|
1364
|
+
schema: {
|
|
1365
|
+
type: "object",
|
|
1366
|
+
properties: {
|
|
1367
|
+
skills: {
|
|
1368
|
+
type: "array",
|
|
1369
|
+
items: {
|
|
1370
|
+
type: "object",
|
|
1371
|
+
properties: {
|
|
1372
|
+
name: { type: "string" },
|
|
1373
|
+
description: { type: "string" },
|
|
1374
|
+
path: { type: "string" },
|
|
1375
|
+
size: { type: "integer" },
|
|
1376
|
+
downloadUrl: { type: "string" },
|
|
1377
|
+
},
|
|
1378
|
+
},
|
|
1379
|
+
},
|
|
1380
|
+
repo: {
|
|
1381
|
+
type: "object",
|
|
1382
|
+
properties: {
|
|
1383
|
+
owner: { type: "string" },
|
|
1384
|
+
repo: { type: "string" },
|
|
1385
|
+
url: { type: "string" },
|
|
1386
|
+
},
|
|
1387
|
+
},
|
|
1388
|
+
},
|
|
1389
|
+
},
|
|
1390
|
+
},
|
|
1391
|
+
},
|
|
1392
|
+
},
|
|
1393
|
+
"404": { description: "Repository not found" },
|
|
1394
|
+
},
|
|
1395
|
+
},
|
|
1396
|
+
},
|
|
1397
|
+
"/skills/github/install": {
|
|
1398
|
+
post: {
|
|
1399
|
+
tags: ["Skills"],
|
|
1400
|
+
summary: "Install skill from GitHub",
|
|
1401
|
+
description: "Downloads and installs a skill from a GitHub repository.",
|
|
1402
|
+
requestBody: {
|
|
1403
|
+
required: true,
|
|
1404
|
+
content: {
|
|
1405
|
+
"application/json": {
|
|
1406
|
+
schema: {
|
|
1407
|
+
type: "object",
|
|
1408
|
+
required: ["owner", "repo", "skillName", "downloadUrl"],
|
|
1409
|
+
properties: {
|
|
1410
|
+
owner: { type: "string", description: "GitHub repo owner" },
|
|
1411
|
+
repo: { type: "string", description: "GitHub repo name" },
|
|
1412
|
+
skillName: { type: "string", description: "Name of the skill to install" },
|
|
1413
|
+
downloadUrl: { type: "string", description: "Raw URL to the SKILL.md file" },
|
|
1414
|
+
projectId: { type: "string", nullable: true, description: "Project ID (null for global)" },
|
|
1415
|
+
},
|
|
1416
|
+
},
|
|
1417
|
+
},
|
|
1418
|
+
},
|
|
1419
|
+
},
|
|
1420
|
+
responses: {
|
|
1421
|
+
"201": {
|
|
1422
|
+
description: "Skill installed",
|
|
1423
|
+
content: {
|
|
1424
|
+
"application/json": {
|
|
1425
|
+
schema: { $ref: "#/components/schemas/Skill" },
|
|
1426
|
+
},
|
|
1427
|
+
},
|
|
1428
|
+
},
|
|
1429
|
+
"400": { description: "Skill already exists or invalid request" },
|
|
1430
|
+
},
|
|
1431
|
+
},
|
|
1432
|
+
},
|
|
1433
|
+
"/skills/{skillId}": {
|
|
1434
|
+
get: {
|
|
1435
|
+
tags: ["Skills"],
|
|
1436
|
+
summary: "Get skill by ID",
|
|
1437
|
+
parameters: [
|
|
1438
|
+
{ name: "skillId", in: "path", required: true, schema: { type: "string" } },
|
|
1439
|
+
],
|
|
1440
|
+
responses: {
|
|
1441
|
+
"200": {
|
|
1442
|
+
description: "Skill details",
|
|
1443
|
+
content: {
|
|
1444
|
+
"application/json": {
|
|
1445
|
+
schema: { $ref: "#/components/schemas/Skill" },
|
|
1446
|
+
},
|
|
1447
|
+
},
|
|
1448
|
+
},
|
|
1449
|
+
"404": { description: "Skill not found" },
|
|
1450
|
+
},
|
|
1451
|
+
},
|
|
1452
|
+
delete: {
|
|
1453
|
+
tags: ["Skills"],
|
|
1454
|
+
summary: "Delete skill",
|
|
1455
|
+
parameters: [
|
|
1456
|
+
{ name: "skillId", in: "path", required: true, schema: { type: "string" } },
|
|
1457
|
+
],
|
|
1458
|
+
responses: {
|
|
1459
|
+
"200": { description: "Skill deleted" },
|
|
1460
|
+
"404": { description: "Skill not found" },
|
|
1461
|
+
},
|
|
1462
|
+
},
|
|
1463
|
+
},
|
|
1300
1464
|
},
|
|
1301
1465
|
components: {
|
|
1302
1466
|
securitySchemes: {
|
|
@@ -1493,6 +1657,37 @@ eventSource.onmessage = (event) => {
|
|
|
1493
1657
|
agentName: { type: "string" },
|
|
1494
1658
|
},
|
|
1495
1659
|
},
|
|
1660
|
+
Skill: {
|
|
1661
|
+
type: "object",
|
|
1662
|
+
properties: {
|
|
1663
|
+
id: { type: "string" },
|
|
1664
|
+
name: { type: "string" },
|
|
1665
|
+
description: { type: "string" },
|
|
1666
|
+
content: { type: "string" },
|
|
1667
|
+
version: { type: "string" },
|
|
1668
|
+
license: { type: "string", nullable: true },
|
|
1669
|
+
compatibility: { type: "string", nullable: true },
|
|
1670
|
+
metadata: { type: "object" },
|
|
1671
|
+
allowed_tools: { type: "array", items: { type: "string" } },
|
|
1672
|
+
source: { type: "string", enum: ["local", "skillsmp", "github", "import"] },
|
|
1673
|
+
source_url: { type: "string", nullable: true },
|
|
1674
|
+
enabled: { type: "boolean" },
|
|
1675
|
+
project_id: { type: "string", nullable: true },
|
|
1676
|
+
created_at: { type: "string", format: "date-time" },
|
|
1677
|
+
updated_at: { type: "string", format: "date-time" },
|
|
1678
|
+
},
|
|
1679
|
+
},
|
|
1680
|
+
SkillCreate: {
|
|
1681
|
+
type: "object",
|
|
1682
|
+
required: ["name", "description", "content"],
|
|
1683
|
+
properties: {
|
|
1684
|
+
name: { type: "string", description: "Skill name (lowercase, hyphens allowed)" },
|
|
1685
|
+
description: { type: "string", description: "What this skill does" },
|
|
1686
|
+
content: { type: "string", description: "Skill instructions in markdown" },
|
|
1687
|
+
source: { type: "string", enum: ["local", "skillsmp", "github", "import"] },
|
|
1688
|
+
project_id: { type: "string", nullable: true, description: "Project ID (null for global)" },
|
|
1689
|
+
},
|
|
1690
|
+
},
|
|
1496
1691
|
McpServer: {
|
|
1497
1692
|
type: "object",
|
|
1498
1693
|
properties: {
|
package/src/providers.ts
CHANGED
|
@@ -175,7 +175,13 @@ export type ProviderId = keyof typeof PROVIDERS;
|
|
|
175
175
|
// Provider Keys Management
|
|
176
176
|
export const ProviderKeys = {
|
|
177
177
|
// Save an API key (encrypts before storing)
|
|
178
|
-
|
|
178
|
+
// projectId: null = global key, string = project-scoped key
|
|
179
|
+
async save(
|
|
180
|
+
providerId: string,
|
|
181
|
+
apiKey: string,
|
|
182
|
+
projectId: string | null = null,
|
|
183
|
+
name: string | null = null
|
|
184
|
+
): Promise<{ success: boolean; error?: string; id?: string }> {
|
|
179
185
|
// Validate format
|
|
180
186
|
const validation = validateKeyFormat(providerId, apiKey);
|
|
181
187
|
if (!validation.valid) {
|
|
@@ -185,14 +191,14 @@ export const ProviderKeys = {
|
|
|
185
191
|
try {
|
|
186
192
|
const encryptedKey = encrypt(apiKey.trim());
|
|
187
193
|
const keyHint = createKeyHint(apiKey.trim());
|
|
188
|
-
ProviderKeysDB.save(providerId, encryptedKey, keyHint);
|
|
189
|
-
return { success: true };
|
|
194
|
+
const record = ProviderKeysDB.save(providerId, encryptedKey, keyHint, projectId, name);
|
|
195
|
+
return { success: true, id: record.id };
|
|
190
196
|
} catch (err) {
|
|
191
197
|
return { success: false, error: `Failed to save key: ${err}` };
|
|
192
198
|
}
|
|
193
199
|
},
|
|
194
200
|
|
|
195
|
-
// Get decrypted API key for a provider
|
|
201
|
+
// Get decrypted API key for a provider (global key)
|
|
196
202
|
getDecrypted(providerId: string): string | null {
|
|
197
203
|
const record = ProviderKeysDB.findByProvider(providerId);
|
|
198
204
|
if (!record) return null;
|
|
@@ -205,33 +211,93 @@ export const ProviderKeys = {
|
|
|
205
211
|
}
|
|
206
212
|
},
|
|
207
213
|
|
|
208
|
-
//
|
|
214
|
+
// Get decrypted API key for a provider and project
|
|
215
|
+
// Falls back to global key if no project-specific key exists
|
|
216
|
+
getDecryptedForProject(providerId: string, projectId: string | null): string | null {
|
|
217
|
+
// Try project-specific key first
|
|
218
|
+
if (projectId) {
|
|
219
|
+
const projectRecord = ProviderKeysDB.findByProviderAndProject(providerId, projectId);
|
|
220
|
+
if (projectRecord) {
|
|
221
|
+
try {
|
|
222
|
+
return decrypt(projectRecord.encrypted_key);
|
|
223
|
+
} catch (err) {
|
|
224
|
+
console.error(`Failed to decrypt project key for ${providerId}/${projectId}:`, err);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Fall back to global key
|
|
229
|
+
return this.getDecrypted(providerId);
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
// Check if a provider has a key configured (global)
|
|
209
233
|
hasKey(providerId: string): boolean {
|
|
210
234
|
return ProviderKeysDB.findByProvider(providerId) !== null;
|
|
211
235
|
},
|
|
212
236
|
|
|
237
|
+
// Check if a provider has a key for a specific project (or global)
|
|
238
|
+
hasKeyForProject(providerId: string, projectId: string | null): boolean {
|
|
239
|
+
if (projectId && ProviderKeysDB.findByProviderAndProject(providerId, projectId)) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
return ProviderKeysDB.findByProvider(providerId) !== null;
|
|
243
|
+
},
|
|
244
|
+
|
|
213
245
|
// Get all configured providers with their status (without exposing keys)
|
|
214
246
|
getAll(): Array<{
|
|
247
|
+
id: string;
|
|
215
248
|
provider_id: string;
|
|
216
249
|
key_hint: string;
|
|
217
250
|
is_valid: boolean;
|
|
218
251
|
last_tested_at: string | null;
|
|
219
252
|
created_at: string;
|
|
253
|
+
project_id: string | null;
|
|
254
|
+
name: string | null;
|
|
220
255
|
}> {
|
|
221
256
|
return ProviderKeysDB.findAll().map(k => ({
|
|
257
|
+
id: k.id,
|
|
222
258
|
provider_id: k.provider_id,
|
|
223
259
|
key_hint: k.key_hint,
|
|
224
260
|
is_valid: k.is_valid,
|
|
225
261
|
last_tested_at: k.last_tested_at,
|
|
226
262
|
created_at: k.created_at,
|
|
263
|
+
project_id: k.project_id,
|
|
264
|
+
name: k.name,
|
|
227
265
|
}));
|
|
228
266
|
},
|
|
229
267
|
|
|
230
|
-
//
|
|
268
|
+
// Get all keys for a specific provider
|
|
269
|
+
getAllByProvider(providerId: string): Array<{
|
|
270
|
+
id: string;
|
|
271
|
+
provider_id: string;
|
|
272
|
+
key_hint: string;
|
|
273
|
+
is_valid: boolean;
|
|
274
|
+
last_tested_at: string | null;
|
|
275
|
+
created_at: string;
|
|
276
|
+
project_id: string | null;
|
|
277
|
+
name: string | null;
|
|
278
|
+
}> {
|
|
279
|
+
return ProviderKeysDB.findAllByProvider(providerId).map(k => ({
|
|
280
|
+
id: k.id,
|
|
281
|
+
provider_id: k.provider_id,
|
|
282
|
+
key_hint: k.key_hint,
|
|
283
|
+
is_valid: k.is_valid,
|
|
284
|
+
last_tested_at: k.last_tested_at,
|
|
285
|
+
created_at: k.created_at,
|
|
286
|
+
project_id: k.project_id,
|
|
287
|
+
name: k.name,
|
|
288
|
+
}));
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
// Delete a provider key (global)
|
|
231
292
|
delete(providerId: string): boolean {
|
|
232
293
|
return ProviderKeysDB.delete(providerId);
|
|
233
294
|
},
|
|
234
295
|
|
|
296
|
+
// Delete a provider key by ID
|
|
297
|
+
deleteById(id: string): boolean {
|
|
298
|
+
return ProviderKeysDB.deleteById(id);
|
|
299
|
+
},
|
|
300
|
+
|
|
235
301
|
// Test if an API key is valid by making a test request
|
|
236
302
|
// TODO: Implement actual API testing per provider (Anthropic needs POST, others GET)
|
|
237
303
|
async test(providerId: string, apiKey?: string): Promise<{ valid: boolean; error?: string }> {
|
|
@@ -254,10 +320,15 @@ export const ProviderKeys = {
|
|
|
254
320
|
return { valid: true };
|
|
255
321
|
},
|
|
256
322
|
|
|
257
|
-
// Get list of provider IDs that have valid keys
|
|
323
|
+
// Get list of provider IDs that have valid keys (global only)
|
|
258
324
|
getConfiguredProviders(): string[] {
|
|
259
325
|
return ProviderKeysDB.getConfiguredProviders();
|
|
260
326
|
},
|
|
327
|
+
|
|
328
|
+
// Get list of all provider IDs that have keys (including project-scoped)
|
|
329
|
+
getAllConfiguredProviders(): string[] {
|
|
330
|
+
return ProviderKeysDB.getAllConfiguredProviders();
|
|
331
|
+
},
|
|
261
332
|
};
|
|
262
333
|
|
|
263
334
|
// Onboarding status management
|