add-skill 1.0.21 → 1.0.22
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/index.js +130 -39
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -245,8 +245,9 @@ function getSkillDisplayName(skill) {
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
// src/installer.ts
|
|
248
|
-
import { mkdir, cp, access, readdir as readdir2 } from "fs/promises";
|
|
249
|
-
import { join as join4, basename as basename2, normalize as normalize2, resolve as resolve3, sep as sep2 } from "path";
|
|
248
|
+
import { mkdir, cp, access, readdir as readdir2, symlink, lstat, rm as rm2, readlink } from "fs/promises";
|
|
249
|
+
import { join as join4, basename as basename2, normalize as normalize2, resolve as resolve3, sep as sep2, relative } from "path";
|
|
250
|
+
import { homedir as homedir2, platform } from "os";
|
|
250
251
|
|
|
251
252
|
// src/agents.ts
|
|
252
253
|
import { homedir } from "os";
|
|
@@ -410,6 +411,8 @@ async function detectInstalledAgents() {
|
|
|
410
411
|
}
|
|
411
412
|
|
|
412
413
|
// src/installer.ts
|
|
414
|
+
var AGENTS_DIR = ".agents";
|
|
415
|
+
var SKILLS_SUBDIR = "skills";
|
|
413
416
|
function sanitizeName(name) {
|
|
414
417
|
let sanitized = name.replace(/[\/\\:\0]/g, "");
|
|
415
418
|
sanitized = sanitized.replace(/^[.\s]+|[.\s]+$/g, "");
|
|
@@ -427,27 +430,82 @@ function isPathSafe(basePath, targetPath) {
|
|
|
427
430
|
const normalizedTarget = normalize2(resolve3(targetPath));
|
|
428
431
|
return normalizedTarget.startsWith(normalizedBase + sep2) || normalizedTarget === normalizedBase;
|
|
429
432
|
}
|
|
433
|
+
function getCanonicalSkillsDir(global, cwd) {
|
|
434
|
+
const baseDir = global ? homedir2() : cwd || process.cwd();
|
|
435
|
+
return join4(baseDir, AGENTS_DIR, SKILLS_SUBDIR);
|
|
436
|
+
}
|
|
437
|
+
async function createSymlink(target, linkPath) {
|
|
438
|
+
try {
|
|
439
|
+
try {
|
|
440
|
+
const stats = await lstat(linkPath);
|
|
441
|
+
if (stats.isSymbolicLink()) {
|
|
442
|
+
const existingTarget = await readlink(linkPath);
|
|
443
|
+
if (resolve3(existingTarget) === resolve3(target)) {
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
446
|
+
await rm2(linkPath);
|
|
447
|
+
} else {
|
|
448
|
+
await rm2(linkPath, { recursive: true });
|
|
449
|
+
}
|
|
450
|
+
} catch {
|
|
451
|
+
}
|
|
452
|
+
const linkDir = join4(linkPath, "..");
|
|
453
|
+
await mkdir(linkDir, { recursive: true });
|
|
454
|
+
const relativePath = relative(linkDir, target);
|
|
455
|
+
const symlinkType = platform() === "win32" ? "junction" : void 0;
|
|
456
|
+
await symlink(relativePath, linkPath, symlinkType);
|
|
457
|
+
return true;
|
|
458
|
+
} catch {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
430
462
|
async function installSkillForAgent(skill, agentType, options = {}) {
|
|
431
463
|
const agent = agents[agentType];
|
|
464
|
+
const isGlobal = options.global ?? false;
|
|
465
|
+
const cwd = options.cwd || process.cwd();
|
|
432
466
|
const rawSkillName = skill.name || basename2(skill.path);
|
|
433
467
|
const skillName = sanitizeName(rawSkillName);
|
|
434
|
-
const
|
|
435
|
-
const
|
|
436
|
-
|
|
468
|
+
const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
|
|
469
|
+
const canonicalDir = join4(canonicalBase, skillName);
|
|
470
|
+
const agentBase = isGlobal ? agent.globalSkillsDir : join4(cwd, agent.skillsDir);
|
|
471
|
+
const agentDir = join4(agentBase, skillName);
|
|
472
|
+
if (!isPathSafe(canonicalBase, canonicalDir)) {
|
|
473
|
+
return {
|
|
474
|
+
success: false,
|
|
475
|
+
path: agentDir,
|
|
476
|
+
error: "Invalid skill name: potential path traversal detected"
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
if (!isPathSafe(agentBase, agentDir)) {
|
|
437
480
|
return {
|
|
438
481
|
success: false,
|
|
439
|
-
path:
|
|
482
|
+
path: agentDir,
|
|
440
483
|
error: "Invalid skill name: potential path traversal detected"
|
|
441
484
|
};
|
|
442
485
|
}
|
|
443
486
|
try {
|
|
444
|
-
await mkdir(
|
|
445
|
-
await copyDirectory(skill.path,
|
|
446
|
-
|
|
487
|
+
await mkdir(canonicalDir, { recursive: true });
|
|
488
|
+
await copyDirectory(skill.path, canonicalDir);
|
|
489
|
+
const symlinkCreated = await createSymlink(canonicalDir, agentDir);
|
|
490
|
+
if (!symlinkCreated) {
|
|
491
|
+
await mkdir(agentDir, { recursive: true });
|
|
492
|
+
await copyDirectory(skill.path, agentDir);
|
|
493
|
+
return {
|
|
494
|
+
success: true,
|
|
495
|
+
path: agentDir,
|
|
496
|
+
canonicalPath: canonicalDir,
|
|
497
|
+
symlinkFailed: true
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
success: true,
|
|
502
|
+
path: agentDir,
|
|
503
|
+
canonicalPath: canonicalDir
|
|
504
|
+
};
|
|
447
505
|
} catch (error) {
|
|
448
506
|
return {
|
|
449
507
|
success: false,
|
|
450
|
-
path:
|
|
508
|
+
path: agentDir,
|
|
451
509
|
error: error instanceof Error ? error.message : "Unknown error"
|
|
452
510
|
};
|
|
453
511
|
}
|
|
@@ -492,17 +550,19 @@ async function isSkillInstalled(skillName, agentType, options = {}) {
|
|
|
492
550
|
return false;
|
|
493
551
|
}
|
|
494
552
|
}
|
|
495
|
-
function
|
|
496
|
-
const agent = agents[agentType];
|
|
553
|
+
function getCanonicalPath(skillName, options = {}) {
|
|
497
554
|
const sanitized = sanitizeName(skillName);
|
|
498
|
-
const
|
|
499
|
-
const
|
|
500
|
-
if (!isPathSafe(
|
|
555
|
+
const canonicalBase = getCanonicalSkillsDir(options.global ?? false, options.cwd);
|
|
556
|
+
const canonicalPath = join4(canonicalBase, sanitized);
|
|
557
|
+
if (!isPathSafe(canonicalBase, canonicalPath)) {
|
|
501
558
|
throw new Error("Invalid skill name: potential path traversal detected");
|
|
502
559
|
}
|
|
503
|
-
return
|
|
560
|
+
return canonicalPath;
|
|
504
561
|
}
|
|
505
562
|
|
|
563
|
+
// src/index.ts
|
|
564
|
+
import { homedir as homedir3 } from "os";
|
|
565
|
+
|
|
506
566
|
// src/telemetry.ts
|
|
507
567
|
var TELEMETRY_URL = "https://add-skill.vercel.sh/t";
|
|
508
568
|
var cliVersion = null;
|
|
@@ -539,7 +599,7 @@ function track(data) {
|
|
|
539
599
|
// package.json
|
|
540
600
|
var package_default = {
|
|
541
601
|
name: "add-skill",
|
|
542
|
-
version: "1.0.
|
|
602
|
+
version: "1.0.22",
|
|
543
603
|
description: "Install agent skills onto coding agents (OpenCode, Claude Code, Codex, Cursor)",
|
|
544
604
|
type: "module",
|
|
545
605
|
bin: {
|
|
@@ -605,6 +665,24 @@ var package_default = {
|
|
|
605
665
|
};
|
|
606
666
|
|
|
607
667
|
// src/index.ts
|
|
668
|
+
function shortenPath(fullPath, cwd) {
|
|
669
|
+
const home2 = homedir3();
|
|
670
|
+
if (fullPath.startsWith(home2)) {
|
|
671
|
+
return fullPath.replace(home2, "~");
|
|
672
|
+
}
|
|
673
|
+
if (fullPath.startsWith(cwd)) {
|
|
674
|
+
return "." + fullPath.slice(cwd.length);
|
|
675
|
+
}
|
|
676
|
+
return fullPath;
|
|
677
|
+
}
|
|
678
|
+
function formatList(items, maxShow = 5) {
|
|
679
|
+
if (items.length <= maxShow) {
|
|
680
|
+
return items.join(", ");
|
|
681
|
+
}
|
|
682
|
+
const shown = items.slice(0, maxShow);
|
|
683
|
+
const remaining = items.length - maxShow;
|
|
684
|
+
return `${shown.join(", ")} +${remaining} more`;
|
|
685
|
+
}
|
|
608
686
|
var version = package_default.version;
|
|
609
687
|
setVersion(version);
|
|
610
688
|
program.name("add-skill").description("Install skills onto coding agents (OpenCode, Claude Code, Codex, Kiro CLI, Cursor, Antigravity, Github Copilot, Roo Code)").version(version).argument("<source>", "Git repo URL, GitHub shorthand (owner/repo), local path (./path), or direct path to skill").option("-g, --global", "Install skill globally (user-level) instead of project-level").option("-a, --agent <agents...>", "Specify agents to install to (opencode, claude-code, codex, kiro-cli, cursor, antigravity, github-copilot, roo)").option("-s, --skill <skills...>", "Specify skill names to install (skip selection prompt)").option("-l, --list", "List available skills in the repository without installing").option("-y, --yes", "Skip confirmation prompts").option("--all", "Install all skills to all agents without any prompts (implies -y -g)").configureOutput({
|
|
@@ -808,8 +886,8 @@ async function main(source, options) {
|
|
|
808
886
|
}
|
|
809
887
|
installGlobally = scope;
|
|
810
888
|
}
|
|
889
|
+
const cwd = process.cwd();
|
|
811
890
|
const summaryLines = [];
|
|
812
|
-
const maxAgentLen = Math.max(...targetAgents.map((a) => agents[a].displayName.length));
|
|
813
891
|
const overwriteStatus = /* @__PURE__ */ new Map();
|
|
814
892
|
for (const skill of selectedSkills) {
|
|
815
893
|
const agentStatus = /* @__PURE__ */ new Map();
|
|
@@ -818,18 +896,20 @@ async function main(source, options) {
|
|
|
818
896
|
}
|
|
819
897
|
overwriteStatus.set(skill.name, agentStatus);
|
|
820
898
|
}
|
|
899
|
+
const agentNames = targetAgents.map((a) => agents[a].displayName);
|
|
900
|
+
const hasOverwrites = Array.from(overwriteStatus.values()).some(
|
|
901
|
+
(agentMap) => Array.from(agentMap.values()).some((v) => v)
|
|
902
|
+
);
|
|
821
903
|
for (const skill of selectedSkills) {
|
|
822
904
|
if (summaryLines.length > 0) summaryLines.push("");
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
summaryLines.push(
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
const agentName = agents[agent].displayName.padEnd(maxAgentLen + 2);
|
|
832
|
-
summaryLines.push(` ${agentName}${chalk.dim(basePath)}${status}`);
|
|
905
|
+
const canonicalPath = getCanonicalPath(skill.name, { global: installGlobally });
|
|
906
|
+
const shortCanonical = shortenPath(canonicalPath, cwd);
|
|
907
|
+
summaryLines.push(`${chalk.cyan(shortCanonical)}`);
|
|
908
|
+
const skillOverwrites = overwriteStatus.get(skill.name);
|
|
909
|
+
const overwriteAgents = targetAgents.filter((a) => skillOverwrites?.get(a)).map((a) => agents[a].displayName);
|
|
910
|
+
summaryLines.push(` ${chalk.dim("\u2192")} ${formatList(agentNames)}`);
|
|
911
|
+
if (overwriteAgents.length > 0) {
|
|
912
|
+
summaryLines.push(` ${chalk.yellow("overwrites:")} ${formatList(overwriteAgents)}`);
|
|
833
913
|
}
|
|
834
914
|
}
|
|
835
915
|
console.log();
|
|
@@ -884,25 +964,36 @@ async function main(source, options) {
|
|
|
884
964
|
if (successful.length > 0) {
|
|
885
965
|
const bySkill = /* @__PURE__ */ new Map();
|
|
886
966
|
for (const r of successful) {
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
bySkill.set(r.skill,
|
|
967
|
+
const skillResults = bySkill.get(r.skill) || [];
|
|
968
|
+
skillResults.push(r);
|
|
969
|
+
bySkill.set(r.skill, skillResults);
|
|
890
970
|
}
|
|
891
971
|
const skillCount = bySkill.size;
|
|
892
972
|
const agentCount = new Set(successful.map((r) => r.agent)).size;
|
|
973
|
+
const symlinkFailures = successful.filter((r) => r.symlinkFailed);
|
|
974
|
+
const copiedAgents = symlinkFailures.map((r) => r.agent);
|
|
893
975
|
const resultLines = [];
|
|
894
|
-
for (const [
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
976
|
+
for (const [, skillResults] of bySkill) {
|
|
977
|
+
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)}`);
|
|
898
989
|
}
|
|
899
|
-
resultLines.push("");
|
|
900
|
-
}
|
|
901
|
-
if (resultLines.length > 0 && resultLines[resultLines.length - 1] === "") {
|
|
902
|
-
resultLines.pop();
|
|
903
990
|
}
|
|
904
991
|
const title = chalk.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""} to ${agentCount} agent${agentCount !== 1 ? "s" : ""}`);
|
|
905
992
|
p.note(resultLines.join("\n"), title);
|
|
993
|
+
if (symlinkFailures.length > 0) {
|
|
994
|
+
p.log.warn(chalk.yellow(`Symlinks failed for: ${formatList(copiedAgents)}`));
|
|
995
|
+
p.log.message(chalk.dim(" Files were copied instead. On Windows, enable Developer Mode for symlink support."));
|
|
996
|
+
}
|
|
906
997
|
}
|
|
907
998
|
if (failed.length > 0) {
|
|
908
999
|
console.log();
|