@lgtm-hq/ai-skills 0.6.0 → 0.7.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/lib/install.js CHANGED
@@ -8,10 +8,10 @@ import { createClackUi, KNOWN_AGENTS } from "./ui.js";
8
8
  /**
9
9
  * Fill unset install selections through the terminal picker.
10
10
  *
11
- * Happy path asks for catalog source (+ vendor skill) and agents. Scope defaults
12
- * to global, installs symlink (not copy), and uses overwrite for the gateway
13
- * fail-closed `--on-conflict` API without prompting — upstream `skills` does
14
- * not implement conflict policies yet.
11
+ * Happy path asks for catalog source, skills (grouped multi-select for first-party),
12
+ * and agents. Scope defaults to global, installs symlink (not copy), and uses
13
+ * overwrite for the gateway fail-closed `--on-conflict` API without prompting —
14
+ * upstream `skills` does not implement conflict policies yet.
15
15
  *
16
16
  * @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
17
  * @param {ReturnType<typeof createClackUi>} [ui] - Injectable interactive UI.
@@ -22,14 +22,15 @@ export async function completeInteractively(options, ui = createClackUi()) {
22
22
 
23
23
  const bundles = await loadBundles();
24
24
  const vendors = await loadVendors();
25
+ const packageVersion = getPackageVersion();
25
26
  const sourceChoices = [
26
- ...Object.entries(bundles.groups).map(([id, bundle]) => ({
27
- value: `bundle:${id}`,
28
- label: `${bundle.name} — ${bundle.description}`,
29
- })),
27
+ {
28
+ value: "first-party",
29
+ label: `lgtm-hq/ai-skills @ v${packageVersion}`,
30
+ },
30
31
  ...vendors.vendors.map((vendor) => ({
31
32
  value: `vendor:${vendor.id}`,
32
- label: `${vendor.repo} @ ${vendor.sha.slice(0, 12)}`,
33
+ label: vendor.repo,
33
34
  })),
34
35
  ];
35
36
 
@@ -40,26 +41,32 @@ export async function completeInteractively(options, ui = createClackUi()) {
40
41
  options: sourceChoices,
41
42
  }),
42
43
  );
43
- const [sourceKind, sourceId] = splitSourceValue(sourceValue);
44
44
 
45
- if (sourceKind === "bundle") {
46
- const bundle = bundles.groups[sourceId];
47
- options.bundle = sourceId;
48
- options.skills = bundle.skills;
45
+ if (sourceValue === "first-party") {
46
+ const skillGroups = buildFirstPartySkillGroups(bundles);
47
+ options.skills = await cancelable(
48
+ ui,
49
+ ui.groupMultiselect({
50
+ message: "Select skills to install",
51
+ options: skillGroups,
52
+ required: true,
53
+ }),
54
+ );
49
55
  } else {
50
- const index = await loadVendorIndex(sourceId);
51
- const skillName = await cancelable(
56
+ const vendorId = parseVendorSourceValue(sourceValue);
57
+ const index = await loadVendorIndex(vendorId);
58
+ options.vendor = vendorId;
59
+ options.skills = await cancelable(
52
60
  ui,
53
- ui.select({
54
- message: `Skill from ${index.vendor.repo}`,
61
+ ui.multiselect({
62
+ message: `Select skills from ${index.vendor.repo}`,
55
63
  options: index.skills.map((skill) => ({
56
64
  value: skill.name,
57
65
  label: skill.name,
58
66
  })),
67
+ required: true,
59
68
  }),
60
69
  );
61
- options.vendor = sourceId;
62
- options.skills = [skillName];
63
70
  }
64
71
 
65
72
  if (options.agents.length === 0) {
@@ -136,14 +143,36 @@ export async function completeInteractively(options, ui = createClackUi()) {
136
143
  }
137
144
 
138
145
  /**
139
- * Split a `kind:id` catalog selection value.
146
+ * Build grouped skill options for first-party interactive install.
140
147
  *
141
- * @param {string} value - Combined select value.
142
- * @returns {[string, string]} Kind and id.
148
+ * @param {{groups: Record<string, {name: string, skills: string[]}>, ungrouped: string[]}} bundles - Loaded bundle catalog.
149
+ * @returns {Record<string, {value: string, label: string}[]>} Clack groupMultiselect options.
143
150
  */
144
- function splitSourceValue(value) {
145
- const separator = value.indexOf(":");
146
- return [value.slice(0, separator), value.slice(separator + 1)];
151
+ function buildFirstPartySkillGroups(bundles) {
152
+ const groups = Object.fromEntries(
153
+ Object.values(bundles.groups).map((bundle) => [
154
+ bundle.name,
155
+ bundle.skills.map((skill) => ({ value: skill, label: skill })),
156
+ ]),
157
+ );
158
+ if (bundles.ungrouped.length > 0) {
159
+ groups.Other = bundles.ungrouped.map((skill) => ({ value: skill, label: skill }));
160
+ }
161
+ return groups;
162
+ }
163
+
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);
147
176
  }
148
177
 
149
178
  /**
package/lib/ui.js CHANGED
@@ -20,6 +20,7 @@ export const KNOWN_AGENTS = [
20
20
  * note: (message: string, title?: string) => void,
21
21
  * select: (options: {message: string, options: {value: string, label: string}[], initialValue?: string}) => Promise<string | symbol>,
22
22
  * multiselect: (options: {message: string, options: {value: string, label: string}[], initialValues?: string[], required?: boolean}) => Promise<string[] | symbol>,
23
+ * groupMultiselect: (options: {message: string, options: Record<string, {value: string, label: string}[]>, initialValues?: string[], required?: boolean}) => Promise<string[] | symbol>,
23
24
  * confirm: (options: {message: string, initialValue?: boolean}) => Promise<boolean | symbol>,
24
25
  * isCancel: (value: unknown) => boolean,
25
26
  * }} Interactive UI adapter.
@@ -31,6 +32,7 @@ export function createClackUi() {
31
32
  note: clack.note,
32
33
  select: clack.select,
33
34
  multiselect: clack.multiselect,
35
+ groupMultiselect: clack.groupMultiselect,
34
36
  confirm: clack.confirm,
35
37
  isCancel: clack.isCancel,
36
38
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lgtm-hq/ai-skills",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Pinned installer gateway for first-party and vendor AI skills.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/lgtm-hq/ai-skills#readme",