apteva 0.2.11 → 0.3.6
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.mvbdnw89.js +227 -0
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/auth/index.ts +11 -3
- package/src/auth/middleware.ts +1 -0
- package/src/crypto.ts +4 -0
- package/src/db.ts +437 -14
- package/src/integrations/skillsmp.ts +318 -0
- package/src/providers.ts +21 -0
- package/src/routes/api.ts +836 -16
- package/src/server.ts +58 -7
- package/src/web/App.tsx +24 -8
- package/src/web/components/agents/AgentCard.tsx +36 -11
- package/src/web/components/agents/AgentPanel.tsx +333 -24
- package/src/web/components/agents/AgentsView.tsx +1 -1
- package/src/web/components/agents/CreateAgentModal.tsx +169 -23
- package/src/web/components/common/Icons.tsx +8 -0
- package/src/web/components/common/index.ts +1 -0
- package/src/web/components/index.ts +1 -0
- package/src/web/components/layout/Header.tsx +4 -2
- package/src/web/components/layout/Sidebar.tsx +7 -1
- package/src/web/components/mcp/McpPage.tsx +602 -19
- package/src/web/components/meta-agent/MetaAgent.tsx +222 -0
- package/src/web/components/settings/SettingsPage.tsx +212 -150
- package/src/web/components/skills/SkillsPage.tsx +871 -0
- package/src/web/context/AuthContext.tsx +5 -0
- package/src/web/context/ProjectContext.tsx +26 -4
- package/src/web/types.ts +48 -3
- package/dist/App.44ge5b89.js +0 -218
package/src/db.ts
CHANGED
|
@@ -5,6 +5,19 @@ import { encrypt, decrypt, encryptObject, decryptObject } from "./crypto";
|
|
|
5
5
|
import { randomBytes } from "crypto";
|
|
6
6
|
|
|
7
7
|
// Types
|
|
8
|
+
export type AgentMode = "coordinator" | "worker";
|
|
9
|
+
|
|
10
|
+
export interface MultiAgentConfig {
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
mode?: AgentMode;
|
|
13
|
+
group?: string; // Defaults to projectId if not specified
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AgentBuiltinTools {
|
|
17
|
+
webSearch: boolean;
|
|
18
|
+
webFetch: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
8
21
|
export interface AgentFeatures {
|
|
9
22
|
memory: boolean;
|
|
10
23
|
tasks: boolean;
|
|
@@ -13,7 +26,8 @@ export interface AgentFeatures {
|
|
|
13
26
|
mcp: boolean;
|
|
14
27
|
realtime: boolean;
|
|
15
28
|
files: boolean;
|
|
16
|
-
agents: boolean;
|
|
29
|
+
agents: boolean | MultiAgentConfig; // Can be boolean for backwards compat or full config
|
|
30
|
+
builtinTools?: AgentBuiltinTools;
|
|
17
31
|
}
|
|
18
32
|
|
|
19
33
|
export const DEFAULT_FEATURES: AgentFeatures = {
|
|
@@ -25,8 +39,25 @@ export const DEFAULT_FEATURES: AgentFeatures = {
|
|
|
25
39
|
realtime: false,
|
|
26
40
|
files: false,
|
|
27
41
|
agents: false,
|
|
42
|
+
builtinTools: { webSearch: false, webFetch: false },
|
|
28
43
|
};
|
|
29
44
|
|
|
45
|
+
// Helper to normalize agents feature to MultiAgentConfig
|
|
46
|
+
export function getMultiAgentConfig(features: AgentFeatures, projectId?: string | null): MultiAgentConfig {
|
|
47
|
+
const agents = features.agents;
|
|
48
|
+
if (typeof agents === "boolean") {
|
|
49
|
+
return {
|
|
50
|
+
enabled: agents,
|
|
51
|
+
mode: "worker",
|
|
52
|
+
group: projectId || undefined,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
...agents,
|
|
57
|
+
group: agents.group || projectId || undefined,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
30
61
|
export interface Agent {
|
|
31
62
|
id: string;
|
|
32
63
|
name: string;
|
|
@@ -37,6 +68,7 @@ export interface Agent {
|
|
|
37
68
|
port: number | null;
|
|
38
69
|
features: AgentFeatures;
|
|
39
70
|
mcp_servers: string[]; // Array of MCP server IDs
|
|
71
|
+
skills: string[]; // Array of Skill IDs
|
|
40
72
|
project_id: string | null; // Optional project grouping
|
|
41
73
|
api_key_encrypted: string | null; // Encrypted API key for agent authentication
|
|
42
74
|
created_at: string;
|
|
@@ -71,6 +103,7 @@ export interface AgentRow {
|
|
|
71
103
|
port: number | null;
|
|
72
104
|
features: string | null;
|
|
73
105
|
mcp_servers: string | null;
|
|
106
|
+
skills: string | null;
|
|
74
107
|
project_id: string | null;
|
|
75
108
|
api_key_encrypted: string | null;
|
|
76
109
|
created_at: string;
|
|
@@ -105,8 +138,9 @@ export interface ProviderKeyRow {
|
|
|
105
138
|
export interface McpServer {
|
|
106
139
|
id: string;
|
|
107
140
|
name: string;
|
|
108
|
-
type: "npm" | "github" | "http" | "custom";
|
|
109
|
-
package: string | null;
|
|
141
|
+
type: "npm" | "pip" | "github" | "http" | "custom";
|
|
142
|
+
package: string | null; // npm or pip package name
|
|
143
|
+
pip_module: string | null; // For pip type: the module to run (e.g., "late.mcp")
|
|
110
144
|
command: string | null;
|
|
111
145
|
args: string | null;
|
|
112
146
|
env: Record<string, string>;
|
|
@@ -115,14 +149,53 @@ export interface McpServer {
|
|
|
115
149
|
port: number | null;
|
|
116
150
|
status: "stopped" | "running";
|
|
117
151
|
source: string | null; // e.g., "composio", "smithery", null for local
|
|
152
|
+
project_id: string | null; // null = global, otherwise project-scoped
|
|
118
153
|
created_at: string;
|
|
119
154
|
}
|
|
120
155
|
|
|
156
|
+
// Skill types
|
|
157
|
+
export interface Skill {
|
|
158
|
+
id: string;
|
|
159
|
+
name: string;
|
|
160
|
+
description: string;
|
|
161
|
+
content: string; // Full SKILL.md body (markdown)
|
|
162
|
+
version: string; // Semantic version (e.g., "1.0.0")
|
|
163
|
+
license: string | null;
|
|
164
|
+
compatibility: string | null;
|
|
165
|
+
metadata: Record<string, string>;
|
|
166
|
+
allowed_tools: string[];
|
|
167
|
+
source: "local" | "skillsmp" | "github" | "import";
|
|
168
|
+
source_url: string | null;
|
|
169
|
+
enabled: boolean;
|
|
170
|
+
project_id: string | null; // null = global, otherwise project-scoped
|
|
171
|
+
created_at: string;
|
|
172
|
+
updated_at: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface SkillRow {
|
|
176
|
+
id: string;
|
|
177
|
+
name: string;
|
|
178
|
+
description: string;
|
|
179
|
+
content: string;
|
|
180
|
+
version: string;
|
|
181
|
+
license: string | null;
|
|
182
|
+
compatibility: string | null;
|
|
183
|
+
metadata: string | null;
|
|
184
|
+
allowed_tools: string | null;
|
|
185
|
+
source: string;
|
|
186
|
+
source_url: string | null;
|
|
187
|
+
enabled: number;
|
|
188
|
+
project_id: string | null;
|
|
189
|
+
created_at: string;
|
|
190
|
+
updated_at: string;
|
|
191
|
+
}
|
|
192
|
+
|
|
121
193
|
export interface McpServerRow {
|
|
122
194
|
id: string;
|
|
123
195
|
name: string;
|
|
124
196
|
type: string;
|
|
125
197
|
package: string | null;
|
|
198
|
+
pip_module: string | null;
|
|
126
199
|
command: string | null;
|
|
127
200
|
args: string | null;
|
|
128
201
|
env: string | null;
|
|
@@ -131,6 +204,7 @@ export interface McpServerRow {
|
|
|
131
204
|
port: number | null;
|
|
132
205
|
status: string;
|
|
133
206
|
source: string | null;
|
|
207
|
+
project_id: string | null;
|
|
134
208
|
created_at: string;
|
|
135
209
|
}
|
|
136
210
|
|
|
@@ -371,6 +445,61 @@ function runMigrations() {
|
|
|
371
445
|
ALTER TABLE agents ADD COLUMN api_key_encrypted TEXT;
|
|
372
446
|
`,
|
|
373
447
|
},
|
|
448
|
+
{
|
|
449
|
+
name: "016_create_skills",
|
|
450
|
+
sql: `
|
|
451
|
+
CREATE TABLE IF NOT EXISTS skills (
|
|
452
|
+
id TEXT PRIMARY KEY,
|
|
453
|
+
name TEXT NOT NULL,
|
|
454
|
+
description TEXT NOT NULL,
|
|
455
|
+
content TEXT NOT NULL,
|
|
456
|
+
license TEXT,
|
|
457
|
+
compatibility TEXT,
|
|
458
|
+
metadata TEXT DEFAULT '{}',
|
|
459
|
+
allowed_tools TEXT DEFAULT '[]',
|
|
460
|
+
source TEXT NOT NULL DEFAULT 'local',
|
|
461
|
+
source_url TEXT,
|
|
462
|
+
enabled INTEGER DEFAULT 1,
|
|
463
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
464
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
465
|
+
);
|
|
466
|
+
CREATE INDEX IF NOT EXISTS idx_skills_name ON skills(name);
|
|
467
|
+
CREATE INDEX IF NOT EXISTS idx_skills_source ON skills(source);
|
|
468
|
+
CREATE INDEX IF NOT EXISTS idx_skills_enabled ON skills(enabled);
|
|
469
|
+
`,
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
name: "017_add_skills_to_agents",
|
|
473
|
+
sql: `
|
|
474
|
+
ALTER TABLE agents ADD COLUMN skills TEXT DEFAULT '[]';
|
|
475
|
+
`,
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
name: "018_add_skill_version",
|
|
479
|
+
sql: `
|
|
480
|
+
ALTER TABLE skills ADD COLUMN version TEXT DEFAULT '1.0.0';
|
|
481
|
+
`,
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
name: "019_add_mcp_server_project_id",
|
|
485
|
+
sql: `
|
|
486
|
+
ALTER TABLE mcp_servers ADD COLUMN project_id TEXT REFERENCES projects(id) ON DELETE SET NULL;
|
|
487
|
+
CREATE INDEX IF NOT EXISTS idx_mcp_servers_project ON mcp_servers(project_id);
|
|
488
|
+
`,
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
name: "020_add_skill_project_id",
|
|
492
|
+
sql: `
|
|
493
|
+
ALTER TABLE skills ADD COLUMN project_id TEXT REFERENCES projects(id) ON DELETE SET NULL;
|
|
494
|
+
CREATE INDEX IF NOT EXISTS idx_skills_project ON skills(project_id);
|
|
495
|
+
`,
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
name: "021_add_mcp_server_pip_module",
|
|
499
|
+
sql: `
|
|
500
|
+
ALTER TABLE mcp_servers ADD COLUMN pip_module TEXT;
|
|
501
|
+
`,
|
|
502
|
+
},
|
|
374
503
|
];
|
|
375
504
|
|
|
376
505
|
// Check which migrations have been applied
|
|
@@ -383,9 +512,20 @@ function runMigrations() {
|
|
|
383
512
|
// Run pending migrations
|
|
384
513
|
for (const migration of migrations) {
|
|
385
514
|
if (!applied.has(migration.name)) {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
515
|
+
try {
|
|
516
|
+
// Migration runs silently
|
|
517
|
+
db.run(migration.sql);
|
|
518
|
+
db.run("INSERT INTO migrations (name) VALUES (?)", [migration.name]);
|
|
519
|
+
} catch (err) {
|
|
520
|
+
// Log error but continue - some migrations may fail if partially applied
|
|
521
|
+
console.error(`[db] Migration ${migration.name} failed:`, err);
|
|
522
|
+
// Still mark as applied to avoid retrying broken migrations
|
|
523
|
+
try {
|
|
524
|
+
db.run("INSERT INTO migrations (name) VALUES (?)", [migration.name]);
|
|
525
|
+
} catch {
|
|
526
|
+
// Ignore if already marked
|
|
527
|
+
}
|
|
528
|
+
}
|
|
389
529
|
}
|
|
390
530
|
}
|
|
391
531
|
|
|
@@ -464,16 +604,17 @@ export const AgentDB = {
|
|
|
464
604
|
const now = new Date().toISOString();
|
|
465
605
|
const featuresJson = JSON.stringify(agent.features || DEFAULT_FEATURES);
|
|
466
606
|
const mcpServersJson = JSON.stringify(agent.mcp_servers || []);
|
|
607
|
+
const skillsJson = JSON.stringify(agent.skills || []);
|
|
467
608
|
// Assign port permanently at creation time
|
|
468
609
|
const port = agent.port ?? this.getNextAvailablePort();
|
|
469
610
|
// Generate and encrypt API key
|
|
470
611
|
const apiKey = generateAgentApiKey(agent.id);
|
|
471
612
|
const apiKeyEncrypted = encrypt(apiKey);
|
|
472
613
|
const stmt = db.prepare(`
|
|
473
|
-
INSERT INTO agents (id, name, model, provider, system_prompt, features, mcp_servers, project_id, status, port, api_key_encrypted, created_at, updated_at)
|
|
474
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'stopped', ?, ?, ?, ?)
|
|
614
|
+
INSERT INTO agents (id, name, model, provider, system_prompt, features, mcp_servers, skills, project_id, status, port, api_key_encrypted, created_at, updated_at)
|
|
615
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'stopped', ?, ?, ?, ?)
|
|
475
616
|
`);
|
|
476
|
-
stmt.run(agent.id, agent.name, agent.model, agent.provider, agent.system_prompt, featuresJson, mcpServersJson, agent.project_id || null, port, apiKeyEncrypted, now, now);
|
|
617
|
+
stmt.run(agent.id, agent.name, agent.model, agent.provider, agent.system_prompt, featuresJson, mcpServersJson, skillsJson, agent.project_id || null, port, apiKeyEncrypted, now, now);
|
|
477
618
|
return this.findById(agent.id)!;
|
|
478
619
|
},
|
|
479
620
|
|
|
@@ -535,6 +676,10 @@ export const AgentDB = {
|
|
|
535
676
|
fields.push("mcp_servers = ?");
|
|
536
677
|
values.push(JSON.stringify(updates.mcp_servers));
|
|
537
678
|
}
|
|
679
|
+
if (updates.skills !== undefined) {
|
|
680
|
+
fields.push("skills = ?");
|
|
681
|
+
values.push(JSON.stringify(updates.skills));
|
|
682
|
+
}
|
|
538
683
|
if (updates.project_id !== undefined) {
|
|
539
684
|
fields.push("project_id = ?");
|
|
540
685
|
values.push(updates.project_id);
|
|
@@ -561,6 +706,15 @@ export const AgentDB = {
|
|
|
561
706
|
return rows.map(rowToAgent);
|
|
562
707
|
},
|
|
563
708
|
|
|
709
|
+
// Find agents that have a specific skill
|
|
710
|
+
findBySkill(skillId: string): Agent[] {
|
|
711
|
+
// SQLite JSON query: check if skills array contains the skillId
|
|
712
|
+
const rows = db.query(
|
|
713
|
+
`SELECT * FROM agents WHERE skills LIKE ? ORDER BY created_at DESC`
|
|
714
|
+
).all(`%"${skillId}"%`) as AgentRow[];
|
|
715
|
+
return rows.map(rowToAgent);
|
|
716
|
+
},
|
|
717
|
+
|
|
564
718
|
// Delete agent
|
|
565
719
|
delete(id: string): boolean {
|
|
566
720
|
const result = db.run("DELETE FROM agents WHERE id = ?", [id]);
|
|
@@ -711,11 +865,12 @@ export const ProjectDB = {
|
|
|
711
865
|
return row.count;
|
|
712
866
|
},
|
|
713
867
|
|
|
714
|
-
// Get agent count per project
|
|
868
|
+
// Get agent count per project (excludes meta agent)
|
|
715
869
|
getAgentCounts(): Map<string | null, number> {
|
|
716
870
|
const rows = db.query(`
|
|
717
871
|
SELECT project_id, COUNT(*) as count
|
|
718
872
|
FROM agents
|
|
873
|
+
WHERE id != 'apteva-assistant'
|
|
719
874
|
GROUP BY project_id
|
|
720
875
|
`).all() as { project_id: string | null; count: number }[];
|
|
721
876
|
|
|
@@ -817,6 +972,14 @@ function rowToAgent(row: AgentRow): Agent {
|
|
|
817
972
|
// Use empty array if parsing fails
|
|
818
973
|
}
|
|
819
974
|
}
|
|
975
|
+
let skills: string[] = [];
|
|
976
|
+
if (row.skills) {
|
|
977
|
+
try {
|
|
978
|
+
skills = JSON.parse(row.skills);
|
|
979
|
+
} catch {
|
|
980
|
+
// Use empty array if parsing fails
|
|
981
|
+
}
|
|
982
|
+
}
|
|
820
983
|
return {
|
|
821
984
|
id: row.id,
|
|
822
985
|
name: row.name,
|
|
@@ -827,6 +990,7 @@ function rowToAgent(row: AgentRow): Agent {
|
|
|
827
990
|
port: row.port,
|
|
828
991
|
features,
|
|
829
992
|
mcp_servers,
|
|
993
|
+
skills,
|
|
830
994
|
project_id: row.project_id,
|
|
831
995
|
api_key_encrypted: row.api_key_encrypted,
|
|
832
996
|
created_at: row.created_at,
|
|
@@ -923,12 +1087,12 @@ export const McpServerDB = {
|
|
|
923
1087
|
const envEncrypted = encryptObject(server.env || {});
|
|
924
1088
|
const headersEncrypted = encryptObject(server.headers || {});
|
|
925
1089
|
const stmt = db.prepare(`
|
|
926
|
-
INSERT INTO mcp_servers (id, name, type, package, command, args, env, url, headers, source, status, port, created_at)
|
|
927
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'stopped', NULL, ?)
|
|
1090
|
+
INSERT INTO mcp_servers (id, name, type, package, pip_module, command, args, env, url, headers, source, project_id, status, port, created_at)
|
|
1091
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'stopped', NULL, ?)
|
|
928
1092
|
`);
|
|
929
1093
|
stmt.run(
|
|
930
|
-
server.id, server.name, server.type, server.package, server.command, server.args,
|
|
931
|
-
envEncrypted, server.url || null, headersEncrypted, server.source || null, now
|
|
1094
|
+
server.id, server.name, server.type, server.package, server.pip_module || null, server.command, server.args,
|
|
1095
|
+
envEncrypted, server.url || null, headersEncrypted, server.source || null, server.project_id || null, now
|
|
932
1096
|
);
|
|
933
1097
|
return this.findById(server.id)!;
|
|
934
1098
|
},
|
|
@@ -967,6 +1131,10 @@ export const McpServerDB = {
|
|
|
967
1131
|
fields.push("package = ?");
|
|
968
1132
|
values.push(updates.package);
|
|
969
1133
|
}
|
|
1134
|
+
if (updates.pip_module !== undefined) {
|
|
1135
|
+
fields.push("pip_module = ?");
|
|
1136
|
+
values.push(updates.pip_module);
|
|
1137
|
+
}
|
|
970
1138
|
if (updates.command !== undefined) {
|
|
971
1139
|
fields.push("command = ?");
|
|
972
1140
|
values.push(updates.command);
|
|
@@ -993,6 +1161,10 @@ export const McpServerDB = {
|
|
|
993
1161
|
fields.push("source = ?");
|
|
994
1162
|
values.push(updates.source);
|
|
995
1163
|
}
|
|
1164
|
+
if (updates.project_id !== undefined) {
|
|
1165
|
+
fields.push("project_id = ?");
|
|
1166
|
+
values.push(updates.project_id);
|
|
1167
|
+
}
|
|
996
1168
|
if (updates.port !== undefined) {
|
|
997
1169
|
fields.push("port = ?");
|
|
998
1170
|
values.push(updates.port);
|
|
@@ -1027,6 +1199,34 @@ export const McpServerDB = {
|
|
|
1027
1199
|
const row = db.query("SELECT COUNT(*) as count FROM mcp_servers").get() as { count: number };
|
|
1028
1200
|
return row.count;
|
|
1029
1201
|
},
|
|
1202
|
+
|
|
1203
|
+
// Find servers by project (null = global only)
|
|
1204
|
+
findByProject(projectId: string | null): McpServer[] {
|
|
1205
|
+
if (projectId === null) {
|
|
1206
|
+
const rows = db.query("SELECT * FROM mcp_servers WHERE project_id IS NULL ORDER BY created_at DESC").all() as McpServerRow[];
|
|
1207
|
+
return rows.map(rowToMcpServer);
|
|
1208
|
+
}
|
|
1209
|
+
const rows = db.query("SELECT * FROM mcp_servers WHERE project_id = ? ORDER BY created_at DESC").all(projectId) as McpServerRow[];
|
|
1210
|
+
return rows.map(rowToMcpServer);
|
|
1211
|
+
},
|
|
1212
|
+
|
|
1213
|
+
// Find servers available for an agent (global + agent's project)
|
|
1214
|
+
findForAgent(agentProjectId: string | null): McpServer[] {
|
|
1215
|
+
if (agentProjectId === null) {
|
|
1216
|
+
// Agent has no project, only show global servers
|
|
1217
|
+
const rows = db.query("SELECT * FROM mcp_servers WHERE project_id IS NULL ORDER BY created_at DESC").all() as McpServerRow[];
|
|
1218
|
+
return rows.map(rowToMcpServer);
|
|
1219
|
+
}
|
|
1220
|
+
// Agent has a project, show global + project servers
|
|
1221
|
+
const rows = db.query("SELECT * FROM mcp_servers WHERE project_id IS NULL OR project_id = ? ORDER BY created_at DESC").all(agentProjectId) as McpServerRow[];
|
|
1222
|
+
return rows.map(rowToMcpServer);
|
|
1223
|
+
},
|
|
1224
|
+
|
|
1225
|
+
// Find global servers only
|
|
1226
|
+
findGlobal(): McpServer[] {
|
|
1227
|
+
const rows = db.query("SELECT * FROM mcp_servers WHERE project_id IS NULL ORDER BY created_at DESC").all() as McpServerRow[];
|
|
1228
|
+
return rows.map(rowToMcpServer);
|
|
1229
|
+
},
|
|
1030
1230
|
};
|
|
1031
1231
|
|
|
1032
1232
|
// Helper to convert DB row to McpServer type
|
|
@@ -1039,6 +1239,7 @@ function rowToMcpServer(row: McpServerRow): McpServer {
|
|
|
1039
1239
|
name: row.name,
|
|
1040
1240
|
type: row.type as McpServer["type"],
|
|
1041
1241
|
package: row.package,
|
|
1242
|
+
pip_module: row.pip_module,
|
|
1042
1243
|
command: row.command,
|
|
1043
1244
|
args: row.args,
|
|
1044
1245
|
env,
|
|
@@ -1047,6 +1248,7 @@ function rowToMcpServer(row: McpServerRow): McpServer {
|
|
|
1047
1248
|
port: row.port,
|
|
1048
1249
|
status: row.status as "stopped" | "running",
|
|
1049
1250
|
source: row.source,
|
|
1251
|
+
project_id: row.project_id,
|
|
1050
1252
|
created_at: row.created_at,
|
|
1051
1253
|
};
|
|
1052
1254
|
}
|
|
@@ -1621,6 +1823,227 @@ function rowToSession(row: SessionRow): Session {
|
|
|
1621
1823
|
};
|
|
1622
1824
|
}
|
|
1623
1825
|
|
|
1826
|
+
// Skill operations
|
|
1827
|
+
export const SkillDB = {
|
|
1828
|
+
// Create a new skill
|
|
1829
|
+
create(skill: Omit<Skill, "id" | "created_at" | "updated_at">): Skill {
|
|
1830
|
+
const id = generateId();
|
|
1831
|
+
const now = new Date().toISOString();
|
|
1832
|
+
const metadataJson = JSON.stringify(skill.metadata || {});
|
|
1833
|
+
const allowedToolsJson = JSON.stringify(skill.allowed_tools || []);
|
|
1834
|
+
|
|
1835
|
+
db.run(
|
|
1836
|
+
`INSERT INTO skills (id, name, description, content, version, license, compatibility, metadata, allowed_tools, source, source_url, enabled, project_id, created_at, updated_at)
|
|
1837
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1838
|
+
[
|
|
1839
|
+
id,
|
|
1840
|
+
skill.name,
|
|
1841
|
+
skill.description,
|
|
1842
|
+
skill.content,
|
|
1843
|
+
skill.version || "1.0.0",
|
|
1844
|
+
skill.license || null,
|
|
1845
|
+
skill.compatibility || null,
|
|
1846
|
+
metadataJson,
|
|
1847
|
+
allowedToolsJson,
|
|
1848
|
+
skill.source,
|
|
1849
|
+
skill.source_url || null,
|
|
1850
|
+
skill.enabled ? 1 : 0,
|
|
1851
|
+
skill.project_id || null,
|
|
1852
|
+
now,
|
|
1853
|
+
now,
|
|
1854
|
+
]
|
|
1855
|
+
);
|
|
1856
|
+
|
|
1857
|
+
return this.findById(id)!;
|
|
1858
|
+
},
|
|
1859
|
+
|
|
1860
|
+
// Find skill by ID
|
|
1861
|
+
findById(id: string): Skill | null {
|
|
1862
|
+
const row = db.query("SELECT * FROM skills WHERE id = ?").get(id) as SkillRow | null;
|
|
1863
|
+
return row ? rowToSkill(row) : null;
|
|
1864
|
+
},
|
|
1865
|
+
|
|
1866
|
+
// Find skill by name
|
|
1867
|
+
findByName(name: string): Skill | null {
|
|
1868
|
+
const row = db.query("SELECT * FROM skills WHERE name = ?").get(name) as SkillRow | null;
|
|
1869
|
+
return row ? rowToSkill(row) : null;
|
|
1870
|
+
},
|
|
1871
|
+
|
|
1872
|
+
// Get all skills
|
|
1873
|
+
findAll(): Skill[] {
|
|
1874
|
+
const rows = db.query("SELECT * FROM skills ORDER BY name ASC").all() as SkillRow[];
|
|
1875
|
+
return rows.map(rowToSkill);
|
|
1876
|
+
},
|
|
1877
|
+
|
|
1878
|
+
// Get enabled skills
|
|
1879
|
+
findEnabled(): Skill[] {
|
|
1880
|
+
const rows = db.query("SELECT * FROM skills WHERE enabled = 1 ORDER BY name ASC").all() as SkillRow[];
|
|
1881
|
+
return rows.map(rowToSkill);
|
|
1882
|
+
},
|
|
1883
|
+
|
|
1884
|
+
// Update skill
|
|
1885
|
+
update(id: string, updates: Partial<Omit<Skill, "id" | "created_at">>): Skill | null {
|
|
1886
|
+
const skill = this.findById(id);
|
|
1887
|
+
if (!skill) return null;
|
|
1888
|
+
|
|
1889
|
+
const fields: string[] = [];
|
|
1890
|
+
const values: unknown[] = [];
|
|
1891
|
+
|
|
1892
|
+
if (updates.name !== undefined) {
|
|
1893
|
+
fields.push("name = ?");
|
|
1894
|
+
values.push(updates.name);
|
|
1895
|
+
}
|
|
1896
|
+
if (updates.description !== undefined) {
|
|
1897
|
+
fields.push("description = ?");
|
|
1898
|
+
values.push(updates.description);
|
|
1899
|
+
}
|
|
1900
|
+
if (updates.content !== undefined) {
|
|
1901
|
+
fields.push("content = ?");
|
|
1902
|
+
values.push(updates.content);
|
|
1903
|
+
}
|
|
1904
|
+
if (updates.version !== undefined) {
|
|
1905
|
+
fields.push("version = ?");
|
|
1906
|
+
values.push(updates.version);
|
|
1907
|
+
}
|
|
1908
|
+
if (updates.license !== undefined) {
|
|
1909
|
+
fields.push("license = ?");
|
|
1910
|
+
values.push(updates.license);
|
|
1911
|
+
}
|
|
1912
|
+
if (updates.compatibility !== undefined) {
|
|
1913
|
+
fields.push("compatibility = ?");
|
|
1914
|
+
values.push(updates.compatibility);
|
|
1915
|
+
}
|
|
1916
|
+
if (updates.metadata !== undefined) {
|
|
1917
|
+
fields.push("metadata = ?");
|
|
1918
|
+
values.push(JSON.stringify(updates.metadata));
|
|
1919
|
+
}
|
|
1920
|
+
if (updates.allowed_tools !== undefined) {
|
|
1921
|
+
fields.push("allowed_tools = ?");
|
|
1922
|
+
values.push(JSON.stringify(updates.allowed_tools));
|
|
1923
|
+
}
|
|
1924
|
+
if (updates.source !== undefined) {
|
|
1925
|
+
fields.push("source = ?");
|
|
1926
|
+
values.push(updates.source);
|
|
1927
|
+
}
|
|
1928
|
+
if (updates.source_url !== undefined) {
|
|
1929
|
+
fields.push("source_url = ?");
|
|
1930
|
+
values.push(updates.source_url);
|
|
1931
|
+
}
|
|
1932
|
+
if (updates.enabled !== undefined) {
|
|
1933
|
+
fields.push("enabled = ?");
|
|
1934
|
+
values.push(updates.enabled ? 1 : 0);
|
|
1935
|
+
}
|
|
1936
|
+
if (updates.project_id !== undefined) {
|
|
1937
|
+
fields.push("project_id = ?");
|
|
1938
|
+
values.push(updates.project_id);
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
if (fields.length > 0) {
|
|
1942
|
+
fields.push("updated_at = ?");
|
|
1943
|
+
values.push(new Date().toISOString());
|
|
1944
|
+
values.push(id);
|
|
1945
|
+
|
|
1946
|
+
db.run(`UPDATE skills SET ${fields.join(", ")} WHERE id = ?`, values);
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
return this.findById(id);
|
|
1950
|
+
},
|
|
1951
|
+
|
|
1952
|
+
// Toggle skill enabled/disabled
|
|
1953
|
+
setEnabled(id: string, enabled: boolean): Skill | null {
|
|
1954
|
+
return this.update(id, { enabled });
|
|
1955
|
+
},
|
|
1956
|
+
|
|
1957
|
+
// Delete skill
|
|
1958
|
+
delete(id: string): boolean {
|
|
1959
|
+
const result = db.run("DELETE FROM skills WHERE id = ?", [id]);
|
|
1960
|
+
return result.changes > 0;
|
|
1961
|
+
},
|
|
1962
|
+
|
|
1963
|
+
// Count skills
|
|
1964
|
+
count(): number {
|
|
1965
|
+
const row = db.query("SELECT COUNT(*) as count FROM skills").get() as { count: number };
|
|
1966
|
+
return row.count;
|
|
1967
|
+
},
|
|
1968
|
+
|
|
1969
|
+
// Count enabled skills
|
|
1970
|
+
countEnabled(): number {
|
|
1971
|
+
const row = db.query("SELECT COUNT(*) as count FROM skills WHERE enabled = 1").get() as { count: number };
|
|
1972
|
+
return row.count;
|
|
1973
|
+
},
|
|
1974
|
+
|
|
1975
|
+
// Check if skill with name exists
|
|
1976
|
+
exists(name: string): boolean {
|
|
1977
|
+
const row = db.query("SELECT COUNT(*) as count FROM skills WHERE name = ?").get(name) as { count: number };
|
|
1978
|
+
return row.count > 0;
|
|
1979
|
+
},
|
|
1980
|
+
|
|
1981
|
+
// Find skills by project (null = global only)
|
|
1982
|
+
findByProject(projectId: string | null): Skill[] {
|
|
1983
|
+
if (projectId === null) {
|
|
1984
|
+
const rows = db.query("SELECT * FROM skills WHERE project_id IS NULL ORDER BY name ASC").all() as SkillRow[];
|
|
1985
|
+
return rows.map(rowToSkill);
|
|
1986
|
+
}
|
|
1987
|
+
const rows = db.query("SELECT * FROM skills WHERE project_id = ? ORDER BY name ASC").all(projectId) as SkillRow[];
|
|
1988
|
+
return rows.map(rowToSkill);
|
|
1989
|
+
},
|
|
1990
|
+
|
|
1991
|
+
// Find skills available for an agent (global + agent's project)
|
|
1992
|
+
findForAgent(agentProjectId: string | null): Skill[] {
|
|
1993
|
+
if (agentProjectId === null) {
|
|
1994
|
+
// Agent has no project, only show global skills
|
|
1995
|
+
const rows = db.query("SELECT * FROM skills WHERE project_id IS NULL ORDER BY name ASC").all() as SkillRow[];
|
|
1996
|
+
return rows.map(rowToSkill);
|
|
1997
|
+
}
|
|
1998
|
+
// Agent has a project, show global + project skills
|
|
1999
|
+
const rows = db.query("SELECT * FROM skills WHERE project_id IS NULL OR project_id = ? ORDER BY name ASC").all(agentProjectId) as SkillRow[];
|
|
2000
|
+
return rows.map(rowToSkill);
|
|
2001
|
+
},
|
|
2002
|
+
|
|
2003
|
+
// Find global skills only
|
|
2004
|
+
findGlobal(): Skill[] {
|
|
2005
|
+
const rows = db.query("SELECT * FROM skills WHERE project_id IS NULL ORDER BY name ASC").all() as SkillRow[];
|
|
2006
|
+
return rows.map(rowToSkill);
|
|
2007
|
+
},
|
|
2008
|
+
};
|
|
2009
|
+
|
|
2010
|
+
// Helper to convert DB row to Skill type
|
|
2011
|
+
function rowToSkill(row: SkillRow): Skill {
|
|
2012
|
+
let metadata: Record<string, string> = {};
|
|
2013
|
+
if (row.metadata) {
|
|
2014
|
+
try {
|
|
2015
|
+
metadata = JSON.parse(row.metadata);
|
|
2016
|
+
} catch {
|
|
2017
|
+
// Use empty object if parsing fails
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
let allowed_tools: string[] = [];
|
|
2021
|
+
if (row.allowed_tools) {
|
|
2022
|
+
try {
|
|
2023
|
+
allowed_tools = JSON.parse(row.allowed_tools);
|
|
2024
|
+
} catch {
|
|
2025
|
+
// Use empty array if parsing fails
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
return {
|
|
2029
|
+
id: row.id,
|
|
2030
|
+
name: row.name,
|
|
2031
|
+
description: row.description,
|
|
2032
|
+
content: row.content,
|
|
2033
|
+
version: row.version || "1.0.0",
|
|
2034
|
+
license: row.license,
|
|
2035
|
+
compatibility: row.compatibility,
|
|
2036
|
+
metadata,
|
|
2037
|
+
allowed_tools,
|
|
2038
|
+
source: row.source as Skill["source"],
|
|
2039
|
+
source_url: row.source_url,
|
|
2040
|
+
enabled: row.enabled === 1,
|
|
2041
|
+
project_id: row.project_id,
|
|
2042
|
+
created_at: row.created_at,
|
|
2043
|
+
updated_at: row.updated_at,
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
|
|
1624
2047
|
// Generate unique ID
|
|
1625
2048
|
export function generateId(): string {
|
|
1626
2049
|
return Math.random().toString(36).substring(2, 15);
|