@lgtm-hq/ai-skills 0.7.0 → 0.8.0
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/data/vendors.json +3 -0
- package/data/vendors.yaml +3 -0
- package/lib/catalog.js +1 -1
- package/lib/install.js +212 -62
- package/package.json +1 -1
package/data/vendors.json
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"id": "mattpocock",
|
|
5
5
|
"repo": "mattpocock/skills",
|
|
6
6
|
"sha": "391a2701dd948f94f56a39f7533f8eea9a859c87",
|
|
7
|
+
"displayRef": "latest",
|
|
7
8
|
"skillRoots": [
|
|
8
9
|
"skills"
|
|
9
10
|
],
|
|
@@ -14,6 +15,7 @@
|
|
|
14
15
|
"id": "anthropics",
|
|
15
16
|
"repo": "anthropics/skills",
|
|
16
17
|
"sha": "9d2f1ae187231d8199c64b5b762e1bdf2244733d",
|
|
18
|
+
"displayRef": "latest",
|
|
17
19
|
"skillRoots": [
|
|
18
20
|
"skills"
|
|
19
21
|
],
|
|
@@ -24,6 +26,7 @@
|
|
|
24
26
|
"id": "anthropics-claude-code",
|
|
25
27
|
"repo": "anthropics/claude-code",
|
|
26
28
|
"sha": "15a21e1b4e240e2da6a4953d5f148a806c9c9bb2",
|
|
29
|
+
"displayRef": "latest",
|
|
27
30
|
"skillRoots": [
|
|
28
31
|
"plugins/*/skills"
|
|
29
32
|
],
|
package/data/vendors.yaml
CHANGED
|
@@ -3,6 +3,7 @@ vendors:
|
|
|
3
3
|
- id: mattpocock
|
|
4
4
|
repo: mattpocock/skills
|
|
5
5
|
sha: "391a2701dd948f94f56a39f7533f8eea9a859c87"
|
|
6
|
+
displayRef: latest
|
|
6
7
|
skillRoots:
|
|
7
8
|
- skills
|
|
8
9
|
license: MIT
|
|
@@ -10,6 +11,7 @@ vendors:
|
|
|
10
11
|
- id: anthropics
|
|
11
12
|
repo: anthropics/skills
|
|
12
13
|
sha: "9d2f1ae187231d8199c64b5b762e1bdf2244733d"
|
|
14
|
+
displayRef: latest
|
|
13
15
|
skillRoots:
|
|
14
16
|
- skills
|
|
15
17
|
license: Apache-2.0
|
|
@@ -17,6 +19,7 @@ vendors:
|
|
|
17
19
|
- id: anthropics-claude-code
|
|
18
20
|
repo: anthropics/claude-code
|
|
19
21
|
sha: "15a21e1b4e240e2da6a4953d5f148a806c9c9bb2"
|
|
22
|
+
displayRef: latest
|
|
20
23
|
skillRoots:
|
|
21
24
|
- plugins/*/skills
|
|
22
25
|
license: Commercial
|
package/lib/catalog.js
CHANGED
|
@@ -27,7 +27,7 @@ export function loadBundles() {
|
|
|
27
27
|
/**
|
|
28
28
|
* Load all vendor records baked into the package.
|
|
29
29
|
*
|
|
30
|
-
* @returns {Promise<{vendors: Array<{id: string, repo: string, sha: string, skillRoots: string[], license: string, homepage: string}>}>} Vendor registry.
|
|
30
|
+
* @returns {Promise<{vendors: Array<{id: string, repo: string, sha: string, displayRef?: string, skillRoots: string[], license: string, homepage: string}>}>} Vendor registry.
|
|
31
31
|
*/
|
|
32
32
|
export function loadVendors() {
|
|
33
33
|
return readCatalog("vendors.json");
|
package/lib/install.js
CHANGED
|
@@ -6,69 +6,111 @@ import { buildSkillsArguments, runSkills } from "./skills-runner.js";
|
|
|
6
6
|
import { createClackUi, KNOWN_AGENTS } from "./ui.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* @typedef {{firstParty: string[], vendors: Record<string, string[]>}} InstallCart
|
|
10
|
+
* Interactive skill cart keyed by catalog source.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {{vendor: string | null, skills: string[]}} InstallBatch
|
|
15
|
+
* One upstream `skills add` invocation (single source).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fill unset install selections through the home/cart wizard.
|
|
10
20
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* overwrite for the gateway fail-closed
|
|
14
|
-
*
|
|
21
|
+
* Home lists catalogs to browse; selections accumulate in a cart. Proceed asks
|
|
22
|
+
* for agents/scope once, then returns install batches. Scope defaults to global,
|
|
23
|
+
* installs symlink (not copy), and uses overwrite for the gateway fail-closed
|
|
24
|
+
* `--on-conflict` API without prompting.
|
|
15
25
|
*
|
|
16
26
|
* @param {{agents: string[], bundle: string | null, copy: boolean, global: boolean, onConflict: string | null, project: boolean, skills: string[], vendor: string | null, yes: boolean}} options - Initial install options.
|
|
17
27
|
* @param {ReturnType<typeof createClackUi>} [ui] - Injectable interactive UI.
|
|
18
|
-
* @returns {Promise<typeof options>}
|
|
28
|
+
* @returns {Promise<typeof options & {installBatches: InstallBatch[]}>} Shared options plus per-source install batches.
|
|
19
29
|
*/
|
|
20
30
|
export async function completeInteractively(options, ui = createClackUi()) {
|
|
21
31
|
ui.intro("ai-skills gateway");
|
|
22
32
|
|
|
33
|
+
// CLI already named a source/skills: honor it instead of discarding into an empty cart.
|
|
34
|
+
if (options.vendor || options.bundle || options.skills.length > 0) {
|
|
35
|
+
const installBatches = await batchesFromCliOptions(options);
|
|
36
|
+
return finishInteractiveInstall(options, ui, installBatches);
|
|
37
|
+
}
|
|
38
|
+
|
|
23
39
|
const bundles = await loadBundles();
|
|
24
40
|
const vendors = await loadVendors();
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
{
|
|
28
|
-
value: "first-party",
|
|
29
|
-
label: `lgtm-hq/ai-skills @ v${packageVersion}`,
|
|
30
|
-
},
|
|
31
|
-
...vendors.vendors.map((vendor) => ({
|
|
32
|
-
value: `vendor:${vendor.id}`,
|
|
33
|
-
label: vendor.repo,
|
|
34
|
-
})),
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
const sourceValue = await cancelable(
|
|
38
|
-
ui,
|
|
39
|
-
ui.select({
|
|
40
|
-
message: "Install from which catalog?",
|
|
41
|
-
options: sourceChoices,
|
|
42
|
-
}),
|
|
43
|
-
);
|
|
41
|
+
/** @type {InstallCart} */
|
|
42
|
+
const cart = { firstParty: [], vendors: {} };
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
options.skills = await cancelable(
|
|
48
|
-
ui,
|
|
49
|
-
ui.groupMultiselect({
|
|
50
|
-
message: "Select skills to install",
|
|
51
|
-
options: skillGroups,
|
|
52
|
-
required: true,
|
|
53
|
-
}),
|
|
54
|
-
);
|
|
55
|
-
} else {
|
|
56
|
-
const vendorId = parseVendorSourceValue(sourceValue);
|
|
57
|
-
const index = await loadVendorIndex(vendorId);
|
|
58
|
-
options.vendor = vendorId;
|
|
59
|
-
options.skills = await cancelable(
|
|
44
|
+
while (true) {
|
|
45
|
+
const action = await cancelable(
|
|
60
46
|
ui,
|
|
61
|
-
ui.
|
|
62
|
-
message:
|
|
63
|
-
options:
|
|
64
|
-
value: skill.name,
|
|
65
|
-
label: skill.name,
|
|
66
|
-
})),
|
|
67
|
-
required: true,
|
|
47
|
+
ui.select({
|
|
48
|
+
message: "Install skills",
|
|
49
|
+
options: buildHomeOptions(cart, vendors),
|
|
68
50
|
}),
|
|
69
51
|
);
|
|
52
|
+
|
|
53
|
+
if (action === "cancel") {
|
|
54
|
+
throw new Error("Install cancelled");
|
|
55
|
+
}
|
|
56
|
+
if (action === "proceed") {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
if (action === "browse:first-party") {
|
|
60
|
+
cart.firstParty = await cancelable(
|
|
61
|
+
ui,
|
|
62
|
+
ui.groupMultiselect({
|
|
63
|
+
message: `Skills from lgtm-hq/ai-skills @ v${getPackageVersion()}`,
|
|
64
|
+
options: buildFirstPartySkillGroups(bundles),
|
|
65
|
+
initialValues: cart.firstParty,
|
|
66
|
+
required: false,
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (action.startsWith("browse:vendor:")) {
|
|
72
|
+
const vendorId = action.slice("browse:vendor:".length);
|
|
73
|
+
const index = await loadVendorIndex(vendorId);
|
|
74
|
+
// Prefer registry record so displayRef survives (baked indexes omit it).
|
|
75
|
+
const vendor = vendors.vendors.find((entry) => entry.id === vendorId) ?? index.vendor;
|
|
76
|
+
cart.vendors[vendorId] = await cancelable(
|
|
77
|
+
ui,
|
|
78
|
+
ui.multiselect({
|
|
79
|
+
message: `Skills from ${vendorDisplayLabel(vendor)}`,
|
|
80
|
+
options: index.skills.map((skill) => ({
|
|
81
|
+
value: skill.name,
|
|
82
|
+
label: skill.name,
|
|
83
|
+
})),
|
|
84
|
+
initialValues: cart.vendors[vendorId] ?? [],
|
|
85
|
+
required: false,
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
throw new Error(`Unknown home action: ${action}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const installBatches = batchesFromCart(cart);
|
|
94
|
+
if (installBatches.length === 0) {
|
|
95
|
+
throw new Error("Install cancelled");
|
|
70
96
|
}
|
|
71
97
|
|
|
98
|
+
return finishInteractiveInstall(options, ui, installBatches);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Ask agents/scope/advanced once, then return shared options plus install batches.
|
|
103
|
+
*
|
|
104
|
+
* @param {{agents: string[], bundle: string | null, copy: boolean, global: boolean, onConflict: string | null, project: boolean, skills: string[], vendor: string | null, yes: boolean}} options - Initial install options.
|
|
105
|
+
* @param {ReturnType<typeof createClackUi>} ui - Interactive UI.
|
|
106
|
+
* @param {InstallBatch[]} installBatches - Per-catalog install batches.
|
|
107
|
+
* @returns {Promise<typeof options & {installBatches: InstallBatch[]}>} Shared options plus batches.
|
|
108
|
+
*/
|
|
109
|
+
async function finishInteractiveInstall(options, ui, installBatches) {
|
|
110
|
+
options.bundle = null;
|
|
111
|
+
options.vendor = null;
|
|
112
|
+
options.skills = [];
|
|
113
|
+
|
|
72
114
|
if (options.agents.length === 0) {
|
|
73
115
|
const selected = await cancelable(
|
|
74
116
|
ui,
|
|
@@ -139,9 +181,110 @@ export async function completeInteractively(options, ui = createClackUi()) {
|
|
|
139
181
|
}
|
|
140
182
|
|
|
141
183
|
ui.outro("Starting install…");
|
|
184
|
+
return { ...options, installBatches };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Build home-screen options for the install wizard.
|
|
189
|
+
*
|
|
190
|
+
* @param {InstallCart} cart - Current skill cart.
|
|
191
|
+
* @param {{vendors: Array<{id: string, repo: string, displayRef?: string}>}} vendors - Vendor registry.
|
|
192
|
+
* @returns {{value: string, label: string}[]} Clack select options.
|
|
193
|
+
*/
|
|
194
|
+
export function buildHomeOptions(cart, vendors) {
|
|
195
|
+
const options = [
|
|
196
|
+
{
|
|
197
|
+
value: "browse:first-party",
|
|
198
|
+
label: `Browse lgtm-hq/ai-skills @ v${getPackageVersion()}`,
|
|
199
|
+
},
|
|
200
|
+
...vendors.vendors.map((vendor) => ({
|
|
201
|
+
value: `browse:vendor:${vendor.id}`,
|
|
202
|
+
label: `Browse ${vendorDisplayLabel(vendor)}`,
|
|
203
|
+
})),
|
|
204
|
+
];
|
|
205
|
+
const total = cartSkillCount(cart);
|
|
206
|
+
if (total > 0) {
|
|
207
|
+
options.push({
|
|
208
|
+
value: "proceed",
|
|
209
|
+
label: `Proceed with install (${total} skill${total === 1 ? "" : "s"})`,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
options.push({ value: "cancel", label: "Cancel" });
|
|
142
213
|
return options;
|
|
143
214
|
}
|
|
144
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Consumer-facing catalog pin for a vendor (never a SHA).
|
|
218
|
+
*
|
|
219
|
+
* @param {{repo: string, displayRef?: string}} vendor - Vendor record.
|
|
220
|
+
* @returns {string} `owner/repo @ pin` label.
|
|
221
|
+
*/
|
|
222
|
+
export function vendorDisplayLabel(vendor) {
|
|
223
|
+
const displayRef = typeof vendor.displayRef === "string" ? vendor.displayRef : undefined;
|
|
224
|
+
const pin = displayRef?.trim() || "latest";
|
|
225
|
+
return `${vendor.repo} @ ${pin}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Count skills currently in the cart.
|
|
230
|
+
*
|
|
231
|
+
* @param {InstallCart} cart - Skill cart.
|
|
232
|
+
* @returns {number} Total selected skills.
|
|
233
|
+
*/
|
|
234
|
+
export function cartSkillCount(cart) {
|
|
235
|
+
return (
|
|
236
|
+
cart.firstParty.length +
|
|
237
|
+
Object.values(cart.vendors).reduce((sum, skills) => sum + skills.length, 0)
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Convert a cart into install batches (first-party first).
|
|
243
|
+
*
|
|
244
|
+
* @param {InstallCart} cart - Skill cart.
|
|
245
|
+
* @returns {InstallBatch[]} Non-empty batches only.
|
|
246
|
+
*/
|
|
247
|
+
export function batchesFromCart(cart) {
|
|
248
|
+
/** @type {InstallBatch[]} */
|
|
249
|
+
const batches = [];
|
|
250
|
+
if (cart.firstParty.length > 0) {
|
|
251
|
+
batches.push({ vendor: null, skills: [...cart.firstParty] });
|
|
252
|
+
}
|
|
253
|
+
for (const [vendorId, skills] of Object.entries(cart.vendors)) {
|
|
254
|
+
if (skills.length > 0) {
|
|
255
|
+
batches.push({ vendor: vendorId, skills: [...skills] });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return batches;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Build install batches from CLI source flags (interactive, without -y).
|
|
263
|
+
*
|
|
264
|
+
* @param {{bundle: string | null, skills: string[], vendor: string | null}} options - Parsed install options.
|
|
265
|
+
* @returns {Promise<InstallBatch[]>} One batch for the CLI-selected source.
|
|
266
|
+
*/
|
|
267
|
+
export async function batchesFromCliOptions(options) {
|
|
268
|
+
if (options.vendor) {
|
|
269
|
+
if (options.skills.length === 0) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
"Interactive install with --vendor requires --skill, or omit both to browse catalogs",
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
return [{ vendor: options.vendor, skills: [...options.skills] }];
|
|
275
|
+
}
|
|
276
|
+
if (options.bundle) {
|
|
277
|
+
const bundles = await loadBundles();
|
|
278
|
+
const bundle = bundles.groups[options.bundle];
|
|
279
|
+
if (!bundle) {
|
|
280
|
+
throw new Error(`Unknown first-party bundle: ${options.bundle}`);
|
|
281
|
+
}
|
|
282
|
+
const skills = options.skills.length > 0 ? options.skills : bundle.skills;
|
|
283
|
+
return [{ vendor: null, skills: [...skills] }];
|
|
284
|
+
}
|
|
285
|
+
return [{ vendor: null, skills: [...options.skills] }];
|
|
286
|
+
}
|
|
287
|
+
|
|
145
288
|
/**
|
|
146
289
|
* Build grouped skill options for first-party interactive install.
|
|
147
290
|
*
|
|
@@ -161,20 +304,6 @@ function buildFirstPartySkillGroups(bundles) {
|
|
|
161
304
|
return groups;
|
|
162
305
|
}
|
|
163
306
|
|
|
164
|
-
/**
|
|
165
|
-
* Parse a vendor catalog selection value.
|
|
166
|
-
*
|
|
167
|
-
* @param {string} value - Combined select value (`vendor:<id>`).
|
|
168
|
-
* @returns {string} Vendor id.
|
|
169
|
-
*/
|
|
170
|
-
function parseVendorSourceValue(value) {
|
|
171
|
-
const prefix = "vendor:";
|
|
172
|
-
if (!value.startsWith(prefix)) {
|
|
173
|
-
throw new Error(`Unknown catalog selection: ${value}`);
|
|
174
|
-
}
|
|
175
|
-
return value.slice(prefix.length);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
307
|
/**
|
|
179
308
|
* Abort when Clack reports cancel (Ctrl+C / escape).
|
|
180
309
|
*
|
|
@@ -311,9 +440,30 @@ async function createLockEntries(options, vendor, now) {
|
|
|
311
440
|
/**
|
|
312
441
|
* Create an interactive installer session.
|
|
313
442
|
*
|
|
443
|
+
* Runs one upstream install per selected catalog so mixed first-party + vendor
|
|
444
|
+
* picks complete in a single interactive session.
|
|
445
|
+
*
|
|
314
446
|
* @param {{agents: string[], bundle: string | null, copy: boolean, global: boolean, onConflict: string | null, project: boolean, skills: string[], vendor: string | null, yes: boolean}} options - Install options.
|
|
315
447
|
* @returns {Promise<void>} Resolves when installation completes.
|
|
316
448
|
*/
|
|
317
449
|
export async function installInteractively(options) {
|
|
318
|
-
|
|
450
|
+
const completed = await completeInteractively(options);
|
|
451
|
+
const finished = [];
|
|
452
|
+
for (const batch of completed.installBatches) {
|
|
453
|
+
try {
|
|
454
|
+
await install({
|
|
455
|
+
...completed,
|
|
456
|
+
bundle: null,
|
|
457
|
+
vendor: batch.vendor,
|
|
458
|
+
skills: batch.skills,
|
|
459
|
+
});
|
|
460
|
+
finished.push(batch.vendor ?? "first-party");
|
|
461
|
+
} catch (error) {
|
|
462
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
463
|
+
throw new Error(
|
|
464
|
+
`Install failed for "${batch.vendor ?? "first-party"}" after completing: ` +
|
|
465
|
+
`${finished.length > 0 ? finished.join(", ") : "none"}. ${detail}`,
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
319
469
|
}
|
package/package.json
CHANGED