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.
Files changed (2) hide show
  1. package/dist/cli.mjs +96 -24
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/commands/install.ts
2
- import os2 from "os";
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 resolvePath = (agent) => isGlobal ? path2.join(agent.globalPath, skillName) : path2.join(process.cwd(), agent.projectPath, skillName);
138
- const primaryDest = resolvePath(agents[0]);
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 = resolvePath(agent);
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 p4 = isGlobal ? path2.join(agent.globalPath, name) : path2.join(process.cwd(), agent.projectPath, name);
157
- if (fs2.existsSync(p4)) {
158
- fs2.rmSync(p4, { recursive: true, force: true });
159
- removed.push(p4);
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 os from "os";
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(os.homedir(), ".agents", "skills")
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(os.homedir(), ".claude", "skills")
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(os.homedir(), "skills")
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(os2.tmpdir(), "bf-skills-"));
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.autocompleteMultiselect({
335
- message: `Pick skills to install (${allSkills.length} available \u2014 type to search)`,
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)} ${pc.blue(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 targetLines = targetAgents.map((a) => {
351
- const dest = opts.global ? a.globalPath : `./${a.projectPath}`;
352
- return ` ${pc.dim("\u2192")} ${pc.bold(a.label)} ${pc.dim(dest)}`;
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, opts.global);
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(os2.homedir(), "~");
384
- return `${pc.cyan(skill.name)} ${pc.dim("\u2192")} ${rel}${i > 0 ? pc.dim(" (symlink)") : ""}`;
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`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bf-skills",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "BF Skills installer — Building the Future of Business with AI",
5
5
  "type": "module",
6
6
  "engines": {