@skillshub-labs/cli 0.1.17
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/LICENSE +21 -0
- package/README.md +190 -0
- package/bin/skills-hub +1611 -0
- package/lib/core/kit-core.d.ts +56 -0
- package/lib/core/kit-core.mjs +626 -0
- package/lib/core/kit-types.ts +59 -0
- package/lib/core/provider-core.d.ts +134 -0
- package/lib/core/provider-core.mjs +1223 -0
- package/lib/services/kit-service.d.ts +66 -0
- package/lib/services/kit-service.mjs +266 -0
- package/lib/services/provider-service.d.ts +63 -0
- package/lib/services/provider-service.mjs +183 -0
- package/package.json +94 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { KitLoadoutRecord, KitPolicyRecord, KitRecord } from './kit-types'
|
|
2
|
+
|
|
3
|
+
export function ensureDb(): unknown
|
|
4
|
+
export function getDbPath(): string
|
|
5
|
+
|
|
6
|
+
export function listKitPolicies(): KitPolicyRecord[]
|
|
7
|
+
export function getKitPolicyById(id: string): KitPolicyRecord | null
|
|
8
|
+
export function addKitPolicy(input: {
|
|
9
|
+
name: string
|
|
10
|
+
description?: string
|
|
11
|
+
content: string
|
|
12
|
+
}): KitPolicyRecord
|
|
13
|
+
export function updateKitPolicy(input: {
|
|
14
|
+
id: string
|
|
15
|
+
name?: string
|
|
16
|
+
description?: string
|
|
17
|
+
content?: string
|
|
18
|
+
}): KitPolicyRecord
|
|
19
|
+
export function deleteKitPolicy(id: string): boolean
|
|
20
|
+
|
|
21
|
+
export function listKitLoadouts(): KitLoadoutRecord[]
|
|
22
|
+
export function getKitLoadoutById(id: string): KitLoadoutRecord | null
|
|
23
|
+
export function addKitLoadout(input: {
|
|
24
|
+
name: string
|
|
25
|
+
description?: string
|
|
26
|
+
items: Array<{ skillPath: string; mode?: 'copy' | 'link'; sortOrder?: number }>
|
|
27
|
+
}): KitLoadoutRecord
|
|
28
|
+
export function updateKitLoadout(input: {
|
|
29
|
+
id: string
|
|
30
|
+
name?: string
|
|
31
|
+
description?: string
|
|
32
|
+
items?: Array<{ skillPath: string; mode?: 'copy' | 'link'; sortOrder?: number }>
|
|
33
|
+
}): KitLoadoutRecord
|
|
34
|
+
export function deleteKitLoadout(id: string): boolean
|
|
35
|
+
|
|
36
|
+
export function listKits(): KitRecord[]
|
|
37
|
+
export function getKitById(id: string): KitRecord | null
|
|
38
|
+
export function addKit(input: {
|
|
39
|
+
name: string
|
|
40
|
+
description?: string
|
|
41
|
+
policyId: string
|
|
42
|
+
loadoutId: string
|
|
43
|
+
}): KitRecord
|
|
44
|
+
export function updateKit(input: {
|
|
45
|
+
id: string
|
|
46
|
+
name?: string
|
|
47
|
+
description?: string
|
|
48
|
+
policyId?: string
|
|
49
|
+
loadoutId?: string
|
|
50
|
+
}): KitRecord
|
|
51
|
+
export function deleteKit(id: string): boolean
|
|
52
|
+
export function markKitApplied(input: {
|
|
53
|
+
id: string
|
|
54
|
+
projectPath: string
|
|
55
|
+
agentName: string
|
|
56
|
+
}): KitRecord
|
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import os from 'os'
|
|
4
|
+
import crypto from 'crypto'
|
|
5
|
+
import Database from 'better-sqlite3'
|
|
6
|
+
|
|
7
|
+
const DB_DIR = path.join(os.homedir(), '.skills-hub')
|
|
8
|
+
const DB_PATH = path.join(DB_DIR, 'skills-hub.db')
|
|
9
|
+
|
|
10
|
+
let dbInstance = null
|
|
11
|
+
|
|
12
|
+
function ensureDb() {
|
|
13
|
+
if (dbInstance) return dbInstance
|
|
14
|
+
|
|
15
|
+
fs.mkdirSync(DB_DIR, { recursive: true })
|
|
16
|
+
dbInstance = new Database(DB_PATH)
|
|
17
|
+
dbInstance.pragma('journal_mode = WAL')
|
|
18
|
+
dbInstance.exec(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS kit_policies (
|
|
20
|
+
id TEXT PRIMARY KEY,
|
|
21
|
+
name TEXT NOT NULL,
|
|
22
|
+
description TEXT,
|
|
23
|
+
content TEXT NOT NULL,
|
|
24
|
+
created_at INTEGER NOT NULL,
|
|
25
|
+
updated_at INTEGER NOT NULL
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
CREATE TABLE IF NOT EXISTS kit_loadouts (
|
|
29
|
+
id TEXT PRIMARY KEY,
|
|
30
|
+
name TEXT NOT NULL,
|
|
31
|
+
description TEXT,
|
|
32
|
+
created_at INTEGER NOT NULL,
|
|
33
|
+
updated_at INTEGER NOT NULL
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
CREATE TABLE IF NOT EXISTS kit_loadout_items (
|
|
37
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
38
|
+
loadout_id TEXT NOT NULL,
|
|
39
|
+
skill_path TEXT NOT NULL,
|
|
40
|
+
mode TEXT NOT NULL,
|
|
41
|
+
sort_order INTEGER NOT NULL DEFAULT 0
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_kit_loadout_items_loadout_id
|
|
45
|
+
ON kit_loadout_items(loadout_id);
|
|
46
|
+
|
|
47
|
+
CREATE TABLE IF NOT EXISTS kit_presets (
|
|
48
|
+
id TEXT PRIMARY KEY,
|
|
49
|
+
name TEXT NOT NULL,
|
|
50
|
+
description TEXT,
|
|
51
|
+
policy_id TEXT NOT NULL,
|
|
52
|
+
loadout_id TEXT NOT NULL,
|
|
53
|
+
last_applied_at INTEGER,
|
|
54
|
+
last_applied_target_json TEXT,
|
|
55
|
+
created_at INTEGER NOT NULL,
|
|
56
|
+
updated_at INTEGER NOT NULL
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_kit_presets_policy_id
|
|
60
|
+
ON kit_presets(policy_id);
|
|
61
|
+
|
|
62
|
+
CREATE INDEX IF NOT EXISTS idx_kit_presets_loadout_id
|
|
63
|
+
ON kit_presets(loadout_id);
|
|
64
|
+
`)
|
|
65
|
+
|
|
66
|
+
return dbInstance
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getDbPath() {
|
|
70
|
+
return DB_PATH
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function nowTs() {
|
|
74
|
+
return Date.now()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function createId() {
|
|
78
|
+
if (typeof crypto.randomUUID === 'function') {
|
|
79
|
+
return crypto.randomUUID()
|
|
80
|
+
}
|
|
81
|
+
return `kit-${crypto.randomBytes(16).toString('hex')}`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseJsonSafe(raw, fallback) {
|
|
85
|
+
try {
|
|
86
|
+
return JSON.parse(raw)
|
|
87
|
+
} catch {
|
|
88
|
+
return fallback
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function requireName(value, fieldName = 'name') {
|
|
93
|
+
const normalized = String(value || '').trim()
|
|
94
|
+
if (!normalized) {
|
|
95
|
+
throw new Error(`Kit ${fieldName} is required`)
|
|
96
|
+
}
|
|
97
|
+
return normalized
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function toOptionalText(value) {
|
|
101
|
+
const normalized = String(value || '').trim()
|
|
102
|
+
return normalized || undefined
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function normalizeLoadoutItems(items) {
|
|
106
|
+
if (!Array.isArray(items)) {
|
|
107
|
+
throw new Error('Skills package items must be an array')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const seen = new Set()
|
|
111
|
+
const normalized = []
|
|
112
|
+
|
|
113
|
+
for (const [index, item] of items.entries()) {
|
|
114
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const skillPath = String(item.skillPath || '').trim()
|
|
119
|
+
if (!skillPath || seen.has(skillPath)) {
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const mode = item.mode === 'link' ? 'link' : 'copy'
|
|
124
|
+
const sortOrder = Number.isInteger(item.sortOrder) ? item.sortOrder : index
|
|
125
|
+
|
|
126
|
+
normalized.push({ skillPath, mode, sortOrder })
|
|
127
|
+
seen.add(skillPath)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return normalized
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function parsePolicyRow(row) {
|
|
134
|
+
if (!row) return null
|
|
135
|
+
return {
|
|
136
|
+
id: row.id,
|
|
137
|
+
name: row.name,
|
|
138
|
+
description: row.description || undefined,
|
|
139
|
+
content: row.content,
|
|
140
|
+
createdAt: row.created_at,
|
|
141
|
+
updatedAt: row.updated_at,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function parseLoadoutItems(rows) {
|
|
146
|
+
return rows.map((row) => ({
|
|
147
|
+
skillPath: row.skill_path,
|
|
148
|
+
mode: row.mode === 'link' ? 'link' : 'copy',
|
|
149
|
+
sortOrder: row.sort_order,
|
|
150
|
+
}))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function parseLoadoutRow(row, items) {
|
|
154
|
+
if (!row) return null
|
|
155
|
+
return {
|
|
156
|
+
id: row.id,
|
|
157
|
+
name: row.name,
|
|
158
|
+
description: row.description || undefined,
|
|
159
|
+
items,
|
|
160
|
+
createdAt: row.created_at,
|
|
161
|
+
updatedAt: row.updated_at,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function parseKitRow(row) {
|
|
166
|
+
if (!row) return null
|
|
167
|
+
return {
|
|
168
|
+
id: row.id,
|
|
169
|
+
name: row.name,
|
|
170
|
+
description: row.description || undefined,
|
|
171
|
+
policyId: row.policy_id,
|
|
172
|
+
loadoutId: row.loadout_id,
|
|
173
|
+
lastAppliedAt: row.last_applied_at || undefined,
|
|
174
|
+
lastAppliedTarget: row.last_applied_target_json
|
|
175
|
+
? parseJsonSafe(row.last_applied_target_json, undefined)
|
|
176
|
+
: undefined,
|
|
177
|
+
createdAt: row.created_at,
|
|
178
|
+
updatedAt: row.updated_at,
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function listKitPolicies() {
|
|
183
|
+
const db = ensureDb()
|
|
184
|
+
const rows = db
|
|
185
|
+
.prepare(
|
|
186
|
+
`SELECT id, name, description, content, created_at, updated_at
|
|
187
|
+
FROM kit_policies
|
|
188
|
+
ORDER BY updated_at DESC, name ASC`
|
|
189
|
+
)
|
|
190
|
+
.all()
|
|
191
|
+
return rows.map(parsePolicyRow)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function getKitPolicyById(id) {
|
|
195
|
+
if (!id) return null
|
|
196
|
+
const db = ensureDb()
|
|
197
|
+
const row = db
|
|
198
|
+
.prepare(
|
|
199
|
+
`SELECT id, name, description, content, created_at, updated_at
|
|
200
|
+
FROM kit_policies
|
|
201
|
+
WHERE id = ?`
|
|
202
|
+
)
|
|
203
|
+
.get(id)
|
|
204
|
+
return parsePolicyRow(row)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function addKitPolicy(input) {
|
|
208
|
+
const db = ensureDb()
|
|
209
|
+
const id = createId()
|
|
210
|
+
const ts = nowTs()
|
|
211
|
+
|
|
212
|
+
const name = requireName(input?.name, 'AGENTS.md name')
|
|
213
|
+
const content = String(input?.content || '').trim()
|
|
214
|
+
if (!content) {
|
|
215
|
+
throw new Error('AGENTS.md content is required')
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
db.prepare(
|
|
219
|
+
`INSERT INTO kit_policies (id, name, description, content, created_at, updated_at)
|
|
220
|
+
VALUES (@id, @name, @description, @content, @createdAt, @updatedAt)`
|
|
221
|
+
).run({
|
|
222
|
+
id,
|
|
223
|
+
name,
|
|
224
|
+
description: toOptionalText(input?.description) || null,
|
|
225
|
+
content,
|
|
226
|
+
createdAt: ts,
|
|
227
|
+
updatedAt: ts,
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
return getKitPolicyById(id)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function updateKitPolicy(input) {
|
|
234
|
+
const id = String(input?.id || '').trim()
|
|
235
|
+
if (!id) {
|
|
236
|
+
throw new Error('AGENTS.md id is required')
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const existing = getKitPolicyById(id)
|
|
240
|
+
if (!existing) {
|
|
241
|
+
throw new Error(`AGENTS.md not found: ${id}`)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const nextName =
|
|
245
|
+
input?.name === undefined ? existing.name : requireName(input.name, 'AGENTS.md name')
|
|
246
|
+
const nextDescription =
|
|
247
|
+
input?.description === undefined ? existing.description || null : toOptionalText(input.description) || null
|
|
248
|
+
const nextContent = input?.content === undefined ? existing.content : String(input.content || '').trim()
|
|
249
|
+
|
|
250
|
+
if (!nextContent) {
|
|
251
|
+
throw new Error('AGENTS.md content is required')
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const db = ensureDb()
|
|
255
|
+
db.prepare(
|
|
256
|
+
`UPDATE kit_policies
|
|
257
|
+
SET name = @name,
|
|
258
|
+
description = @description,
|
|
259
|
+
content = @content,
|
|
260
|
+
updated_at = @updatedAt
|
|
261
|
+
WHERE id = @id`
|
|
262
|
+
).run({
|
|
263
|
+
id,
|
|
264
|
+
name: nextName,
|
|
265
|
+
description: nextDescription,
|
|
266
|
+
content: nextContent,
|
|
267
|
+
updatedAt: nowTs(),
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
return getKitPolicyById(id)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function deleteKitPolicy(id) {
|
|
274
|
+
if (!id) return false
|
|
275
|
+
const db = ensureDb()
|
|
276
|
+
const usedByKit = db
|
|
277
|
+
.prepare(`SELECT COUNT(1) AS count FROM kit_presets WHERE policy_id = ?`)
|
|
278
|
+
.get(id)
|
|
279
|
+
|
|
280
|
+
if (usedByKit?.count > 0) {
|
|
281
|
+
throw new Error('AGENTS.md is referenced by existing kit presets; remove kits first')
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const result = db.prepare(`DELETE FROM kit_policies WHERE id = ?`).run(id)
|
|
285
|
+
return result.changes > 0
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function listLoadoutItems(loadoutId) {
|
|
289
|
+
const db = ensureDb()
|
|
290
|
+
const rows = db
|
|
291
|
+
.prepare(
|
|
292
|
+
`SELECT skill_path, mode, sort_order
|
|
293
|
+
FROM kit_loadout_items
|
|
294
|
+
WHERE loadout_id = ?
|
|
295
|
+
ORDER BY sort_order ASC, id ASC`
|
|
296
|
+
)
|
|
297
|
+
.all(loadoutId)
|
|
298
|
+
return parseLoadoutItems(rows)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function listKitLoadouts() {
|
|
302
|
+
const db = ensureDb()
|
|
303
|
+
const rows = db
|
|
304
|
+
.prepare(
|
|
305
|
+
`SELECT id, name, description, created_at, updated_at
|
|
306
|
+
FROM kit_loadouts
|
|
307
|
+
ORDER BY updated_at DESC, name ASC`
|
|
308
|
+
)
|
|
309
|
+
.all()
|
|
310
|
+
|
|
311
|
+
return rows.map((row) => parseLoadoutRow(row, listLoadoutItems(row.id)))
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function getKitLoadoutById(id) {
|
|
315
|
+
if (!id) return null
|
|
316
|
+
const db = ensureDb()
|
|
317
|
+
const row = db
|
|
318
|
+
.prepare(
|
|
319
|
+
`SELECT id, name, description, created_at, updated_at
|
|
320
|
+
FROM kit_loadouts
|
|
321
|
+
WHERE id = ?`
|
|
322
|
+
)
|
|
323
|
+
.get(id)
|
|
324
|
+
|
|
325
|
+
if (!row) return null
|
|
326
|
+
return parseLoadoutRow(row, listLoadoutItems(id))
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function addKitLoadout(input) {
|
|
330
|
+
const db = ensureDb()
|
|
331
|
+
const id = createId()
|
|
332
|
+
const ts = nowTs()
|
|
333
|
+
|
|
334
|
+
const name = requireName(input?.name, 'skills package name')
|
|
335
|
+
const items = normalizeLoadoutItems(input?.items || [])
|
|
336
|
+
|
|
337
|
+
db.prepare(
|
|
338
|
+
`INSERT INTO kit_loadouts (id, name, description, created_at, updated_at)
|
|
339
|
+
VALUES (@id, @name, @description, @createdAt, @updatedAt)`
|
|
340
|
+
).run({
|
|
341
|
+
id,
|
|
342
|
+
name,
|
|
343
|
+
description: toOptionalText(input?.description) || null,
|
|
344
|
+
createdAt: ts,
|
|
345
|
+
updatedAt: ts,
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
if (items.length > 0) {
|
|
349
|
+
const insertItem = db.prepare(
|
|
350
|
+
`INSERT INTO kit_loadout_items (loadout_id, skill_path, mode, sort_order)
|
|
351
|
+
VALUES (@loadoutId, @skillPath, @mode, @sortOrder)`
|
|
352
|
+
)
|
|
353
|
+
const tx = db.transaction((inputItems) => {
|
|
354
|
+
for (const item of inputItems) {
|
|
355
|
+
insertItem.run({
|
|
356
|
+
loadoutId: id,
|
|
357
|
+
skillPath: item.skillPath,
|
|
358
|
+
mode: item.mode,
|
|
359
|
+
sortOrder: item.sortOrder,
|
|
360
|
+
})
|
|
361
|
+
}
|
|
362
|
+
})
|
|
363
|
+
tx(items)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return getKitLoadoutById(id)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function updateKitLoadout(input) {
|
|
370
|
+
const id = String(input?.id || '').trim()
|
|
371
|
+
if (!id) {
|
|
372
|
+
throw new Error('Skills package id is required')
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const existing = getKitLoadoutById(id)
|
|
376
|
+
if (!existing) {
|
|
377
|
+
throw new Error(`Skills package not found: ${id}`)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const nextName =
|
|
381
|
+
input?.name === undefined ? existing.name : requireName(input.name, 'skills package name')
|
|
382
|
+
const nextDescription =
|
|
383
|
+
input?.description === undefined
|
|
384
|
+
? existing.description || null
|
|
385
|
+
: toOptionalText(input.description) || null
|
|
386
|
+
const nextItems = input?.items === undefined ? existing.items : normalizeLoadoutItems(input.items)
|
|
387
|
+
|
|
388
|
+
const db = ensureDb()
|
|
389
|
+
const updateLoadout = db.prepare(
|
|
390
|
+
`UPDATE kit_loadouts
|
|
391
|
+
SET name = @name,
|
|
392
|
+
description = @description,
|
|
393
|
+
updated_at = @updatedAt
|
|
394
|
+
WHERE id = @id`
|
|
395
|
+
)
|
|
396
|
+
const deleteItems = db.prepare(`DELETE FROM kit_loadout_items WHERE loadout_id = ?`)
|
|
397
|
+
const insertItem = db.prepare(
|
|
398
|
+
`INSERT INTO kit_loadout_items (loadout_id, skill_path, mode, sort_order)
|
|
399
|
+
VALUES (@loadoutId, @skillPath, @mode, @sortOrder)`
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
const tx = db.transaction(() => {
|
|
403
|
+
updateLoadout.run({
|
|
404
|
+
id,
|
|
405
|
+
name: nextName,
|
|
406
|
+
description: nextDescription,
|
|
407
|
+
updatedAt: nowTs(),
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
deleteItems.run(id)
|
|
411
|
+
|
|
412
|
+
for (const item of nextItems) {
|
|
413
|
+
insertItem.run({
|
|
414
|
+
loadoutId: id,
|
|
415
|
+
skillPath: item.skillPath,
|
|
416
|
+
mode: item.mode,
|
|
417
|
+
sortOrder: item.sortOrder,
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
tx()
|
|
423
|
+
|
|
424
|
+
return getKitLoadoutById(id)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function deleteKitLoadout(id) {
|
|
428
|
+
if (!id) return false
|
|
429
|
+
const db = ensureDb()
|
|
430
|
+
const usedByKit = db
|
|
431
|
+
.prepare(`SELECT COUNT(1) AS count FROM kit_presets WHERE loadout_id = ?`)
|
|
432
|
+
.get(id)
|
|
433
|
+
|
|
434
|
+
if (usedByKit?.count > 0) {
|
|
435
|
+
throw new Error('Skills package is referenced by existing kit presets; remove kits first')
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const tx = db.transaction(() => {
|
|
439
|
+
db.prepare(`DELETE FROM kit_loadout_items WHERE loadout_id = ?`).run(id)
|
|
440
|
+
return db.prepare(`DELETE FROM kit_loadouts WHERE id = ?`).run(id)
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
const result = tx()
|
|
444
|
+
return result.changes > 0
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function listKits() {
|
|
448
|
+
const db = ensureDb()
|
|
449
|
+
const rows = db
|
|
450
|
+
.prepare(
|
|
451
|
+
`SELECT id, name, description, policy_id, loadout_id, last_applied_at, last_applied_target_json,
|
|
452
|
+
created_at, updated_at
|
|
453
|
+
FROM kit_presets
|
|
454
|
+
ORDER BY updated_at DESC, name ASC`
|
|
455
|
+
)
|
|
456
|
+
.all()
|
|
457
|
+
return rows.map(parseKitRow)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function getKitById(id) {
|
|
461
|
+
if (!id) return null
|
|
462
|
+
const db = ensureDb()
|
|
463
|
+
const row = db
|
|
464
|
+
.prepare(
|
|
465
|
+
`SELECT id, name, description, policy_id, loadout_id, last_applied_at, last_applied_target_json,
|
|
466
|
+
created_at, updated_at
|
|
467
|
+
FROM kit_presets
|
|
468
|
+
WHERE id = ?`
|
|
469
|
+
)
|
|
470
|
+
.get(id)
|
|
471
|
+
return parseKitRow(row)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function ensureKitRefsExist(policyId, loadoutId) {
|
|
475
|
+
if (!getKitPolicyById(policyId)) {
|
|
476
|
+
throw new Error(`AGENTS.md not found: ${policyId}`)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (!getKitLoadoutById(loadoutId)) {
|
|
480
|
+
throw new Error(`Skills package not found: ${loadoutId}`)
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function addKit(input) {
|
|
485
|
+
const db = ensureDb()
|
|
486
|
+
const id = createId()
|
|
487
|
+
const ts = nowTs()
|
|
488
|
+
|
|
489
|
+
const name = requireName(input?.name, 'kit name')
|
|
490
|
+
const policyId = String(input?.policyId || '').trim()
|
|
491
|
+
const loadoutId = String(input?.loadoutId || '').trim()
|
|
492
|
+
if (!policyId || !loadoutId) {
|
|
493
|
+
throw new Error('Kit must include both AGENTS.md and Skills package')
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
ensureKitRefsExist(policyId, loadoutId)
|
|
497
|
+
|
|
498
|
+
db.prepare(
|
|
499
|
+
`INSERT INTO kit_presets (
|
|
500
|
+
id, name, description, policy_id, loadout_id, created_at, updated_at
|
|
501
|
+
) VALUES (
|
|
502
|
+
@id, @name, @description, @policyId, @loadoutId, @createdAt, @updatedAt
|
|
503
|
+
)`
|
|
504
|
+
).run({
|
|
505
|
+
id,
|
|
506
|
+
name,
|
|
507
|
+
description: toOptionalText(input?.description) || null,
|
|
508
|
+
policyId,
|
|
509
|
+
loadoutId,
|
|
510
|
+
createdAt: ts,
|
|
511
|
+
updatedAt: ts,
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
return getKitById(id)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function updateKit(input) {
|
|
518
|
+
const id = String(input?.id || '').trim()
|
|
519
|
+
if (!id) {
|
|
520
|
+
throw new Error('Kit id is required')
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const existing = getKitById(id)
|
|
524
|
+
if (!existing) {
|
|
525
|
+
throw new Error(`Kit not found: ${id}`)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const nextName = input?.name === undefined ? existing.name : requireName(input.name, 'kit name')
|
|
529
|
+
const nextDescription =
|
|
530
|
+
input?.description === undefined
|
|
531
|
+
? existing.description || null
|
|
532
|
+
: toOptionalText(input.description) || null
|
|
533
|
+
const nextPolicyId =
|
|
534
|
+
input?.policyId === undefined ? existing.policyId : String(input.policyId || '').trim()
|
|
535
|
+
const nextLoadoutId =
|
|
536
|
+
input?.loadoutId === undefined ? existing.loadoutId : String(input.loadoutId || '').trim()
|
|
537
|
+
|
|
538
|
+
if (!nextPolicyId || !nextLoadoutId) {
|
|
539
|
+
throw new Error('Kit must include both AGENTS.md and Skills package')
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
ensureKitRefsExist(nextPolicyId, nextLoadoutId)
|
|
543
|
+
|
|
544
|
+
const db = ensureDb()
|
|
545
|
+
db.prepare(
|
|
546
|
+
`UPDATE kit_presets
|
|
547
|
+
SET name = @name,
|
|
548
|
+
description = @description,
|
|
549
|
+
policy_id = @policyId,
|
|
550
|
+
loadout_id = @loadoutId,
|
|
551
|
+
updated_at = @updatedAt
|
|
552
|
+
WHERE id = @id`
|
|
553
|
+
).run({
|
|
554
|
+
id,
|
|
555
|
+
name: nextName,
|
|
556
|
+
description: nextDescription,
|
|
557
|
+
policyId: nextPolicyId,
|
|
558
|
+
loadoutId: nextLoadoutId,
|
|
559
|
+
updatedAt: nowTs(),
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
return getKitById(id)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function deleteKit(id) {
|
|
566
|
+
if (!id) return false
|
|
567
|
+
const db = ensureDb()
|
|
568
|
+
const result = db.prepare(`DELETE FROM kit_presets WHERE id = ?`).run(id)
|
|
569
|
+
return result.changes > 0
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function markKitApplied(input) {
|
|
573
|
+
const id = String(input?.id || '').trim()
|
|
574
|
+
if (!id) {
|
|
575
|
+
throw new Error('Kit id is required')
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const existing = getKitById(id)
|
|
579
|
+
if (!existing) {
|
|
580
|
+
throw new Error(`Kit not found: ${id}`)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const projectPath = String(input?.projectPath || '').trim()
|
|
584
|
+
const agentName = String(input?.agentName || '').trim()
|
|
585
|
+
if (!projectPath || !agentName) {
|
|
586
|
+
throw new Error('projectPath and agentName are required to mark kit application')
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const appliedAt = nowTs()
|
|
590
|
+
const db = ensureDb()
|
|
591
|
+
db.prepare(
|
|
592
|
+
`UPDATE kit_presets
|
|
593
|
+
SET last_applied_at = @lastAppliedAt,
|
|
594
|
+
last_applied_target_json = @lastAppliedTargetJson,
|
|
595
|
+
updated_at = @updatedAt
|
|
596
|
+
WHERE id = @id`
|
|
597
|
+
).run({
|
|
598
|
+
id,
|
|
599
|
+
lastAppliedAt: appliedAt,
|
|
600
|
+
lastAppliedTargetJson: JSON.stringify({ projectPath, agentName }),
|
|
601
|
+
updatedAt: appliedAt,
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
return getKitById(id)
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
export {
|
|
608
|
+
ensureDb,
|
|
609
|
+
getDbPath,
|
|
610
|
+
listKitPolicies,
|
|
611
|
+
getKitPolicyById,
|
|
612
|
+
addKitPolicy,
|
|
613
|
+
updateKitPolicy,
|
|
614
|
+
deleteKitPolicy,
|
|
615
|
+
listKitLoadouts,
|
|
616
|
+
getKitLoadoutById,
|
|
617
|
+
addKitLoadout,
|
|
618
|
+
updateKitLoadout,
|
|
619
|
+
deleteKitLoadout,
|
|
620
|
+
listKits,
|
|
621
|
+
getKitById,
|
|
622
|
+
addKit,
|
|
623
|
+
updateKit,
|
|
624
|
+
deleteKit,
|
|
625
|
+
markKitApplied,
|
|
626
|
+
}
|