bf-skills 1.0.0 → 1.0.2

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 +108 -82
  2. package/package.json +1 -1
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
  }
@@ -80,6 +91,7 @@ function copyDir(src, dest) {
80
91
  fs2.mkdirSync(dest, { recursive: true });
81
92
  const entries = fs2.readdirSync(src, { withFileTypes: true });
82
93
  for (const entry of entries) {
94
+ if (entry.name === ".git" || entry.name === ".DS_Store") continue;
83
95
  const srcPath = path2.join(src, entry.name);
84
96
  const destPath = path2.join(dest, entry.name);
85
97
  if (entry.isDirectory()) {
@@ -89,46 +101,70 @@ function copyDir(src, dest) {
89
101
  }
90
102
  }
91
103
  }
92
- function installSkill(skill, agent, global) {
93
- const basePath = global ? agent.globalPath : path2.join(process.cwd(), agent.projectPath);
94
- const destDir = path2.join(basePath, skill.name);
95
- fs2.mkdirSync(basePath, { recursive: true });
96
- if (fs2.existsSync(destDir)) {
97
- fs2.rmSync(destDir, { recursive: true, force: true });
104
+ function removePath(p4) {
105
+ if (fs2.existsSync(p4)) {
106
+ fs2.rmSync(p4, { recursive: true, force: true });
107
+ }
108
+ }
109
+ function installSkill(skillDir, skillName, agents, isGlobal) {
110
+ if (agents.length === 0) throw new Error("No install targets selected");
111
+ const resolvePath = (agent) => isGlobal ? path2.join(agent.globalPath, skillName) : path2.join(process.cwd(), agent.projectPath, skillName);
112
+ const primaryDest = resolvePath(agents[0]);
113
+ fs2.mkdirSync(path2.dirname(primaryDest), { recursive: true });
114
+ removePath(primaryDest);
115
+ copyDir(skillDir, primaryDest);
116
+ const installed = [primaryDest];
117
+ for (const agent of agents.slice(1)) {
118
+ const dest = resolvePath(agent);
119
+ fs2.mkdirSync(path2.dirname(dest), { recursive: true });
120
+ removePath(dest);
121
+ const rel = path2.relative(path2.dirname(dest), primaryDest);
122
+ fs2.symlinkSync(rel, dest);
123
+ installed.push(dest);
98
124
  }
99
- copyDir(skill.dir, destDir);
100
- return destDir;
125
+ return installed;
101
126
  }
102
- function removeSkill(name, agent, global) {
103
- const basePath = global ? agent.globalPath : path2.join(process.cwd(), agent.projectPath);
104
- const destDir = path2.join(basePath, name);
105
- if (!fs2.existsSync(destDir)) return false;
106
- fs2.rmSync(destDir, { recursive: true, force: true });
107
- return true;
127
+ function removeSkill(name, agents, isGlobal) {
128
+ const removed = [];
129
+ for (const agent of agents) {
130
+ const p4 = isGlobal ? path2.join(agent.globalPath, name) : path2.join(process.cwd(), agent.projectPath, name);
131
+ if (fs2.existsSync(p4)) {
132
+ fs2.rmSync(p4, { recursive: true, force: true });
133
+ removed.push(p4);
134
+ }
135
+ }
136
+ return removed;
108
137
  }
109
- function listInstalledSkills(agent, global) {
110
- const basePath = global ? agent.globalPath : path2.join(process.cwd(), agent.projectPath);
138
+ function listInstalledSkills(agent, isGlobal) {
139
+ const basePath = isGlobal ? agent.globalPath : path2.join(process.cwd(), agent.projectPath);
111
140
  if (!fs2.existsSync(basePath)) return [];
112
- return fs2.readdirSync(basePath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
141
+ return fs2.readdirSync(basePath, { withFileTypes: true }).filter((e) => e.isDirectory() || e.isSymbolicLink()).map((e) => e.name);
113
142
  }
114
143
 
115
144
  // src/lib/agents.ts
116
145
  import os from "os";
117
146
  import path3 from "path";
118
147
  var AGENTS = [
148
+ {
149
+ id: "general",
150
+ label: ".agents/skills (general agents)",
151
+ projectPath: ".agents/skills",
152
+ globalPath: path3.join(os.homedir(), ".agents", "skills")
153
+ },
119
154
  {
120
155
  id: "claude-code",
121
- label: "Claude Code",
156
+ label: ".claude/skills (Claude Code)",
122
157
  projectPath: ".claude/skills",
123
158
  globalPath: path3.join(os.homedir(), ".claude", "skills")
124
159
  },
125
160
  {
126
- id: "general",
127
- label: "General agents",
128
- projectPath: ".agents/skills",
129
- globalPath: path3.join(os.homedir(), ".agents", "skills")
161
+ id: "root",
162
+ label: "skills/ (root folder)",
163
+ projectPath: "skills",
164
+ globalPath: path3.join(os.homedir(), "skills")
130
165
  }
131
166
  ];
167
+ var DEFAULT_AGENT_IDS = ["general", "claude-code"];
132
168
  function getAgentById(id) {
133
169
  return AGENTS.find((a) => a.id === id);
134
170
  }
@@ -187,6 +223,37 @@ async function runInstall(source, opts) {
187
223
  process.exit(1);
188
224
  }
189
225
  p.intro(`${pc.bold("bf-skills")}`);
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: "Install to: (\u2191\u2193 move Space toggle Enter confirm)",
243
+ options: AGENTS.map((a) => ({
244
+ value: a.id,
245
+ label: a.label,
246
+ hint: opts.global ? a.globalPath : path4.join(process.cwd(), a.projectPath)
247
+ })),
248
+ initialValues: DEFAULT_AGENT_IDS,
249
+ required: true
250
+ });
251
+ if (p.isCancel(chosen)) {
252
+ p.cancel("Installation cancelled.");
253
+ process.exit(0);
254
+ }
255
+ targetAgents = AGENTS.filter((a) => chosen.includes(a.id));
256
+ }
190
257
  let repoPath;
191
258
  let tmpDir;
192
259
  const isLocal = !repoUrl.startsWith("http") && !repoUrl.startsWith("git@");
@@ -213,7 +280,6 @@ async function runInstall(source, opts) {
213
280
  cleanup(tmpDir);
214
281
  process.exit(1);
215
282
  }
216
- p.log.info(`Found ${allSkills.length} skill${allSkills.length === 1 ? "" : "s"}`);
217
283
  let selectedSkills;
218
284
  if (opts.skills && opts.skills.length > 0) {
219
285
  selectedSkills = [];
@@ -225,17 +291,15 @@ async function runInstall(source, opts) {
225
291
  process.exit(1);
226
292
  }
227
293
  selectedSkills.push(match);
228
- p.log.step(`Installing ${COLORS.blue}${match.name}${COLORS.reset}`);
229
294
  }
230
295
  } else if (opts.yes) {
231
296
  selectedSkills = allSkills;
232
297
  } else {
233
298
  const selected = await p.multiselect({
234
- message: "Select skills to install (space to toggle, enter to confirm)",
299
+ message: `Select skills (${allSkills.length} available)`,
235
300
  options: allSkills.map((s) => ({
236
301
  value: s.name,
237
- label: pc.bold(s.name.padEnd(30)) + pc.dim(s.description),
238
- hint: s.description
302
+ label: pc.bold(s.name.padEnd(28)) + pc.dim(s.description)
239
303
  })),
240
304
  required: true
241
305
  });
@@ -246,53 +310,20 @@ async function runInstall(source, opts) {
246
310
  }
247
311
  selectedSkills = allSkills.filter((s) => selected.includes(s.name));
248
312
  }
249
- let targetAgents;
250
- if (opts.agent) {
251
- if (opts.agent === "both") {
252
- targetAgents = [...AGENTS];
253
- } else {
254
- const found = getAgentById(opts.agent);
255
- if (!found) {
256
- p.log.error(`Unknown agent "${opts.agent}". Use: claude-code, general, or both`);
257
- cleanup(tmpDir);
258
- process.exit(1);
259
- }
260
- targetAgents = [found];
261
- }
262
- } else if (opts.yes) {
263
- targetAgents = [AGENTS[0]];
264
- } else {
265
- const agentChoice = await p.select({
266
- message: "Install to:",
267
- options: [
268
- { value: "claude-code", label: `${pc.bold("Claude Code")} ${pc.dim("(~/.claude/skills/)")}` },
269
- { value: "general", label: `${pc.bold("General agents")} ${pc.dim("(~/.agents/skills/)")}` },
270
- { value: "both", label: `${pc.bold("Both")}` }
271
- ]
272
- });
273
- if (p.isCancel(agentChoice)) {
274
- p.cancel("Installation cancelled.");
275
- cleanup(tmpDir);
276
- process.exit(0);
277
- }
278
- targetAgents = agentChoice === "both" ? [...AGENTS] : [getAgentById(agentChoice)];
279
- }
280
313
  const spinner2 = p.spinner();
281
314
  spinner2.start(`Installing ${selectedSkills.length} skill${selectedSkills.length === 1 ? "" : "s"}...`);
282
- const installed = [];
315
+ const results = [];
283
316
  for (const skill of selectedSkills) {
284
- for (const agent of targetAgents) {
285
- const dest = installSkill(skill, agent, opts.global);
286
- installed.push(`${skill.name} \u2192 ${dest}`);
287
- }
317
+ const paths = installSkill(skill.dir, skill.name, targetAgents, opts.global);
318
+ results.push(...paths.map(
319
+ (dest, i) => `${pc.bold(skill.name)} \u2192 ${pc.dim(dest)}${i > 0 ? pc.dim(" (symlink)") : ""}`
320
+ ));
288
321
  }
289
322
  spinner2.stop(`Done \u2014 ${selectedSkills.length} skill${selectedSkills.length === 1 ? "" : "s"} installed`);
290
- for (const line of installed) {
323
+ for (const line of results) {
291
324
  p.log.step(line);
292
325
  }
293
- p.outro(
294
- `${pc.dim("$ npx bf-skills list")} ${pc.dim("$ npx bf-skills remove <name>")}`
295
- );
326
+ p.outro(`${pc.dim("$ npx bf-skills list")} ${pc.dim("$ npx bf-skills remove <name>")}`);
296
327
  cleanup(tmpDir);
297
328
  showFooter();
298
329
  }
@@ -311,11 +342,11 @@ function runList(opts) {
311
342
  let total = 0;
312
343
  for (const agent of AGENTS) {
313
344
  const skills = listInstalledSkills(agent, opts.global);
314
- const pathLabel = opts.global ? agent.globalPath : `./${agent.projectPath}`;
345
+ const basePath = opts.global ? agent.globalPath : `./${agent.projectPath}`;
315
346
  if (skills.length === 0) {
316
- p2.log.info(`${pc2.bold(agent.label)} ${pc2.dim(pathLabel)} \u2014 no skills installed`);
347
+ p2.log.info(`${pc2.bold(agent.label)} ${pc2.dim(basePath)} \u2014 no skills installed`);
317
348
  } else {
318
- p2.log.step(`${pc2.bold(agent.label)} ${pc2.dim(pathLabel)}`);
349
+ p2.log.step(`${pc2.bold(agent.label)} ${pc2.dim(basePath)}`);
319
350
  for (const name of skills) {
320
351
  console.log(` ${pc2.dim("\u2022")} ${name}`);
321
352
  }
@@ -332,19 +363,14 @@ import pc3 from "picocolors";
332
363
  function runRemove(name, opts) {
333
364
  showLogo();
334
365
  p3.intro(`${pc3.bold("bf-skills remove")}`);
335
- let removed = false;
336
- for (const agent of AGENTS) {
337
- const didRemove = removeSkill(name, agent, opts.global);
338
- if (didRemove) {
339
- const pathLabel = opts.global ? agent.globalPath : `./${agent.projectPath}`;
340
- p3.log.step(`Removed ${pc3.bold(name)} from ${pc3.dim(pathLabel)}`);
341
- removed = true;
342
- }
343
- }
344
- if (!removed) {
345
- p3.log.error(`Skill "${name}" not found in any agent skills directory.`);
366
+ const removed = removeSkill(name, AGENTS, opts.global);
367
+ if (removed.length === 0) {
368
+ p3.log.error(`Skill "${name}" not found in any target directory.`);
346
369
  p3.outro("Nothing removed.");
347
370
  } else {
371
+ for (const p_ of removed) {
372
+ p3.log.step(`Removed ${pc3.bold(name)} from ${pc3.dim(p_)}`);
373
+ }
348
374
  p3.outro(`Done \u2014 ${pc3.bold(name)} removed`);
349
375
  }
350
376
  showFooter();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bf-skills",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "BF Skills installer — Building the Future of Business with AI",
5
5
  "type": "module",
6
6
  "engines": {