devchain-cli 0.8.5 → 0.9.1
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/drizzle/0038_soft_texas_twister.sql +51 -0
- package/dist/drizzle/0039_early_the_spike.sql +1 -0
- package/dist/drizzle/0040_typical_mole_man.sql +3 -0
- package/dist/drizzle/0041_community_skill_sources.sql +12 -0
- package/dist/drizzle/meta/0037_snapshot.json +3670 -0
- package/dist/drizzle/meta/0038_snapshot.json +4019 -0
- package/dist/drizzle/meta/0039_snapshot.json +4026 -0
- package/dist/drizzle/meta/0040_snapshot.json +4028 -0
- package/dist/drizzle/meta/0041_snapshot.json +4102 -0
- package/dist/drizzle/meta/_journal.json +28 -0
- package/dist/server/app.module.js +2 -0
- package/dist/server/app.module.js.map +1 -1
- package/dist/server/modules/epics/controllers/epics.controller.js +4 -0
- package/dist/server/modules/epics/controllers/epics.controller.js.map +1 -1
- package/dist/server/modules/mcp/dtos/mcp.dto.d.ts +74 -0
- package/dist/server/modules/mcp/dtos/mcp.dto.js +18 -1
- package/dist/server/modules/mcp/dtos/mcp.dto.js.map +1 -1
- package/dist/server/modules/mcp/dtos/schema-registry.js +2 -0
- package/dist/server/modules/mcp/dtos/schema-registry.js.map +1 -1
- package/dist/server/modules/mcp/mcp.module.js +2 -0
- package/dist/server/modules/mcp/mcp.module.js.map +1 -1
- package/dist/server/modules/mcp/services/mcp.service.d.ts +7 -1
- package/dist/server/modules/mcp/services/mcp.service.js +126 -2
- package/dist/server/modules/mcp/services/mcp.service.js.map +1 -1
- package/dist/server/modules/mcp/tool-definitions.d.ts +177 -0
- package/dist/server/modules/mcp/tool-definitions.js +48 -0
- package/dist/server/modules/mcp/tool-definitions.js.map +1 -1
- package/dist/server/modules/projects/dtos/export.dto.d.ts +15 -15
- package/dist/server/modules/seeders/seeders/0002_seed_replace_permission_mode_plan.d.ts +3 -0
- package/dist/server/modules/seeders/seeders/0002_seed_replace_permission_mode_plan.js +45 -0
- package/dist/server/modules/seeders/seeders/0002_seed_replace_permission_mode_plan.js.map +1 -0
- package/dist/server/modules/seeders/seeders/0003_seed_preseed_jeffallan_claude_skills.d.ts +3 -0
- package/dist/server/modules/seeders/seeders/0003_seed_preseed_jeffallan_claude_skills.js +41 -0
- package/dist/server/modules/seeders/seeders/0003_seed_preseed_jeffallan_claude_skills.js.map +1 -0
- package/dist/server/modules/seeders/seeders/0004_seed_disable_microsoft_source_default.d.ts +3 -0
- package/dist/server/modules/seeders/seeders/0004_seed_disable_microsoft_source_default.js +48 -0
- package/dist/server/modules/seeders/seeders/0004_seed_disable_microsoft_source_default.js.map +1 -0
- package/dist/server/modules/seeders/services/data-seeder.service.js +9 -1
- package/dist/server/modules/seeders/services/data-seeder.service.js.map +1 -1
- package/dist/server/modules/settings/dtos/settings.dto.d.ts +18 -0
- package/dist/server/modules/settings/dtos/settings.dto.js +5 -0
- package/dist/server/modules/settings/dtos/settings.dto.js.map +1 -1
- package/dist/server/modules/settings/services/settings.service.d.ts +5 -0
- package/dist/server/modules/settings/services/settings.service.js +80 -1
- package/dist/server/modules/settings/services/settings.service.js.map +1 -1
- package/dist/server/modules/skills/adapters/anthropic-skill-source.adapter.d.ts +9 -0
- package/dist/server/modules/skills/adapters/anthropic-skill-source.adapter.js +169 -0
- package/dist/server/modules/skills/adapters/anthropic-skill-source.adapter.js.map +1 -0
- package/dist/server/modules/skills/adapters/community-skill-source.adapter.d.ts +11 -0
- package/dist/server/modules/skills/adapters/community-skill-source.adapter.js +181 -0
- package/dist/server/modules/skills/adapters/community-skill-source.adapter.js.map +1 -0
- package/dist/server/modules/skills/adapters/github-skill-source.base.d.ts +48 -0
- package/dist/server/modules/skills/adapters/github-skill-source.base.js +382 -0
- package/dist/server/modules/skills/adapters/github-skill-source.base.js.map +1 -0
- package/dist/server/modules/skills/adapters/microsoft-skill-source.adapter.d.ts +11 -0
- package/dist/server/modules/skills/adapters/microsoft-skill-source.adapter.js +227 -0
- package/dist/server/modules/skills/adapters/microsoft-skill-source.adapter.js.map +1 -0
- package/dist/server/modules/skills/adapters/openai-skill-source.adapter.d.ts +13 -0
- package/dist/server/modules/skills/adapters/openai-skill-source.adapter.js +260 -0
- package/dist/server/modules/skills/adapters/openai-skill-source.adapter.js.map +1 -0
- package/dist/server/modules/skills/adapters/skill-source.adapter.d.ts +26 -0
- package/dist/server/modules/skills/adapters/skill-source.adapter.js +5 -0
- package/dist/server/modules/skills/adapters/skill-source.adapter.js.map +1 -0
- package/dist/server/modules/skills/adapters/trailofbits-skill-source.adapter.d.ts +14 -0
- package/dist/server/modules/skills/adapters/trailofbits-skill-source.adapter.js +253 -0
- package/dist/server/modules/skills/adapters/trailofbits-skill-source.adapter.js.map +1 -0
- package/dist/server/modules/skills/adapters/vercel-skill-source.adapter.d.ts +15 -0
- package/dist/server/modules/skills/adapters/vercel-skill-source.adapter.js +292 -0
- package/dist/server/modules/skills/adapters/vercel-skill-source.adapter.js.map +1 -0
- package/dist/server/modules/skills/controllers/community-sources.controller.d.ts +12 -0
- package/dist/server/modules/skills/controllers/community-sources.controller.js +71 -0
- package/dist/server/modules/skills/controllers/community-sources.controller.js.map +1 -0
- package/dist/server/modules/skills/controllers/skills.controller.d.ts +42 -0
- package/dist/server/modules/skills/controllers/skills.controller.js +257 -0
- package/dist/server/modules/skills/controllers/skills.controller.js.map +1 -0
- package/dist/server/modules/skills/dtos/community-sources.dto.d.ts +100 -0
- package/dist/server/modules/skills/dtos/community-sources.dto.js +138 -0
- package/dist/server/modules/skills/dtos/community-sources.dto.js.map +1 -0
- package/dist/server/modules/skills/dtos/skill.dto.d.ts +169 -0
- package/dist/server/modules/skills/dtos/skill.dto.js +116 -0
- package/dist/server/modules/skills/dtos/skill.dto.js.map +1 -0
- package/dist/server/modules/skills/services/community-sources.service.d.ts +11 -0
- package/dist/server/modules/skills/services/community-sources.service.js +99 -0
- package/dist/server/modules/skills/services/community-sources.service.js.map +1 -0
- package/dist/server/modules/skills/services/skill-category.service.d.ts +5 -0
- package/dist/server/modules/skills/services/skill-category.service.js +155 -0
- package/dist/server/modules/skills/services/skill-category.service.js.map +1 -0
- package/dist/server/modules/skills/services/skill-source-registry.service.d.ts +9 -0
- package/dist/server/modules/skills/services/skill-source-registry.service.js +46 -0
- package/dist/server/modules/skills/services/skill-source-registry.service.js.map +1 -0
- package/dist/server/modules/skills/services/skill-sync.service.d.ts +38 -0
- package/dist/server/modules/skills/services/skill-sync.service.js +281 -0
- package/dist/server/modules/skills/services/skill-sync.service.js.map +1 -0
- package/dist/server/modules/skills/services/skills.service.d.ts +119 -0
- package/dist/server/modules/skills/services/skills.service.js +734 -0
- package/dist/server/modules/skills/services/skills.service.js.map +1 -0
- package/dist/server/modules/skills/skills.module.d.ts +2 -0
- package/dist/server/modules/skills/skills.module.js +76 -0
- package/dist/server/modules/skills/skills.module.js.map +1 -0
- package/dist/server/modules/storage/db/schema.d.ts +769 -0
- package/dist/server/modules/storage/db/schema.js +67 -1
- package/dist/server/modules/storage/db/schema.js.map +1 -1
- package/dist/server/modules/storage/interfaces/storage.interface.d.ts +7 -1
- package/dist/server/modules/storage/interfaces/storage.interface.js.map +1 -1
- package/dist/server/modules/storage/local/local-storage.service.d.ts +13 -1
- package/dist/server/modules/storage/local/local-storage.service.js +209 -7
- package/dist/server/modules/storage/local/local-storage.service.js.map +1 -1
- package/dist/server/modules/storage/models/domain.models.d.ts +59 -1
- package/dist/server/templates/dev-loop.json +85 -78
- package/dist/server/tsconfig.tsbuildinfo +1 -1
- package/dist/server/ui/assets/{ReviewDetailPage-DNZbiwkx.js → ReviewDetailPage-D13dH7Wh.js} +1 -1
- package/dist/server/ui/assets/{ReviewsPage-Dow8B8sr.js → ReviewsPage-C98ST0lf.js} +1 -1
- package/dist/server/ui/assets/index-C8Dc1yQf.js +945 -0
- package/dist/server/ui/assets/index-DZkJ40z9.css +32 -0
- package/dist/server/ui/assets/useReviewSubscription-CmLuF45Z.js +77 -0
- package/dist/server/ui/index.html +2 -2
- package/dist/templates/dev-loop.json +85 -78
- package/package.json +17 -1
- package/dist/server/ui/assets/index--HtPKe24.js +0 -914
- package/dist/server/ui/assets/index-BkiWahA0.css +0 -32
- package/dist/server/ui/assets/useReviewSubscription-CNhZ4Xwh.js +0 -83
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.SkillsService = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const node_crypto_1 = require("node:crypto");
|
|
18
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
19
|
+
const error_types_1 = require("../../../common/errors/error-types");
|
|
20
|
+
const logger_1 = require("../../../common/logging/logger");
|
|
21
|
+
const settings_service_1 = require("../../settings/services/settings.service");
|
|
22
|
+
const db_provider_1 = require("../../storage/db/db.provider");
|
|
23
|
+
const schema_1 = require("../../storage/db/schema");
|
|
24
|
+
const skill_source_registry_service_1 = require("./skill-source-registry.service");
|
|
25
|
+
const logger = (0, logger_1.createLogger)('SkillsService');
|
|
26
|
+
const VALID_SKILL_STATUSES = ['available', 'outdated', 'sync_error'];
|
|
27
|
+
let SkillsService = class SkillsService {
|
|
28
|
+
constructor(db, settingsService, skillSourceRegistry) {
|
|
29
|
+
this.db = db;
|
|
30
|
+
this.settingsService = settingsService;
|
|
31
|
+
this.skillSourceRegistry = skillSourceRegistry;
|
|
32
|
+
}
|
|
33
|
+
async listSkills(options = {}) {
|
|
34
|
+
const enabledSources = await this.getEnabledSources();
|
|
35
|
+
if (enabledSources.length === 0) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
const conditions = [];
|
|
39
|
+
conditions.push((0, drizzle_orm_1.inArray)(schema_1.skills.source, enabledSources));
|
|
40
|
+
if (options.source) {
|
|
41
|
+
conditions.push((0, drizzle_orm_1.eq)(schema_1.skills.source, options.source.trim().toLowerCase()));
|
|
42
|
+
}
|
|
43
|
+
if (options.category) {
|
|
44
|
+
conditions.push((0, drizzle_orm_1.eq)(schema_1.skills.category, options.category));
|
|
45
|
+
}
|
|
46
|
+
if (options.status) {
|
|
47
|
+
conditions.push((0, drizzle_orm_1.eq)(schema_1.skills.status, options.status));
|
|
48
|
+
}
|
|
49
|
+
const searchTerm = options.q?.trim().toLowerCase();
|
|
50
|
+
if (searchTerm) {
|
|
51
|
+
const likePattern = `%${searchTerm}%`;
|
|
52
|
+
conditions.push((0, drizzle_orm_1.sql) `(
|
|
53
|
+
lower(${schema_1.skills.slug}) LIKE ${likePattern}
|
|
54
|
+
OR lower(${schema_1.skills.name}) LIKE ${likePattern}
|
|
55
|
+
OR lower(${schema_1.skills.displayName}) LIKE ${likePattern}
|
|
56
|
+
OR lower(coalesce(${schema_1.skills.description}, '')) LIKE ${likePattern}
|
|
57
|
+
OR lower(coalesce(${schema_1.skills.shortDescription}, '')) LIKE ${likePattern}
|
|
58
|
+
OR lower(coalesce(${schema_1.skills.compatibility}, '')) LIKE ${likePattern}
|
|
59
|
+
)`);
|
|
60
|
+
}
|
|
61
|
+
const whereClause = this.combineConditions(conditions);
|
|
62
|
+
const query = this.db.select().from(schema_1.skills);
|
|
63
|
+
if (whereClause) {
|
|
64
|
+
query.where(whereClause);
|
|
65
|
+
}
|
|
66
|
+
const rows = await query.orderBy((0, drizzle_orm_1.asc)(schema_1.skills.name), (0, drizzle_orm_1.asc)(schema_1.skills.slug));
|
|
67
|
+
return rows.map((row) => this.mapSkillRow(row));
|
|
68
|
+
}
|
|
69
|
+
async listAllForProject(projectId, options = {}) {
|
|
70
|
+
const enabledSources = await this.getEnabledSources();
|
|
71
|
+
if (enabledSources.length === 0) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
const normalizedProjectId = this.requireNonEmpty(projectId, 'projectId');
|
|
75
|
+
const query = this.db
|
|
76
|
+
.select({
|
|
77
|
+
skill: schema_1.skills,
|
|
78
|
+
disabled: (0, drizzle_orm_1.sql) `case when ${schema_1.skillProjectDisabled.id} is null then 0 else 1 end`,
|
|
79
|
+
})
|
|
80
|
+
.from(schema_1.skills)
|
|
81
|
+
.leftJoin(schema_1.skillProjectDisabled, (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.skillProjectDisabled.skillId, schema_1.skills.id), (0, drizzle_orm_1.eq)(schema_1.skillProjectDisabled.projectId, normalizedProjectId)));
|
|
82
|
+
const conditions = [(0, drizzle_orm_1.inArray)(schema_1.skills.source, enabledSources)];
|
|
83
|
+
this.appendProjectSkillFilterConditions(conditions, options);
|
|
84
|
+
const whereClause = this.combineConditions(conditions);
|
|
85
|
+
if (whereClause) {
|
|
86
|
+
query.where(whereClause);
|
|
87
|
+
}
|
|
88
|
+
const rows = await query.orderBy((0, drizzle_orm_1.asc)(schema_1.skills.name), (0, drizzle_orm_1.asc)(schema_1.skills.slug));
|
|
89
|
+
return rows.map((row) => ({
|
|
90
|
+
...this.mapSkillRow(row.skill),
|
|
91
|
+
disabled: Number(row.disabled) === 1,
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
async listDiscoverable(projectId, options = {}) {
|
|
95
|
+
const enabledSources = await this.getEnabledSources();
|
|
96
|
+
if (enabledSources.length === 0) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
const normalizedProjectId = this.requireNonEmpty(projectId, 'projectId');
|
|
100
|
+
const query = this.db
|
|
101
|
+
.select({ skill: schema_1.skills })
|
|
102
|
+
.from(schema_1.skills)
|
|
103
|
+
.leftJoin(schema_1.skillProjectDisabled, (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.skillProjectDisabled.skillId, schema_1.skills.id), (0, drizzle_orm_1.eq)(schema_1.skillProjectDisabled.projectId, normalizedProjectId)));
|
|
104
|
+
const conditions = [(0, drizzle_orm_1.isNull)(schema_1.skillProjectDisabled.id)];
|
|
105
|
+
conditions.push((0, drizzle_orm_1.inArray)(schema_1.skills.source, enabledSources));
|
|
106
|
+
this.appendProjectSkillFilterConditions(conditions, options);
|
|
107
|
+
const whereClause = this.combineConditions(conditions);
|
|
108
|
+
if (whereClause) {
|
|
109
|
+
query.where(whereClause);
|
|
110
|
+
}
|
|
111
|
+
const rows = await query.orderBy((0, drizzle_orm_1.asc)(schema_1.skills.name), (0, drizzle_orm_1.asc)(schema_1.skills.slug));
|
|
112
|
+
return rows.map((row) => this.mapSkillRow(row.skill));
|
|
113
|
+
}
|
|
114
|
+
async getSkill(id) {
|
|
115
|
+
const skillId = this.requireNonEmpty(id, 'id');
|
|
116
|
+
const row = await this.db.select().from(schema_1.skills).where((0, drizzle_orm_1.eq)(schema_1.skills.id, skillId)).limit(1);
|
|
117
|
+
if (!row[0]) {
|
|
118
|
+
throw new error_types_1.NotFoundError('Skill', skillId);
|
|
119
|
+
}
|
|
120
|
+
return this.mapSkillRow(row[0]);
|
|
121
|
+
}
|
|
122
|
+
async getSkillBySlug(slug) {
|
|
123
|
+
const normalizedSlug = this.requireNonEmpty(slug, 'slug');
|
|
124
|
+
const row = await this.db.select().from(schema_1.skills).where((0, drizzle_orm_1.eq)(schema_1.skills.slug, normalizedSlug)).limit(1);
|
|
125
|
+
if (!row[0]) {
|
|
126
|
+
throw new error_types_1.NotFoundError('Skill', normalizedSlug);
|
|
127
|
+
}
|
|
128
|
+
return this.mapSkillRow(row[0]);
|
|
129
|
+
}
|
|
130
|
+
async resolveSkillSummariesBySlugs(slugsToResolve) {
|
|
131
|
+
const uniqueSlugs = Array.from(new Set(slugsToResolve.map((slug) => this.requireNonEmpty(slug, 'slug').toLowerCase())));
|
|
132
|
+
if (uniqueSlugs.length === 0) {
|
|
133
|
+
return {};
|
|
134
|
+
}
|
|
135
|
+
const rows = await this.db
|
|
136
|
+
.select({
|
|
137
|
+
id: schema_1.skills.id,
|
|
138
|
+
slug: schema_1.skills.slug,
|
|
139
|
+
name: schema_1.skills.name,
|
|
140
|
+
displayName: schema_1.skills.displayName,
|
|
141
|
+
source: schema_1.skills.source,
|
|
142
|
+
category: schema_1.skills.category,
|
|
143
|
+
shortDescription: schema_1.skills.shortDescription,
|
|
144
|
+
description: schema_1.skills.description,
|
|
145
|
+
})
|
|
146
|
+
.from(schema_1.skills)
|
|
147
|
+
.where((0, drizzle_orm_1.inArray)(schema_1.skills.slug, uniqueSlugs));
|
|
148
|
+
const resolved = {};
|
|
149
|
+
for (const row of rows) {
|
|
150
|
+
resolved[row.slug] = {
|
|
151
|
+
id: row.id,
|
|
152
|
+
slug: row.slug,
|
|
153
|
+
name: row.name,
|
|
154
|
+
displayName: row.displayName,
|
|
155
|
+
source: row.source,
|
|
156
|
+
category: row.category,
|
|
157
|
+
shortDescription: row.shortDescription,
|
|
158
|
+
description: row.description,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return resolved;
|
|
162
|
+
}
|
|
163
|
+
async upsertSkill(slug, data) {
|
|
164
|
+
const normalizedSlug = this.requireNonEmpty(slug, 'slug');
|
|
165
|
+
const now = new Date().toISOString();
|
|
166
|
+
const existing = await this.db
|
|
167
|
+
.select()
|
|
168
|
+
.from(schema_1.skills)
|
|
169
|
+
.where((0, drizzle_orm_1.eq)(schema_1.skills.slug, normalizedSlug))
|
|
170
|
+
.limit(1);
|
|
171
|
+
if (existing[0]) {
|
|
172
|
+
const updatePayload = { updatedAt: now };
|
|
173
|
+
if (data.name !== undefined) {
|
|
174
|
+
updatePayload.name = this.requireNonEmpty(data.name, 'name');
|
|
175
|
+
}
|
|
176
|
+
if (data.displayName !== undefined) {
|
|
177
|
+
updatePayload.displayName = this.requireNonEmpty(data.displayName, 'displayName');
|
|
178
|
+
}
|
|
179
|
+
if (data.source !== undefined) {
|
|
180
|
+
updatePayload.source = this.requireNonEmpty(data.source, 'source');
|
|
181
|
+
}
|
|
182
|
+
if (data.description !== undefined) {
|
|
183
|
+
updatePayload.description = this.normalizeNullableString(data.description);
|
|
184
|
+
}
|
|
185
|
+
if (data.shortDescription !== undefined) {
|
|
186
|
+
updatePayload.shortDescription = this.normalizeNullableString(data.shortDescription);
|
|
187
|
+
}
|
|
188
|
+
if (data.sourceUrl !== undefined) {
|
|
189
|
+
updatePayload.sourceUrl = this.normalizeNullableString(data.sourceUrl);
|
|
190
|
+
}
|
|
191
|
+
if (data.sourceCommit !== undefined) {
|
|
192
|
+
updatePayload.sourceCommit = this.normalizeNullableString(data.sourceCommit);
|
|
193
|
+
}
|
|
194
|
+
if (data.category !== undefined) {
|
|
195
|
+
updatePayload.category = this.normalizeNullableString(data.category);
|
|
196
|
+
}
|
|
197
|
+
if (data.license !== undefined) {
|
|
198
|
+
updatePayload.license = this.normalizeNullableString(data.license);
|
|
199
|
+
}
|
|
200
|
+
if (data.compatibility !== undefined) {
|
|
201
|
+
updatePayload.compatibility = this.normalizeNullableString(data.compatibility);
|
|
202
|
+
}
|
|
203
|
+
if (data.instructionContent !== undefined) {
|
|
204
|
+
updatePayload.instructionContent = this.normalizeNullableString(data.instructionContent);
|
|
205
|
+
}
|
|
206
|
+
if (data.contentPath !== undefined) {
|
|
207
|
+
updatePayload.contentPath = this.normalizeNullableString(data.contentPath);
|
|
208
|
+
}
|
|
209
|
+
if (data.lastSyncedAt !== undefined) {
|
|
210
|
+
updatePayload.lastSyncedAt = this.normalizeNullableString(data.lastSyncedAt);
|
|
211
|
+
}
|
|
212
|
+
if (data.frontmatter !== undefined) {
|
|
213
|
+
updatePayload.frontmatter = this.serializeJsonObject(data.frontmatter, 'frontmatter');
|
|
214
|
+
}
|
|
215
|
+
if (data.resources !== undefined) {
|
|
216
|
+
updatePayload.resources = this.serializeResources(data.resources);
|
|
217
|
+
}
|
|
218
|
+
if (data.status !== undefined) {
|
|
219
|
+
updatePayload.status = this.validateStatus(data.status);
|
|
220
|
+
}
|
|
221
|
+
await this.db.update(schema_1.skills).set(updatePayload).where((0, drizzle_orm_1.eq)(schema_1.skills.slug, normalizedSlug));
|
|
222
|
+
return this.getSkillBySlug(normalizedSlug);
|
|
223
|
+
}
|
|
224
|
+
const source = this.requireNonEmpty(data.source ?? '', 'source');
|
|
225
|
+
const name = this.requireNonEmpty(data.name ?? normalizedSlug, 'name');
|
|
226
|
+
const displayName = this.requireNonEmpty(data.displayName ?? data.name ?? normalizedSlug, 'displayName');
|
|
227
|
+
const insertPayload = {
|
|
228
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
229
|
+
slug: normalizedSlug,
|
|
230
|
+
name,
|
|
231
|
+
displayName,
|
|
232
|
+
description: this.normalizeNullableString(data.description ?? null),
|
|
233
|
+
shortDescription: this.normalizeNullableString(data.shortDescription ?? null),
|
|
234
|
+
source,
|
|
235
|
+
sourceUrl: this.normalizeNullableString(data.sourceUrl ?? null),
|
|
236
|
+
sourceCommit: this.normalizeNullableString(data.sourceCommit ?? null),
|
|
237
|
+
category: this.normalizeNullableString(data.category ?? null),
|
|
238
|
+
license: this.normalizeNullableString(data.license ?? null),
|
|
239
|
+
compatibility: this.normalizeNullableString(data.compatibility ?? null),
|
|
240
|
+
frontmatter: this.serializeJsonObject(data.frontmatter ?? null, 'frontmatter'),
|
|
241
|
+
instructionContent: this.normalizeNullableString(data.instructionContent ?? null),
|
|
242
|
+
contentPath: this.normalizeNullableString(data.contentPath ?? null),
|
|
243
|
+
resources: this.serializeResources(data.resources ?? []),
|
|
244
|
+
status: this.validateStatus(data.status ?? 'available'),
|
|
245
|
+
lastSyncedAt: this.normalizeNullableString(data.lastSyncedAt ?? null),
|
|
246
|
+
createdAt: now,
|
|
247
|
+
updatedAt: now,
|
|
248
|
+
};
|
|
249
|
+
await this.db.insert(schema_1.skills).values(insertPayload);
|
|
250
|
+
return this.getSkillBySlug(normalizedSlug);
|
|
251
|
+
}
|
|
252
|
+
async disableSkill(projectId, skillId) {
|
|
253
|
+
const normalizedProjectId = this.requireNonEmpty(projectId, 'projectId');
|
|
254
|
+
const normalizedSkillId = this.requireNonEmpty(skillId, 'skillId');
|
|
255
|
+
const now = new Date().toISOString();
|
|
256
|
+
try {
|
|
257
|
+
await this.db.insert(schema_1.skillProjectDisabled).values({
|
|
258
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
259
|
+
projectId: normalizedProjectId,
|
|
260
|
+
skillId: normalizedSkillId,
|
|
261
|
+
createdAt: now,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
if (this.isUniqueConstraintError(error)) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (this.isForeignKeyConstraintError(error)) {
|
|
269
|
+
throw new error_types_1.ValidationError('Cannot disable skill for unknown project or skill.', {
|
|
270
|
+
projectId: normalizedProjectId,
|
|
271
|
+
skillId: normalizedSkillId,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
throw new error_types_1.StorageError('Failed to disable skill for project.', {
|
|
275
|
+
projectId: normalizedProjectId,
|
|
276
|
+
skillId: normalizedSkillId,
|
|
277
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
async enableSkill(projectId, skillId) {
|
|
282
|
+
const normalizedProjectId = this.requireNonEmpty(projectId, 'projectId');
|
|
283
|
+
const normalizedSkillId = this.requireNonEmpty(skillId, 'skillId');
|
|
284
|
+
await this.db
|
|
285
|
+
.delete(schema_1.skillProjectDisabled)
|
|
286
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.skillProjectDisabled.projectId, normalizedProjectId), (0, drizzle_orm_1.eq)(schema_1.skillProjectDisabled.skillId, normalizedSkillId)));
|
|
287
|
+
}
|
|
288
|
+
async listDisabled(projectId) {
|
|
289
|
+
const normalizedProjectId = this.requireNonEmpty(projectId, 'projectId');
|
|
290
|
+
const rows = await this.db
|
|
291
|
+
.select({ skillId: schema_1.skillProjectDisabled.skillId })
|
|
292
|
+
.from(schema_1.skillProjectDisabled)
|
|
293
|
+
.where((0, drizzle_orm_1.eq)(schema_1.skillProjectDisabled.projectId, normalizedProjectId))
|
|
294
|
+
.orderBy((0, drizzle_orm_1.asc)(schema_1.skillProjectDisabled.createdAt));
|
|
295
|
+
return rows.map((row) => row.skillId);
|
|
296
|
+
}
|
|
297
|
+
async disableAll(projectId) {
|
|
298
|
+
const enabledSources = await this.getEnabledSources();
|
|
299
|
+
if (enabledSources.length === 0) {
|
|
300
|
+
return 0;
|
|
301
|
+
}
|
|
302
|
+
const normalizedProjectId = this.requireNonEmpty(projectId, 'projectId');
|
|
303
|
+
const now = new Date().toISOString();
|
|
304
|
+
const [allSkills, disabledRows] = await Promise.all([
|
|
305
|
+
this.db
|
|
306
|
+
.select({ skillId: schema_1.skills.id })
|
|
307
|
+
.from(schema_1.skills)
|
|
308
|
+
.where((0, drizzle_orm_1.inArray)(schema_1.skills.source, enabledSources)),
|
|
309
|
+
this.db
|
|
310
|
+
.select({ skillId: schema_1.skillProjectDisabled.skillId })
|
|
311
|
+
.from(schema_1.skillProjectDisabled)
|
|
312
|
+
.innerJoin(schema_1.skills, (0, drizzle_orm_1.eq)(schema_1.skills.id, schema_1.skillProjectDisabled.skillId))
|
|
313
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.skillProjectDisabled.projectId, normalizedProjectId), (0, drizzle_orm_1.inArray)(schema_1.skills.source, enabledSources))),
|
|
314
|
+
]);
|
|
315
|
+
const disabledSet = new Set(disabledRows.map((row) => row.skillId));
|
|
316
|
+
const rowsToInsert = allSkills
|
|
317
|
+
.filter((row) => !disabledSet.has(row.skillId))
|
|
318
|
+
.map((row) => ({
|
|
319
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
320
|
+
projectId: normalizedProjectId,
|
|
321
|
+
skillId: row.skillId,
|
|
322
|
+
createdAt: now,
|
|
323
|
+
}));
|
|
324
|
+
if (rowsToInsert.length === 0) {
|
|
325
|
+
return 0;
|
|
326
|
+
}
|
|
327
|
+
await this.db.insert(schema_1.skillProjectDisabled).values(rowsToInsert);
|
|
328
|
+
return rowsToInsert.length;
|
|
329
|
+
}
|
|
330
|
+
async enableAll(projectId) {
|
|
331
|
+
const enabledSources = await this.getEnabledSources();
|
|
332
|
+
if (enabledSources.length === 0) {
|
|
333
|
+
return 0;
|
|
334
|
+
}
|
|
335
|
+
const normalizedProjectId = this.requireNonEmpty(projectId, 'projectId');
|
|
336
|
+
const disabledRows = await this.db
|
|
337
|
+
.select({ skillId: schema_1.skillProjectDisabled.skillId })
|
|
338
|
+
.from(schema_1.skillProjectDisabled)
|
|
339
|
+
.innerJoin(schema_1.skills, (0, drizzle_orm_1.eq)(schema_1.skills.id, schema_1.skillProjectDisabled.skillId))
|
|
340
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.skillProjectDisabled.projectId, normalizedProjectId), (0, drizzle_orm_1.inArray)(schema_1.skills.source, enabledSources)));
|
|
341
|
+
if (disabledRows.length === 0) {
|
|
342
|
+
return 0;
|
|
343
|
+
}
|
|
344
|
+
const disabledSkillIds = disabledRows.map((row) => row.skillId);
|
|
345
|
+
await this.db
|
|
346
|
+
.delete(schema_1.skillProjectDisabled)
|
|
347
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.skillProjectDisabled.projectId, normalizedProjectId), (0, drizzle_orm_1.inArray)(schema_1.skillProjectDisabled.skillId, disabledSkillIds)));
|
|
348
|
+
return disabledRows.length;
|
|
349
|
+
}
|
|
350
|
+
async listSources() {
|
|
351
|
+
const sourceRows = await this.db
|
|
352
|
+
.select({ source: schema_1.skills.source, skillCount: (0, drizzle_orm_1.count)() })
|
|
353
|
+
.from(schema_1.skills)
|
|
354
|
+
.groupBy(schema_1.skills.source);
|
|
355
|
+
const sourceCountMap = new Map(sourceRows.map((row) => [row.source.trim().toLowerCase(), Number(row.skillCount)]));
|
|
356
|
+
const sourceSettings = this.settingsService.getSkillSourcesEnabled();
|
|
357
|
+
const registeredSources = await this.getRegisteredSources();
|
|
358
|
+
return registeredSources.map((source) => ({
|
|
359
|
+
name: source.name,
|
|
360
|
+
enabled: this.isSourceEnabled(source.name, sourceSettings),
|
|
361
|
+
repoUrl: source.repoUrl,
|
|
362
|
+
skillCount: sourceCountMap.get(source.name) ?? 0,
|
|
363
|
+
}));
|
|
364
|
+
}
|
|
365
|
+
async setSourceEnabled(sourceName, enabled) {
|
|
366
|
+
const normalizedSourceName = await this.requireKnownSourceName(sourceName);
|
|
367
|
+
await this.settingsService.setSkillSourceEnabled(normalizedSourceName, enabled);
|
|
368
|
+
return { name: normalizedSourceName, enabled };
|
|
369
|
+
}
|
|
370
|
+
async logUsage(skillId, skillSlug, projectId, agentId, agentNameSnapshot) {
|
|
371
|
+
const normalizedSkillId = this.requireNonEmpty(skillId, 'skillId');
|
|
372
|
+
const normalizedSkillSlug = this.requireNonEmpty(skillSlug, 'skillSlug');
|
|
373
|
+
const now = new Date().toISOString();
|
|
374
|
+
const usageRecord = {
|
|
375
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
376
|
+
skillId: normalizedSkillId,
|
|
377
|
+
skillSlug: normalizedSkillSlug,
|
|
378
|
+
projectId: this.normalizeNullableString(projectId ?? null) ?? null,
|
|
379
|
+
agentId: this.normalizeNullableString(agentId ?? null) ?? null,
|
|
380
|
+
agentNameSnapshot: this.normalizeNullableString(agentNameSnapshot ?? null) ?? null,
|
|
381
|
+
accessedAt: now,
|
|
382
|
+
};
|
|
383
|
+
await this.db.insert(schema_1.skillUsageLog).values({
|
|
384
|
+
id: usageRecord.id,
|
|
385
|
+
skillId: usageRecord.skillId,
|
|
386
|
+
skillSlug: usageRecord.skillSlug,
|
|
387
|
+
projectId: usageRecord.projectId,
|
|
388
|
+
agentId: usageRecord.agentId,
|
|
389
|
+
agentNameSnapshot: usageRecord.agentNameSnapshot,
|
|
390
|
+
accessedAt: usageRecord.accessedAt,
|
|
391
|
+
});
|
|
392
|
+
return usageRecord;
|
|
393
|
+
}
|
|
394
|
+
async getUsageStats(options = {}) {
|
|
395
|
+
const conditions = [];
|
|
396
|
+
if (options.projectId !== undefined) {
|
|
397
|
+
if (options.projectId === null) {
|
|
398
|
+
conditions.push((0, drizzle_orm_1.isNull)(schema_1.skillUsageLog.projectId));
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
conditions.push((0, drizzle_orm_1.eq)(schema_1.skillUsageLog.projectId, options.projectId));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (options.from) {
|
|
405
|
+
conditions.push((0, drizzle_orm_1.gte)(schema_1.skillUsageLog.accessedAt, options.from));
|
|
406
|
+
}
|
|
407
|
+
if (options.to) {
|
|
408
|
+
conditions.push((0, drizzle_orm_1.lte)(schema_1.skillUsageLog.accessedAt, options.to));
|
|
409
|
+
}
|
|
410
|
+
const usageCountExpr = (0, drizzle_orm_1.count)();
|
|
411
|
+
const firstAccessedExpr = (0, drizzle_orm_1.sql) `min(${schema_1.skillUsageLog.accessedAt})`;
|
|
412
|
+
const lastAccessedExpr = (0, drizzle_orm_1.sql) `max(${schema_1.skillUsageLog.accessedAt})`;
|
|
413
|
+
const whereClause = this.combineConditions(conditions);
|
|
414
|
+
const limit = options.limit ?? 100;
|
|
415
|
+
const offset = options.offset ?? 0;
|
|
416
|
+
const query = this.db
|
|
417
|
+
.select({
|
|
418
|
+
skillId: schema_1.skillUsageLog.skillId,
|
|
419
|
+
skillSlug: schema_1.skillUsageLog.skillSlug,
|
|
420
|
+
usageCount: usageCountExpr,
|
|
421
|
+
firstAccessedAt: firstAccessedExpr,
|
|
422
|
+
lastAccessedAt: lastAccessedExpr,
|
|
423
|
+
skillName: schema_1.skills.name,
|
|
424
|
+
skillDisplayName: schema_1.skills.displayName,
|
|
425
|
+
})
|
|
426
|
+
.from(schema_1.skillUsageLog)
|
|
427
|
+
.leftJoin(schema_1.skills, (0, drizzle_orm_1.eq)(schema_1.skills.id, schema_1.skillUsageLog.skillId));
|
|
428
|
+
if (whereClause) {
|
|
429
|
+
query.where(whereClause);
|
|
430
|
+
}
|
|
431
|
+
const rows = await query
|
|
432
|
+
.groupBy(schema_1.skillUsageLog.skillId, schema_1.skillUsageLog.skillSlug, schema_1.skills.name, schema_1.skills.displayName)
|
|
433
|
+
.orderBy((0, drizzle_orm_1.desc)(usageCountExpr), (0, drizzle_orm_1.desc)(lastAccessedExpr))
|
|
434
|
+
.limit(limit)
|
|
435
|
+
.offset(offset);
|
|
436
|
+
return rows.map((row) => ({
|
|
437
|
+
skillId: row.skillId,
|
|
438
|
+
skillSlug: row.skillSlug,
|
|
439
|
+
usageCount: Number(row.usageCount ?? 0),
|
|
440
|
+
firstAccessedAt: row.firstAccessedAt,
|
|
441
|
+
lastAccessedAt: row.lastAccessedAt,
|
|
442
|
+
skillName: row.skillName ?? null,
|
|
443
|
+
skillDisplayName: row.skillDisplayName ?? null,
|
|
444
|
+
}));
|
|
445
|
+
}
|
|
446
|
+
async listUsageLog(options = {}) {
|
|
447
|
+
const conditions = [];
|
|
448
|
+
if (options.projectId) {
|
|
449
|
+
conditions.push((0, drizzle_orm_1.eq)(schema_1.skillUsageLog.projectId, options.projectId));
|
|
450
|
+
}
|
|
451
|
+
if (options.skillId) {
|
|
452
|
+
conditions.push((0, drizzle_orm_1.eq)(schema_1.skillUsageLog.skillId, options.skillId));
|
|
453
|
+
}
|
|
454
|
+
if (options.agentId) {
|
|
455
|
+
conditions.push((0, drizzle_orm_1.eq)(schema_1.skillUsageLog.agentId, options.agentId));
|
|
456
|
+
}
|
|
457
|
+
if (options.from) {
|
|
458
|
+
conditions.push((0, drizzle_orm_1.gte)(schema_1.skillUsageLog.accessedAt, options.from));
|
|
459
|
+
}
|
|
460
|
+
if (options.to) {
|
|
461
|
+
conditions.push((0, drizzle_orm_1.lte)(schema_1.skillUsageLog.accessedAt, options.to));
|
|
462
|
+
}
|
|
463
|
+
const whereClause = this.combineConditions(conditions);
|
|
464
|
+
const limit = options.limit ?? 100;
|
|
465
|
+
const offset = options.offset ?? 0;
|
|
466
|
+
const itemsQuery = this.db.select().from(schema_1.skillUsageLog);
|
|
467
|
+
if (whereClause) {
|
|
468
|
+
itemsQuery.where(whereClause);
|
|
469
|
+
}
|
|
470
|
+
const rows = await itemsQuery
|
|
471
|
+
.orderBy((0, drizzle_orm_1.desc)(schema_1.skillUsageLog.accessedAt))
|
|
472
|
+
.limit(limit)
|
|
473
|
+
.offset(offset);
|
|
474
|
+
const totalQuery = this.db.select({ count: (0, drizzle_orm_1.sql) `count(*)` }).from(schema_1.skillUsageLog);
|
|
475
|
+
if (whereClause) {
|
|
476
|
+
totalQuery.where(whereClause);
|
|
477
|
+
}
|
|
478
|
+
const totalResult = await totalQuery;
|
|
479
|
+
const total = Number(totalResult[0]?.count ?? 0);
|
|
480
|
+
return {
|
|
481
|
+
items: rows.map((row) => ({
|
|
482
|
+
id: row.id,
|
|
483
|
+
skillId: row.skillId,
|
|
484
|
+
skillSlug: row.skillSlug,
|
|
485
|
+
projectId: row.projectId,
|
|
486
|
+
agentId: row.agentId,
|
|
487
|
+
agentNameSnapshot: row.agentNameSnapshot,
|
|
488
|
+
accessedAt: row.accessedAt,
|
|
489
|
+
})),
|
|
490
|
+
total,
|
|
491
|
+
limit,
|
|
492
|
+
offset,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
appendProjectSkillFilterConditions(conditions, options) {
|
|
496
|
+
const searchTerm = options.q?.trim().toLowerCase();
|
|
497
|
+
if (searchTerm) {
|
|
498
|
+
const likePattern = `%${searchTerm}%`;
|
|
499
|
+
conditions.push((0, drizzle_orm_1.sql) `(
|
|
500
|
+
lower(${schema_1.skills.slug}) LIKE ${likePattern}
|
|
501
|
+
OR lower(${schema_1.skills.name}) LIKE ${likePattern}
|
|
502
|
+
OR lower(${schema_1.skills.displayName}) LIKE ${likePattern}
|
|
503
|
+
OR lower(coalesce(${schema_1.skills.description}, '')) LIKE ${likePattern}
|
|
504
|
+
OR lower(coalesce(${schema_1.skills.shortDescription}, '')) LIKE ${likePattern}
|
|
505
|
+
OR lower(coalesce(${schema_1.skills.compatibility}, '')) LIKE ${likePattern}
|
|
506
|
+
)`);
|
|
507
|
+
}
|
|
508
|
+
if (options.source) {
|
|
509
|
+
conditions.push((0, drizzle_orm_1.eq)(schema_1.skills.source, options.source.trim().toLowerCase()));
|
|
510
|
+
}
|
|
511
|
+
if (options.category) {
|
|
512
|
+
conditions.push((0, drizzle_orm_1.eq)(schema_1.skills.category, options.category));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
mapSkillRow(row) {
|
|
516
|
+
return {
|
|
517
|
+
id: row.id,
|
|
518
|
+
slug: row.slug,
|
|
519
|
+
name: row.name,
|
|
520
|
+
displayName: row.displayName,
|
|
521
|
+
description: row.description,
|
|
522
|
+
shortDescription: row.shortDescription,
|
|
523
|
+
source: row.source,
|
|
524
|
+
sourceUrl: row.sourceUrl,
|
|
525
|
+
sourceCommit: row.sourceCommit,
|
|
526
|
+
category: row.category,
|
|
527
|
+
license: row.license,
|
|
528
|
+
compatibility: row.compatibility,
|
|
529
|
+
frontmatter: this.parseJsonObject(row.frontmatter, 'frontmatter'),
|
|
530
|
+
instructionContent: row.instructionContent,
|
|
531
|
+
contentPath: row.contentPath,
|
|
532
|
+
resources: this.parseResources(row.resources),
|
|
533
|
+
status: this.parseStatus(row.status),
|
|
534
|
+
lastSyncedAt: row.lastSyncedAt,
|
|
535
|
+
createdAt: row.createdAt,
|
|
536
|
+
updatedAt: row.updatedAt,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
parseJsonObject(rawValue, fieldName) {
|
|
540
|
+
if (rawValue === null) {
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
try {
|
|
544
|
+
const parsed = JSON.parse(rawValue);
|
|
545
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
546
|
+
return parsed;
|
|
547
|
+
}
|
|
548
|
+
logger.warn({ fieldName }, 'Expected JSON object but found different type');
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
logger.warn({
|
|
553
|
+
fieldName,
|
|
554
|
+
error: error instanceof Error ? error.message : String(error),
|
|
555
|
+
}, 'Failed to parse JSON object field');
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
parseResources(rawValue) {
|
|
560
|
+
if (rawValue === null) {
|
|
561
|
+
return [];
|
|
562
|
+
}
|
|
563
|
+
try {
|
|
564
|
+
const parsed = JSON.parse(rawValue);
|
|
565
|
+
if (!Array.isArray(parsed)) {
|
|
566
|
+
logger.warn('Expected resources JSON array but found different type');
|
|
567
|
+
return [];
|
|
568
|
+
}
|
|
569
|
+
return parsed
|
|
570
|
+
.filter((item) => typeof item === 'string')
|
|
571
|
+
.map((item) => item.trim())
|
|
572
|
+
.filter((item) => item.length > 0);
|
|
573
|
+
}
|
|
574
|
+
catch (error) {
|
|
575
|
+
logger.warn({ error: error instanceof Error ? error.message : String(error) }, 'Failed to parse resources JSON field');
|
|
576
|
+
return [];
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
serializeJsonObject(value, fieldName) {
|
|
580
|
+
if (value === undefined) {
|
|
581
|
+
return undefined;
|
|
582
|
+
}
|
|
583
|
+
if (value === null) {
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
try {
|
|
587
|
+
return JSON.stringify(value);
|
|
588
|
+
}
|
|
589
|
+
catch (error) {
|
|
590
|
+
throw new error_types_1.ValidationError(`Invalid ${fieldName}: value is not serializable JSON.`, {
|
|
591
|
+
fieldName,
|
|
592
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
serializeResources(resources) {
|
|
597
|
+
if (resources === undefined) {
|
|
598
|
+
return undefined;
|
|
599
|
+
}
|
|
600
|
+
if (resources === null) {
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
if (!Array.isArray(resources)) {
|
|
604
|
+
throw new error_types_1.ValidationError('Invalid resources: expected an array of strings.');
|
|
605
|
+
}
|
|
606
|
+
const normalizedResources = resources
|
|
607
|
+
.filter((item) => typeof item === 'string')
|
|
608
|
+
.map((item) => item.trim())
|
|
609
|
+
.filter((item) => item.length > 0);
|
|
610
|
+
try {
|
|
611
|
+
return JSON.stringify(normalizedResources);
|
|
612
|
+
}
|
|
613
|
+
catch (error) {
|
|
614
|
+
throw new error_types_1.ValidationError('Invalid resources: value is not serializable JSON.', {
|
|
615
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
validateStatus(status) {
|
|
620
|
+
if (VALID_SKILL_STATUSES.includes(status)) {
|
|
621
|
+
return status;
|
|
622
|
+
}
|
|
623
|
+
throw new error_types_1.ValidationError('Invalid skill status value.', {
|
|
624
|
+
status,
|
|
625
|
+
supportedStatuses: VALID_SKILL_STATUSES,
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
parseStatus(status) {
|
|
629
|
+
if (VALID_SKILL_STATUSES.includes(status)) {
|
|
630
|
+
return status;
|
|
631
|
+
}
|
|
632
|
+
logger.warn({ status }, 'Unknown skill status in database; defaulting to available');
|
|
633
|
+
return 'available';
|
|
634
|
+
}
|
|
635
|
+
normalizeNullableString(value) {
|
|
636
|
+
if (value === undefined) {
|
|
637
|
+
return undefined;
|
|
638
|
+
}
|
|
639
|
+
if (value === null) {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
const trimmed = value.trim();
|
|
643
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
644
|
+
}
|
|
645
|
+
requireNonEmpty(value, fieldName) {
|
|
646
|
+
const normalized = value.trim();
|
|
647
|
+
if (!normalized) {
|
|
648
|
+
throw new error_types_1.ValidationError(`${fieldName} is required.`, { fieldName });
|
|
649
|
+
}
|
|
650
|
+
return normalized;
|
|
651
|
+
}
|
|
652
|
+
combineConditions(conditions) {
|
|
653
|
+
if (conditions.length === 0) {
|
|
654
|
+
return undefined;
|
|
655
|
+
}
|
|
656
|
+
if (conditions.length === 1) {
|
|
657
|
+
return conditions[0];
|
|
658
|
+
}
|
|
659
|
+
return (0, drizzle_orm_1.and)(...conditions);
|
|
660
|
+
}
|
|
661
|
+
async getRegisteredSources() {
|
|
662
|
+
const adapters = await this.skillSourceRegistry.getAdapters();
|
|
663
|
+
const sourceMap = new Map();
|
|
664
|
+
for (const adapter of adapters) {
|
|
665
|
+
const normalizedName = adapter.sourceName.trim().toLowerCase();
|
|
666
|
+
if (!normalizedName || sourceMap.has(normalizedName)) {
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
sourceMap.set(normalizedName, adapter.repoUrl);
|
|
670
|
+
}
|
|
671
|
+
return Array.from(sourceMap.entries())
|
|
672
|
+
.map(([name, repoUrl]) => ({ name, repoUrl }))
|
|
673
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
674
|
+
}
|
|
675
|
+
async getEnabledSources() {
|
|
676
|
+
const sourceSettings = this.settingsService.getSkillSourcesEnabled();
|
|
677
|
+
const registeredSources = await this.getRegisteredSources();
|
|
678
|
+
return registeredSources
|
|
679
|
+
.map((source) => source.name)
|
|
680
|
+
.filter((sourceName) => this.isSourceEnabled(sourceName, sourceSettings));
|
|
681
|
+
}
|
|
682
|
+
isSourceEnabled(sourceName, sourceSettings) {
|
|
683
|
+
return sourceSettings[sourceName] !== false;
|
|
684
|
+
}
|
|
685
|
+
async requireKnownSourceName(sourceName) {
|
|
686
|
+
const normalized = this.requireNonEmpty(sourceName, 'sourceName').toLowerCase();
|
|
687
|
+
const knownSources = new Set((await this.getRegisteredSources()).map((source) => source.name));
|
|
688
|
+
if (!knownSources.has(normalized)) {
|
|
689
|
+
throw new error_types_1.ValidationError(`Unknown skill source: ${normalized}`, { sourceName: normalized });
|
|
690
|
+
}
|
|
691
|
+
return normalized;
|
|
692
|
+
}
|
|
693
|
+
isUniqueConstraintError(error) {
|
|
694
|
+
const code = this.readErrorCode(error);
|
|
695
|
+
const message = this.readErrorMessage(error);
|
|
696
|
+
return (code === 'SQLITE_CONSTRAINT' ||
|
|
697
|
+
code === 'SQLITE_CONSTRAINT_UNIQUE' ||
|
|
698
|
+
code === 19 ||
|
|
699
|
+
message.includes('UNIQUE constraint failed'));
|
|
700
|
+
}
|
|
701
|
+
isForeignKeyConstraintError(error) {
|
|
702
|
+
const code = this.readErrorCode(error);
|
|
703
|
+
const message = this.readErrorMessage(error);
|
|
704
|
+
return (code === 'SQLITE_CONSTRAINT_FOREIGNKEY' ||
|
|
705
|
+
code === 'SQLITE_CONSTRAINT' ||
|
|
706
|
+
code === 19 ||
|
|
707
|
+
message.includes('FOREIGN KEY constraint failed'));
|
|
708
|
+
}
|
|
709
|
+
readErrorCode(error) {
|
|
710
|
+
if (typeof error !== 'object' || error === null || !('code' in error)) {
|
|
711
|
+
return undefined;
|
|
712
|
+
}
|
|
713
|
+
const code = error.code;
|
|
714
|
+
if (typeof code === 'string' || typeof code === 'number') {
|
|
715
|
+
return code;
|
|
716
|
+
}
|
|
717
|
+
return undefined;
|
|
718
|
+
}
|
|
719
|
+
readErrorMessage(error) {
|
|
720
|
+
if (typeof error !== 'object' || error === null || !('message' in error)) {
|
|
721
|
+
return '';
|
|
722
|
+
}
|
|
723
|
+
const message = error.message;
|
|
724
|
+
return typeof message === 'string' ? message : '';
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
exports.SkillsService = SkillsService;
|
|
728
|
+
exports.SkillsService = SkillsService = __decorate([
|
|
729
|
+
(0, common_1.Injectable)(),
|
|
730
|
+
__param(0, (0, common_1.Inject)(db_provider_1.DB_CONNECTION)),
|
|
731
|
+
__metadata("design:paramtypes", [Function, settings_service_1.SettingsService,
|
|
732
|
+
skill_source_registry_service_1.SkillSourceRegistryService])
|
|
733
|
+
], SkillsService);
|
|
734
|
+
//# sourceMappingURL=skills.service.js.map
|