bf-skills 1.0.1 → 1.1.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.
Files changed (2) hide show
  1. package/dist/cli.mjs +76 -57
  2. package/package.json +2 -2
package/dist/cli.mjs CHANGED
@@ -41,6 +41,17 @@ function readFrontmatter(filePath) {
41
41
  return {};
42
42
  }
43
43
  }
44
+ function shortTagline(raw) {
45
+ if (!raw) return "";
46
+ const flat = raw.replace(/\s+/g, " ").trim();
47
+ const periodIdx = flat.indexOf(". ");
48
+ const first = periodIdx > 0 ? flat.slice(0, periodIdx) : flat;
49
+ const text = first.replace(/^This\s+skill\s+should\s+be\s+used\s+when\s+the\s+user\s+(asks?\s+(to|about|for)\s+)?/i, "").replace(/^(Complete\s+(guide\s+for|reference\s+for|skill\s+for)?|Comprehensive\s+|Full-?(stack\s+|spectrum\s+|cycle\s+)?|Unified\s+|Expert\s+skill\s+(for|covering)\s+)/i, "").replace(/^(Use\s+(when|for|whenever)|Covers\s+|Trigger(s)?\s*(when|on)|Designed\s+to)\s+/i, "").replace(/\s+skill$/i, "").trim();
50
+ const result = text || first;
51
+ if (result.length <= 60) return result;
52
+ const cut = result.lastIndexOf(" ", 57);
53
+ return (cut > 15 ? result.slice(0, cut) : result.slice(0, 57)) + "\u2026";
54
+ }
44
55
  function walkForSkills(dir, results) {
45
56
  if (!fs.existsSync(dir)) return;
46
57
  const entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -51,7 +62,7 @@ function walkForSkills(dir, results) {
51
62
  if (fs.existsSync(skillMd)) {
52
63
  const fm = readFrontmatter(skillMd);
53
64
  const name = fm["name"] || entry.name;
54
- const description = fm["description"] || "";
65
+ const description = shortTagline(fm["description"] || "");
55
66
  results.push({ name, description, dir: skillDir });
56
67
  }
57
68
  }
@@ -211,7 +222,38 @@ async function runInstall(source, opts) {
211
222
  p.log.error(err.message);
212
223
  process.exit(1);
213
224
  }
214
- p.intro(`${pc.bold("bf-skills")}`);
225
+ p.intro(pc.bold("bf-skills installer"));
226
+ let targetAgents;
227
+ if (opts.agent) {
228
+ if (opts.agent === "all") {
229
+ targetAgents = [...AGENTS];
230
+ } else {
231
+ const found = getAgentById(opts.agent);
232
+ if (!found) {
233
+ p.log.error(`Unknown agent "${opts.agent}". Use: general, claude-code, root, or all`);
234
+ process.exit(1);
235
+ }
236
+ targetAgents = [found];
237
+ }
238
+ } else if (opts.yes) {
239
+ targetAgents = AGENTS.filter((a) => DEFAULT_AGENT_IDS.includes(a.id));
240
+ } else {
241
+ const chosen = await p.multiselect({
242
+ message: "Where do you want to install skills?",
243
+ options: AGENTS.map((a) => ({
244
+ value: a.id,
245
+ label: a.label,
246
+ hint: opts.global ? a.globalPath : `./${a.projectPath}`
247
+ })),
248
+ initialValues: DEFAULT_AGENT_IDS,
249
+ required: true
250
+ });
251
+ if (p.isCancel(chosen)) {
252
+ p.cancel("Cancelled.");
253
+ process.exit(0);
254
+ }
255
+ targetAgents = AGENTS.filter((a) => chosen.includes(a.id));
256
+ }
215
257
  let repoPath;
216
258
  let tmpDir;
217
259
  const isLocal = !repoUrl.startsWith("http") && !repoUrl.startsWith("git@");
@@ -220,14 +262,14 @@ async function runInstall(source, opts) {
220
262
  } else {
221
263
  tmpDir = fs3.mkdtempSync(path4.join(os2.tmpdir(), "bf-skills-"));
222
264
  repoPath = tmpDir;
223
- const spinner3 = p.spinner();
224
- spinner3.start("Cloning repository...");
265
+ const s = p.spinner();
266
+ s.start("Fetching skills catalog...");
225
267
  try {
226
268
  await simpleGit().clone(repoUrl, repoPath, ["--depth", "1"]);
227
- spinner3.stop("Repository cloned");
269
+ s.stop("Catalog ready");
228
270
  } catch (err) {
229
- spinner3.stop("Clone failed");
230
- p.log.error(`Could not clone ${repoUrl}: ${err.message}`);
271
+ s.stop("Failed to fetch catalog");
272
+ p.log.error(`${err.message}`);
231
273
  cleanup(tmpDir);
232
274
  process.exit(1);
233
275
  }
@@ -238,7 +280,6 @@ async function runInstall(source, opts) {
238
280
  cleanup(tmpDir);
239
281
  process.exit(1);
240
282
  }
241
- p.log.info(`Found ${allSkills.length} skill${allSkills.length === 1 ? "" : "s"}`);
242
283
  let selectedSkills;
243
284
  if (opts.skills && opts.skills.length > 0) {
244
285
  selectedSkills = [];
@@ -250,72 +291,50 @@ async function runInstall(source, opts) {
250
291
  process.exit(1);
251
292
  }
252
293
  selectedSkills.push(match);
253
- p.log.step(`Installing ${COLORS.blue}${match.name}${COLORS.reset}`);
254
294
  }
255
295
  } else if (opts.yes) {
256
296
  selectedSkills = allSkills;
257
297
  } else {
258
- const selected = await p.multiselect({
259
- message: "Select skills to install (\u2191\u2193 move Space toggle Enter confirm)",
298
+ const selected = await p.autocompleteMultiselect({
299
+ message: `Pick skills to install (${allSkills.length} available \u2014 type to search)`,
260
300
  options: allSkills.map((s) => ({
261
301
  value: s.name,
262
- label: pc.bold(s.name.padEnd(32)) + pc.dim(s.description)
302
+ label: s.name,
303
+ hint: s.description
263
304
  })),
264
305
  required: true
265
306
  });
266
307
  if (p.isCancel(selected)) {
267
- p.cancel("Installation cancelled.");
308
+ p.cancel("Cancelled.");
268
309
  cleanup(tmpDir);
269
310
  process.exit(0);
270
311
  }
271
312
  selectedSkills = allSkills.filter((s) => selected.includes(s.name));
272
313
  }
273
- let targetAgents;
274
- if (opts.agent) {
275
- if (opts.agent === "all") {
276
- targetAgents = [...AGENTS];
277
- } else {
278
- const found = getAgentById(opts.agent);
279
- if (!found) {
280
- p.log.error(`Unknown agent "${opts.agent}". Use: general, claude-code, root, or all`);
281
- cleanup(tmpDir);
282
- process.exit(1);
314
+ const targetLabels = targetAgents.map((a) => {
315
+ const dest = opts.global ? a.globalPath : `./${a.projectPath}`;
316
+ return `${pc.bold(a.label)} ${pc.dim(dest)}`;
317
+ });
318
+ p.note(
319
+ [
320
+ `${pc.bold("Skills:")} ${selectedSkills.map((s) => pc.cyan(s.name)).join(", ")}`,
321
+ `${pc.bold("Install to:")}`,
322
+ ...targetLabels.map((t) => ` ${pc.dim("\u2192")} ${t}`)
323
+ ].join("\n"),
324
+ "Ready to install"
325
+ );
326
+ await p.tasks(
327
+ selectedSkills.map((skill) => ({
328
+ title: `Installing ${skill.name}`,
329
+ task: async () => {
330
+ const paths = installSkill(skill.dir, skill.name, targetAgents, opts.global);
331
+ return `${skill.name} \u2192 ${paths[0]}`;
283
332
  }
284
- targetAgents = [found];
285
- }
286
- } else if (opts.yes) {
287
- targetAgents = AGENTS.filter((a) => DEFAULT_AGENT_IDS.includes(a.id));
288
- } else {
289
- const chosen = await p.multiselect({
290
- message: "Install to: (\u2191\u2193 move Space toggle Enter confirm)",
291
- options: AGENTS.map((a) => ({
292
- value: a.id,
293
- label: a.label,
294
- hint: opts.global ? a.globalPath : path4.join(process.cwd(), a.projectPath)
295
- })),
296
- initialValues: DEFAULT_AGENT_IDS,
297
- required: true
298
- });
299
- if (p.isCancel(chosen)) {
300
- p.cancel("Installation cancelled.");
301
- cleanup(tmpDir);
302
- process.exit(0);
303
- }
304
- targetAgents = AGENTS.filter((a) => chosen.includes(a.id));
305
- }
306
- const spinner2 = p.spinner();
307
- spinner2.start(`Installing ${selectedSkills.length} skill${selectedSkills.length === 1 ? "" : "s"}...`);
308
- const results = [];
309
- for (const skill of selectedSkills) {
310
- const paths = installSkill(skill.dir, skill.name, targetAgents, opts.global);
311
- results.push(...paths.map((p4, i) => `${skill.name} \u2192 ${p4}${i > 0 ? pc.dim(" (symlink)") : ""}`));
312
- }
313
- spinner2.stop(`Done \u2014 ${selectedSkills.length} skill${selectedSkills.length === 1 ? "" : "s"} installed`);
314
- for (const line of results) {
315
- p.log.step(line);
316
- }
333
+ }))
334
+ );
317
335
  p.outro(
318
- `${pc.dim("$ npx bf-skills list")} ${pc.dim("$ npx bf-skills remove <name>")}`
336
+ `${pc.green("\u2713")} ${selectedSkills.length} skill${selectedSkills.length === 1 ? "" : "s"} installed
337
+ ${pc.dim("$ npx bf-skills list")} ${pc.dim("$ npx bf-skills remove <name>")}`
319
338
  );
320
339
  cleanup(tmpDir);
321
340
  showFooter();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bf-skills",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "BF Skills installer — Building the Future of Business with AI",
5
5
  "type": "module",
6
6
  "engines": {
@@ -15,7 +15,7 @@
15
15
  "prepublishOnly": "npm run build"
16
16
  },
17
17
  "dependencies": {
18
- "@clack/prompts": "^0.9.1",
18
+ "@clack/prompts": "^1.2.0",
19
19
  "picocolors": "^1.1.1",
20
20
  "simple-git": "^3.27.0",
21
21
  "yaml": "^2.7.0"