add-skill 1.0.22 → 1.0.23

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/index.js +75 -21
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -447,7 +447,13 @@ async function createSymlink(target, linkPath) {
447
447
  } else {
448
448
  await rm2(linkPath, { recursive: true });
449
449
  }
450
- } catch {
450
+ } catch (err) {
451
+ if (err && typeof err === "object" && "code" in err && err.code === "ELOOP") {
452
+ try {
453
+ await rm2(linkPath, { force: true });
454
+ } catch {
455
+ }
456
+ }
451
457
  }
452
458
  const linkDir = join4(linkPath, "..");
453
459
  await mkdir(linkDir, { recursive: true });
@@ -469,10 +475,12 @@ async function installSkillForAgent(skill, agentType, options = {}) {
469
475
  const canonicalDir = join4(canonicalBase, skillName);
470
476
  const agentBase = isGlobal ? agent.globalSkillsDir : join4(cwd, agent.skillsDir);
471
477
  const agentDir = join4(agentBase, skillName);
478
+ const installMode = options.mode ?? "symlink";
472
479
  if (!isPathSafe(canonicalBase, canonicalDir)) {
473
480
  return {
474
481
  success: false,
475
482
  path: agentDir,
483
+ mode: installMode,
476
484
  error: "Invalid skill name: potential path traversal detected"
477
485
  };
478
486
  }
@@ -480,32 +488,49 @@ async function installSkillForAgent(skill, agentType, options = {}) {
480
488
  return {
481
489
  success: false,
482
490
  path: agentDir,
491
+ mode: installMode,
483
492
  error: "Invalid skill name: potential path traversal detected"
484
493
  };
485
494
  }
486
495
  try {
496
+ if (installMode === "copy") {
497
+ await mkdir(agentDir, { recursive: true });
498
+ await copyDirectory(skill.path, agentDir);
499
+ return {
500
+ success: true,
501
+ path: agentDir,
502
+ mode: "copy"
503
+ };
504
+ }
487
505
  await mkdir(canonicalDir, { recursive: true });
488
506
  await copyDirectory(skill.path, canonicalDir);
489
507
  const symlinkCreated = await createSymlink(canonicalDir, agentDir);
490
508
  if (!symlinkCreated) {
509
+ try {
510
+ await rm2(agentDir, { recursive: true, force: true });
511
+ } catch {
512
+ }
491
513
  await mkdir(agentDir, { recursive: true });
492
514
  await copyDirectory(skill.path, agentDir);
493
515
  return {
494
516
  success: true,
495
517
  path: agentDir,
496
518
  canonicalPath: canonicalDir,
519
+ mode: "symlink",
497
520
  symlinkFailed: true
498
521
  };
499
522
  }
500
523
  return {
501
524
  success: true,
502
525
  path: agentDir,
503
- canonicalPath: canonicalDir
526
+ canonicalPath: canonicalDir,
527
+ mode: "symlink"
504
528
  };
505
529
  } catch (error) {
506
530
  return {
507
531
  success: false,
508
532
  path: agentDir,
533
+ mode: installMode,
509
534
  error: error instanceof Error ? error.message : "Unknown error"
510
535
  };
511
536
  }
@@ -599,7 +624,7 @@ function track(data) {
599
624
  // package.json
600
625
  var package_default = {
601
626
  name: "add-skill",
602
- version: "1.0.22",
627
+ version: "1.0.23",
603
628
  description: "Install agent skills onto coding agents (OpenCode, Claude Code, Codex, Cursor)",
604
629
  type: "module",
605
630
  bin: {
@@ -886,6 +911,22 @@ async function main(source, options) {
886
911
  }
887
912
  installGlobally = scope;
888
913
  }
914
+ let installMode = "symlink";
915
+ if (!options.yes) {
916
+ const modeChoice = await p.select({
917
+ message: "Installation method",
918
+ options: [
919
+ { value: "symlink", label: "Symlink (Recommended)", hint: "Single source of truth, easy updates" },
920
+ { value: "copy", label: "Copy to all agents", hint: "Independent copies for each agent" }
921
+ ]
922
+ });
923
+ if (p.isCancel(modeChoice)) {
924
+ p.cancel("Installation cancelled");
925
+ await cleanup(tempDir);
926
+ process.exit(0);
927
+ }
928
+ installMode = modeChoice;
929
+ }
889
930
  const cwd = process.cwd();
890
931
  const summaryLines = [];
891
932
  const overwriteStatus = /* @__PURE__ */ new Map();
@@ -902,12 +943,17 @@ async function main(source, options) {
902
943
  );
903
944
  for (const skill of selectedSkills) {
904
945
  if (summaryLines.length > 0) summaryLines.push("");
905
- const canonicalPath = getCanonicalPath(skill.name, { global: installGlobally });
906
- const shortCanonical = shortenPath(canonicalPath, cwd);
907
- summaryLines.push(`${chalk.cyan(shortCanonical)}`);
946
+ if (installMode === "symlink") {
947
+ const canonicalPath = getCanonicalPath(skill.name, { global: installGlobally });
948
+ const shortCanonical = shortenPath(canonicalPath, cwd);
949
+ summaryLines.push(`${chalk.cyan(shortCanonical)}`);
950
+ summaryLines.push(` ${chalk.dim("symlink \u2192")} ${formatList(agentNames)}`);
951
+ } else {
952
+ summaryLines.push(`${chalk.cyan(getSkillDisplayName(skill))}`);
953
+ summaryLines.push(` ${chalk.dim("copy \u2192")} ${formatList(agentNames)}`);
954
+ }
908
955
  const skillOverwrites = overwriteStatus.get(skill.name);
909
956
  const overwriteAgents = targetAgents.filter((a) => skillOverwrites?.get(a)).map((a) => agents[a].displayName);
910
- summaryLines.push(` ${chalk.dim("\u2192")} ${formatList(agentNames)}`);
911
957
  if (overwriteAgents.length > 0) {
912
958
  summaryLines.push(` ${chalk.yellow("overwrites:")} ${formatList(overwriteAgents)}`);
913
959
  }
@@ -926,7 +972,7 @@ async function main(source, options) {
926
972
  const results = [];
927
973
  for (const skill of selectedSkills) {
928
974
  for (const agent of targetAgents) {
929
- const result = await installSkillForAgent(skill, agent, { global: installGlobally });
975
+ const result = await installSkillForAgent(skill, agent, { global: installGlobally, mode: installMode });
930
976
  results.push({
931
977
  skill: getSkillDisplayName(skill),
932
978
  agent: agents[agent].displayName,
@@ -970,22 +1016,30 @@ async function main(source, options) {
970
1016
  }
971
1017
  const skillCount = bySkill.size;
972
1018
  const agentCount = new Set(successful.map((r) => r.agent)).size;
973
- const symlinkFailures = successful.filter((r) => r.symlinkFailed);
1019
+ const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
974
1020
  const copiedAgents = symlinkFailures.map((r) => r.agent);
975
1021
  const resultLines = [];
976
- for (const [, skillResults] of bySkill) {
1022
+ for (const [skillName, skillResults] of bySkill) {
977
1023
  const firstResult = skillResults[0];
978
- if (firstResult.canonicalPath) {
979
- const shortPath = shortenPath(firstResult.canonicalPath, cwd);
980
- resultLines.push(`${chalk.green("\u2713")} ${shortPath}`);
981
- }
982
- const symlinked = skillResults.filter((r) => !r.symlinkFailed).map((r) => r.agent);
983
- const copied = skillResults.filter((r) => r.symlinkFailed).map((r) => r.agent);
984
- if (symlinked.length > 0) {
985
- resultLines.push(` ${chalk.dim("\u2192")} ${formatList(symlinked)}`);
986
- }
987
- if (copied.length > 0) {
988
- resultLines.push(` ${chalk.yellow("copied \u2192")} ${formatList(copied)}`);
1024
+ if (firstResult.mode === "copy") {
1025
+ resultLines.push(`${chalk.green("\u2713")} ${skillName} ${chalk.dim("(copied)")}`);
1026
+ for (const r of skillResults) {
1027
+ const shortPath = shortenPath(r.path, cwd);
1028
+ resultLines.push(` ${chalk.dim("\u2192")} ${shortPath}`);
1029
+ }
1030
+ } else {
1031
+ if (firstResult.canonicalPath) {
1032
+ const shortPath = shortenPath(firstResult.canonicalPath, cwd);
1033
+ resultLines.push(`${chalk.green("\u2713")} ${shortPath}`);
1034
+ }
1035
+ const symlinked = skillResults.filter((r) => !r.symlinkFailed).map((r) => r.agent);
1036
+ const copied = skillResults.filter((r) => r.symlinkFailed).map((r) => r.agent);
1037
+ if (symlinked.length > 0) {
1038
+ resultLines.push(` ${chalk.dim("symlink \u2192")} ${formatList(symlinked)}`);
1039
+ }
1040
+ if (copied.length > 0) {
1041
+ resultLines.push(` ${chalk.yellow("copied \u2192")} ${formatList(copied)}`);
1042
+ }
989
1043
  }
990
1044
  }
991
1045
  const title = chalk.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""} to ${agentCount} agent${agentCount !== 1 ? "s" : ""}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "add-skill",
3
- "version": "1.0.22",
3
+ "version": "1.0.23",
4
4
  "description": "Install agent skills onto coding agents (OpenCode, Claude Code, Codex, Cursor)",
5
5
  "type": "module",
6
6
  "bin": {