bf-skills 1.2.0 → 1.3.1
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 +96 -24
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/commands/install.ts
|
|
2
|
-
import
|
|
2
|
+
import os3 from "os";
|
|
3
3
|
import path4 from "path";
|
|
4
4
|
import fs3 from "fs";
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
@@ -112,6 +112,7 @@ function discoverSkills(repoPath) {
|
|
|
112
112
|
|
|
113
113
|
// src/lib/install.ts
|
|
114
114
|
import fs2 from "fs";
|
|
115
|
+
import os from "os";
|
|
115
116
|
import path2 from "path";
|
|
116
117
|
function copyDir(src, dest) {
|
|
117
118
|
fs2.mkdirSync(dest, { recursive: true });
|
|
@@ -132,16 +133,27 @@ function removePath(p4) {
|
|
|
132
133
|
fs2.rmSync(p4, { recursive: true, force: true });
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
|
-
function installSkill(skillDir, skillName, agents, isGlobal) {
|
|
136
|
+
function installSkill(skillDir, skillName, agents, isGlobal, mode = "symlink") {
|
|
136
137
|
if (agents.length === 0) throw new Error("No install targets selected");
|
|
137
|
-
const
|
|
138
|
-
|
|
138
|
+
const resolveDest = (agent) => isGlobal ? path2.join(os.homedir(), agent.globalPath, skillName) : path2.join(process.cwd(), agent.projectPath, skillName);
|
|
139
|
+
if (mode === "copy") {
|
|
140
|
+
const installed2 = [];
|
|
141
|
+
for (const agent of agents) {
|
|
142
|
+
const dest = resolveDest(agent);
|
|
143
|
+
fs2.mkdirSync(path2.dirname(dest), { recursive: true });
|
|
144
|
+
removePath(dest);
|
|
145
|
+
copyDir(skillDir, dest);
|
|
146
|
+
installed2.push(dest);
|
|
147
|
+
}
|
|
148
|
+
return installed2;
|
|
149
|
+
}
|
|
150
|
+
const primaryDest = resolveDest(agents[0]);
|
|
139
151
|
fs2.mkdirSync(path2.dirname(primaryDest), { recursive: true });
|
|
140
152
|
removePath(primaryDest);
|
|
141
153
|
copyDir(skillDir, primaryDest);
|
|
142
154
|
const installed = [primaryDest];
|
|
143
155
|
for (const agent of agents.slice(1)) {
|
|
144
|
-
const dest =
|
|
156
|
+
const dest = resolveDest(agent);
|
|
145
157
|
fs2.mkdirSync(path2.dirname(dest), { recursive: true });
|
|
146
158
|
removePath(dest);
|
|
147
159
|
const rel = path2.relative(path2.dirname(dest), primaryDest);
|
|
@@ -153,41 +165,41 @@ function installSkill(skillDir, skillName, agents, isGlobal) {
|
|
|
153
165
|
function removeSkill(name, agents, isGlobal) {
|
|
154
166
|
const removed = [];
|
|
155
167
|
for (const agent of agents) {
|
|
156
|
-
const
|
|
157
|
-
if (fs2.existsSync(
|
|
158
|
-
fs2.rmSync(
|
|
159
|
-
removed.push(
|
|
168
|
+
const dest = isGlobal ? path2.join(os.homedir(), agent.globalPath, name) : path2.join(process.cwd(), agent.projectPath, name);
|
|
169
|
+
if (fs2.existsSync(dest)) {
|
|
170
|
+
fs2.rmSync(dest, { recursive: true, force: true });
|
|
171
|
+
removed.push(dest);
|
|
160
172
|
}
|
|
161
173
|
}
|
|
162
174
|
return removed;
|
|
163
175
|
}
|
|
164
176
|
function listInstalledSkills(agent, isGlobal) {
|
|
165
|
-
const basePath = isGlobal ? agent.globalPath : path2.join(process.cwd(), agent.projectPath);
|
|
177
|
+
const basePath = isGlobal ? path2.join(os.homedir(), agent.globalPath) : path2.join(process.cwd(), agent.projectPath);
|
|
166
178
|
if (!fs2.existsSync(basePath)) return [];
|
|
167
179
|
return fs2.readdirSync(basePath, { withFileTypes: true }).filter((e) => e.isDirectory() || e.isSymbolicLink()).map((e) => e.name);
|
|
168
180
|
}
|
|
169
181
|
|
|
170
182
|
// src/lib/agents.ts
|
|
171
|
-
import
|
|
183
|
+
import os2 from "os";
|
|
172
184
|
import path3 from "path";
|
|
173
185
|
var AGENTS = [
|
|
174
186
|
{
|
|
175
187
|
id: "general",
|
|
176
188
|
label: ".agents/skills (general agents)",
|
|
177
189
|
projectPath: ".agents/skills",
|
|
178
|
-
globalPath: path3.join(
|
|
190
|
+
globalPath: path3.join(os2.homedir(), ".agents", "skills")
|
|
179
191
|
},
|
|
180
192
|
{
|
|
181
193
|
id: "claude-code",
|
|
182
194
|
label: ".claude/skills (Claude Code)",
|
|
183
195
|
projectPath: ".claude/skills",
|
|
184
|
-
globalPath: path3.join(
|
|
196
|
+
globalPath: path3.join(os2.homedir(), ".claude", "skills")
|
|
185
197
|
},
|
|
186
198
|
{
|
|
187
199
|
id: "root",
|
|
188
200
|
label: "skills/ (root folder)",
|
|
189
201
|
projectPath: "skills",
|
|
190
|
-
globalPath: path3.join(
|
|
202
|
+
globalPath: path3.join(os2.homedir(), "skills")
|
|
191
203
|
}
|
|
192
204
|
];
|
|
193
205
|
var DEFAULT_AGENT_IDS = ["general", "claude-code"];
|
|
@@ -290,13 +302,66 @@ async function runInstall(source, opts) {
|
|
|
290
302
|
}
|
|
291
303
|
targetAgents = AGENTS.filter((a) => chosen.includes(a.id));
|
|
292
304
|
}
|
|
305
|
+
let isGlobal = opts.global;
|
|
306
|
+
if (!opts.yes && !opts.agent) {
|
|
307
|
+
const scope = await p.select({
|
|
308
|
+
message: "Installation scope",
|
|
309
|
+
options: [
|
|
310
|
+
{
|
|
311
|
+
value: false,
|
|
312
|
+
label: pc.blue("Project"),
|
|
313
|
+
hint: "Install in current directory (committed with your project)"
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
value: true,
|
|
317
|
+
label: pc.blue("Global"),
|
|
318
|
+
hint: "Install in home directory (available across all projects)"
|
|
319
|
+
}
|
|
320
|
+
],
|
|
321
|
+
initialValue: false
|
|
322
|
+
});
|
|
323
|
+
if (p.isCancel(scope)) {
|
|
324
|
+
p.cancel("Cancelled.");
|
|
325
|
+
process.exit(0);
|
|
326
|
+
}
|
|
327
|
+
isGlobal = scope;
|
|
328
|
+
}
|
|
329
|
+
let installMode = "symlink";
|
|
330
|
+
const uniqueDirs = new Set(
|
|
331
|
+
targetAgents.map(
|
|
332
|
+
(a) => isGlobal ? path4.join(os3.homedir(), a.globalPath) : path4.join(process.cwd(), a.projectPath)
|
|
333
|
+
)
|
|
334
|
+
);
|
|
335
|
+
if (!opts.yes && !opts.agent && uniqueDirs.size > 1) {
|
|
336
|
+
const modeChoice = await p.select({
|
|
337
|
+
message: "Installation method",
|
|
338
|
+
options: [
|
|
339
|
+
{
|
|
340
|
+
value: "symlink",
|
|
341
|
+
label: pc.blue("Symlink") + pc.dim(" (Recommended)"),
|
|
342
|
+
hint: "Single source of truth \u2014 secondary agents point to the primary copy"
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
value: "copy",
|
|
346
|
+
label: pc.blue("Copy to all agents"),
|
|
347
|
+
hint: "Independent copies for each agent \u2014 no shared state"
|
|
348
|
+
}
|
|
349
|
+
],
|
|
350
|
+
initialValue: "symlink"
|
|
351
|
+
});
|
|
352
|
+
if (p.isCancel(modeChoice)) {
|
|
353
|
+
p.cancel("Cancelled.");
|
|
354
|
+
process.exit(0);
|
|
355
|
+
}
|
|
356
|
+
installMode = modeChoice;
|
|
357
|
+
}
|
|
293
358
|
let repoPath;
|
|
294
359
|
let tmpDir;
|
|
295
360
|
const isLocal = !repoUrl.startsWith("http") && !repoUrl.startsWith("git@");
|
|
296
361
|
if (isLocal) {
|
|
297
362
|
repoPath = path4.resolve(repoUrl);
|
|
298
363
|
} else {
|
|
299
|
-
tmpDir = fs3.mkdtempSync(path4.join(
|
|
364
|
+
tmpDir = fs3.mkdtempSync(path4.join(os3.tmpdir(), "bf-skills-"));
|
|
300
365
|
repoPath = tmpDir;
|
|
301
366
|
const s = p.spinner();
|
|
302
367
|
s.start("Fetching skills catalog...");
|
|
@@ -331,11 +396,11 @@ async function runInstall(source, opts) {
|
|
|
331
396
|
} else if (opts.yes) {
|
|
332
397
|
selectedSkills = allSkills;
|
|
333
398
|
} else {
|
|
334
|
-
const selected = await p.
|
|
335
|
-
message: `Pick skills to install (${allSkills.length} available
|
|
399
|
+
const selected = await p.multiselect({
|
|
400
|
+
message: `Pick skills to install (${allSkills.length} available)`,
|
|
336
401
|
options: allSkills.map((s) => ({
|
|
337
402
|
value: s.name,
|
|
338
|
-
label: `${skillEmoji(s.name)} ${
|
|
403
|
+
label: `${skillEmoji(s.name)} ${s.name}`,
|
|
339
404
|
hint: s.description
|
|
340
405
|
})),
|
|
341
406
|
required: true
|
|
@@ -347,15 +412,21 @@ async function runInstall(source, opts) {
|
|
|
347
412
|
}
|
|
348
413
|
selectedSkills = allSkills.filter((s) => selected.includes(s.name));
|
|
349
414
|
}
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
415
|
+
const scopeLabel = isGlobal ? "Global (~/)" : "Project (./)";
|
|
416
|
+
const methodLabel = installMode === "symlink" ? "Symlink (primary copy + symlinks)" : "Copy (independent copies)";
|
|
417
|
+
const targetLines = targetAgents.map((a, i) => {
|
|
418
|
+
const dest = isGlobal ? path4.join("~", a.globalPath) : `./${a.projectPath}`;
|
|
419
|
+
const tag = installMode === "symlink" && i > 0 ? pc.dim(" \u2192 symlink") : "";
|
|
420
|
+
return ` ${pc.dim("\u2192")} ${pc.bold(a.label)} ${pc.dim(dest)}${tag}`;
|
|
353
421
|
});
|
|
354
422
|
p.note(
|
|
355
423
|
[
|
|
356
424
|
`${pc.bold("Skills:")}`,
|
|
357
425
|
...selectedSkills.map((s) => ` ${skillEmoji(s.name)} ${pc.blue(s.name)} ${pc.dim(s.description)}`),
|
|
358
426
|
"",
|
|
427
|
+
`${pc.bold("Scope:")} ${scopeLabel}`,
|
|
428
|
+
`${pc.bold("Method:")} ${methodLabel}`,
|
|
429
|
+
"",
|
|
359
430
|
`${pc.bold("Install to:")}`,
|
|
360
431
|
...targetLines
|
|
361
432
|
].join("\n"),
|
|
@@ -378,10 +449,11 @@ async function runInstall(source, opts) {
|
|
|
378
449
|
const installed = [];
|
|
379
450
|
for (const skill of selectedSkills) {
|
|
380
451
|
prog.advance(1, `Installing ${pc.cyan(skill.name)}...`);
|
|
381
|
-
const paths = installSkill(skill.dir, skill.name, targetAgents,
|
|
452
|
+
const paths = installSkill(skill.dir, skill.name, targetAgents, isGlobal, installMode);
|
|
382
453
|
installed.push(...paths.map((dest, i) => {
|
|
383
|
-
const rel = dest.startsWith(process.cwd()) ? "." + dest.slice(process.cwd().length) : dest.replace(
|
|
384
|
-
|
|
454
|
+
const rel = dest.startsWith(process.cwd()) ? "." + dest.slice(process.cwd().length) : dest.replace(os3.homedir(), "~");
|
|
455
|
+
const tag = installMode === "symlink" && i > 0 ? pc.dim(" (symlink)") : "";
|
|
456
|
+
return `${pc.cyan(skill.name)} ${pc.dim("\u2192")} ${rel}${tag}`;
|
|
385
457
|
}));
|
|
386
458
|
}
|
|
387
459
|
prog.stop(`${selectedSkills.length} skill${selectedSkills.length === 1 ? "" : "s"} installed`);
|