apteva 0.2.10 → 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/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
- // Migration runs silently
387
- db.run(migration.sql);
388
- db.run("INSERT INTO migrations (name) VALUES (?)", [migration.name]);
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);