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.
- package/dist/cli.mjs +108 -82
- 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
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
100
|
-
return destDir;
|
|
125
|
+
return installed;
|
|
101
126
|
}
|
|
102
|
-
function removeSkill(name,
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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,
|
|
110
|
-
const basePath =
|
|
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: "
|
|
127
|
-
label: "
|
|
128
|
-
projectPath: "
|
|
129
|
-
globalPath: path3.join(os.homedir(), "
|
|
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:
|
|
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(
|
|
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
|
|
315
|
+
const results = [];
|
|
283
316
|
for (const skill of selectedSkills) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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();
|