add-skill 1.0.15 → 1.0.17
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/README.md +1 -2
- package/dist/index.js +95 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Install agent skills onto your coding agents from any git repository.
|
|
4
4
|
|
|
5
5
|
<!-- agent-list:start -->
|
|
6
|
-
Supports **Opencode**, **Claude Code**, **Codex**, **Cursor**, and [
|
|
6
|
+
Supports **Opencode**, **Claude Code**, **Codex**, **Cursor**, and [10 more](#available-agents).
|
|
7
7
|
<!-- agent-list:end -->
|
|
8
8
|
|
|
9
9
|
## Quick Start
|
|
@@ -95,7 +95,6 @@ Skills can be installed to any of these supported agents. Use `-g, --global` to
|
|
|
95
95
|
| GitHub Copilot | `.github/skills/` | `~/.copilot/skills/` |
|
|
96
96
|
| Clawdbot | `skills/` | `~/.clawdbot/skills/` |
|
|
97
97
|
| Droid | `.factory/skills/` | `~/.factory/skills/` |
|
|
98
|
-
| Gemini CLI | `.gemini/skills/` | `~/.gemini/skills/` |
|
|
99
98
|
| Windsurf | `.windsurf/skills/` | `~/.codeium/windsurf/skills/` |
|
|
100
99
|
<!-- available-agents:end -->
|
|
101
100
|
|
package/dist/index.js
CHANGED
|
@@ -5,12 +5,22 @@ import { program } from "commander";
|
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
|
|
8
|
-
// src/
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
// src/source-parser.ts
|
|
9
|
+
import { isAbsolute, resolve } from "path";
|
|
10
|
+
function isLocalPath(input) {
|
|
11
|
+
return isAbsolute(input) || input.startsWith("./") || input.startsWith("../") || input === "." || input === ".." || // Windows absolute paths like C:\ or D:\
|
|
12
|
+
/^[a-zA-Z]:[/\\]/.test(input);
|
|
13
|
+
}
|
|
13
14
|
function parseSource(input) {
|
|
15
|
+
if (isLocalPath(input)) {
|
|
16
|
+
const resolvedPath = resolve(input);
|
|
17
|
+
return {
|
|
18
|
+
type: "local",
|
|
19
|
+
url: resolvedPath,
|
|
20
|
+
// Store resolved path in url for consistency
|
|
21
|
+
localPath: resolvedPath
|
|
22
|
+
};
|
|
23
|
+
}
|
|
14
24
|
const githubTreeMatch = input.match(
|
|
15
25
|
/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)/
|
|
16
26
|
);
|
|
@@ -52,7 +62,7 @@ function parseSource(input) {
|
|
|
52
62
|
};
|
|
53
63
|
}
|
|
54
64
|
const shorthandMatch = input.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
|
|
55
|
-
if (shorthandMatch && !input.includes(":")) {
|
|
65
|
+
if (shorthandMatch && !input.includes(":") && !input.startsWith(".") && !input.startsWith("/")) {
|
|
56
66
|
const [, owner, repo, subpath] = shorthandMatch;
|
|
57
67
|
return {
|
|
58
68
|
type: "github",
|
|
@@ -65,6 +75,12 @@ function parseSource(input) {
|
|
|
65
75
|
url: input
|
|
66
76
|
};
|
|
67
77
|
}
|
|
78
|
+
|
|
79
|
+
// src/git.ts
|
|
80
|
+
import simpleGit from "simple-git";
|
|
81
|
+
import { join, normalize, resolve as resolve2, sep } from "path";
|
|
82
|
+
import { mkdtemp, rm } from "fs/promises";
|
|
83
|
+
import { tmpdir } from "os";
|
|
68
84
|
async function cloneRepo(url) {
|
|
69
85
|
const tempDir = await mkdtemp(join(tmpdir(), "add-skill-"));
|
|
70
86
|
const git = simpleGit();
|
|
@@ -72,8 +88,8 @@ async function cloneRepo(url) {
|
|
|
72
88
|
return tempDir;
|
|
73
89
|
}
|
|
74
90
|
async function cleanupTempDir(dir) {
|
|
75
|
-
const normalizedDir = normalize(
|
|
76
|
-
const normalizedTmpDir = normalize(
|
|
91
|
+
const normalizedDir = normalize(resolve2(dir));
|
|
92
|
+
const normalizedTmpDir = normalize(resolve2(tmpdir()));
|
|
77
93
|
if (!normalizedDir.startsWith(normalizedTmpDir + sep) && normalizedDir !== normalizedTmpDir) {
|
|
78
94
|
throw new Error("Attempted to clean up directory outside of temp directory");
|
|
79
95
|
}
|
|
@@ -193,7 +209,7 @@ function getSkillDisplayName(skill) {
|
|
|
193
209
|
|
|
194
210
|
// src/installer.ts
|
|
195
211
|
import { mkdir, cp, access, readdir as readdir2 } from "fs/promises";
|
|
196
|
-
import { join as join4, basename as basename2, normalize as normalize2, resolve as
|
|
212
|
+
import { join as join4, basename as basename2, normalize as normalize2, resolve as resolve3, sep as sep2 } from "path";
|
|
197
213
|
|
|
198
214
|
// src/agents.ts
|
|
199
215
|
import { homedir } from "os";
|
|
@@ -352,8 +368,8 @@ function sanitizeName(name) {
|
|
|
352
368
|
return sanitized;
|
|
353
369
|
}
|
|
354
370
|
function isPathSafe(basePath, targetPath) {
|
|
355
|
-
const normalizedBase = normalize2(
|
|
356
|
-
const normalizedTarget = normalize2(
|
|
371
|
+
const normalizedBase = normalize2(resolve3(basePath));
|
|
372
|
+
const normalizedTarget = normalize2(resolve3(targetPath));
|
|
357
373
|
return normalizedTarget.startsWith(normalizedBase + sep2) || normalizedTarget === normalizedBase;
|
|
358
374
|
}
|
|
359
375
|
async function installSkillForAgent(skill, agentType, options = {}) {
|
|
@@ -468,7 +484,7 @@ function track(data) {
|
|
|
468
484
|
// package.json
|
|
469
485
|
var package_default = {
|
|
470
486
|
name: "add-skill",
|
|
471
|
-
version: "1.0.
|
|
487
|
+
version: "1.0.17",
|
|
472
488
|
description: "Install agent skills onto coding agents (OpenCode, Claude Code, Codex, Cursor)",
|
|
473
489
|
type: "module",
|
|
474
490
|
bin: {
|
|
@@ -527,7 +543,7 @@ var package_default = {
|
|
|
527
543
|
// src/index.ts
|
|
528
544
|
var version = package_default.version;
|
|
529
545
|
setVersion(version);
|
|
530
|
-
program.name("add-skill").description("Install skills onto coding agents (OpenCode, Claude Code, Codex, Cursor, Antigravity, Github Copilot, Roo Code)").version(version).argument("<source>", "Git repo URL, GitHub shorthand (owner/repo), 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, cursor, antigravity, gitub-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").configureOutput({
|
|
546
|
+
program.name("add-skill").description("Install skills onto coding agents (OpenCode, Claude Code, Codex, 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, cursor, antigravity, gitub-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").configureOutput({
|
|
531
547
|
outputError: (str, write) => {
|
|
532
548
|
if (str.includes("missing required argument")) {
|
|
533
549
|
console.log();
|
|
@@ -557,12 +573,26 @@ async function main(source, options) {
|
|
|
557
573
|
const spinner2 = p.spinner();
|
|
558
574
|
spinner2.start("Parsing source...");
|
|
559
575
|
const parsed = parseSource(source);
|
|
560
|
-
spinner2.stop(`Source: ${chalk.cyan(parsed.url)}${parsed.subpath ? ` (${parsed.subpath})` : ""}`);
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
576
|
+
spinner2.stop(`Source: ${chalk.cyan(parsed.type === "local" ? parsed.localPath : parsed.url)}${parsed.subpath ? ` (${parsed.subpath})` : ""}`);
|
|
577
|
+
let skillsDir;
|
|
578
|
+
if (parsed.type === "local") {
|
|
579
|
+
spinner2.start("Validating local path...");
|
|
580
|
+
const { existsSync: existsSync2 } = await import("fs");
|
|
581
|
+
if (!existsSync2(parsed.localPath)) {
|
|
582
|
+
spinner2.stop(chalk.red("Path not found"));
|
|
583
|
+
p.outro(chalk.red(`Local path does not exist: ${parsed.localPath}`));
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
skillsDir = parsed.localPath;
|
|
587
|
+
spinner2.stop("Local path validated");
|
|
588
|
+
} else {
|
|
589
|
+
spinner2.start("Cloning repository...");
|
|
590
|
+
tempDir = await cloneRepo(parsed.url);
|
|
591
|
+
skillsDir = tempDir;
|
|
592
|
+
spinner2.stop("Repository cloned");
|
|
593
|
+
}
|
|
564
594
|
spinner2.start("Discovering skills...");
|
|
565
|
-
const skills = await discoverSkills(
|
|
595
|
+
const skills = await discoverSkills(skillsDir, parsed.subpath);
|
|
566
596
|
if (skills.length === 0) {
|
|
567
597
|
spinner2.stop(chalk.red("No skills found"));
|
|
568
598
|
p.outro(chalk.red("No valid skills found. Skills require a SKILL.md with name and description."));
|
|
@@ -653,7 +683,8 @@ async function main(source, options) {
|
|
|
653
683
|
const selected = await p.multiselect({
|
|
654
684
|
message: "Select agents to install skills to",
|
|
655
685
|
options: allAgentChoices,
|
|
656
|
-
required: true
|
|
686
|
+
required: true,
|
|
687
|
+
initialValues: Object.keys(agents)
|
|
657
688
|
});
|
|
658
689
|
if (p.isCancel(selected)) {
|
|
659
690
|
p.cancel("Installation cancelled");
|
|
@@ -706,18 +737,32 @@ async function main(source, options) {
|
|
|
706
737
|
}
|
|
707
738
|
installGlobally = scope;
|
|
708
739
|
}
|
|
709
|
-
|
|
710
|
-
|
|
740
|
+
const summaryLines = [];
|
|
741
|
+
const maxAgentLen = Math.max(...targetAgents.map((a) => agents[a].displayName.length));
|
|
742
|
+
const overwriteStatus = /* @__PURE__ */ new Map();
|
|
711
743
|
for (const skill of selectedSkills) {
|
|
712
|
-
|
|
744
|
+
const agentStatus = /* @__PURE__ */ new Map();
|
|
713
745
|
for (const agent of targetAgents) {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
746
|
+
agentStatus.set(agent, await isSkillInstalled(skill.name, agent, { global: installGlobally }));
|
|
747
|
+
}
|
|
748
|
+
overwriteStatus.set(skill.name, agentStatus);
|
|
749
|
+
}
|
|
750
|
+
for (const skill of selectedSkills) {
|
|
751
|
+
if (summaryLines.length > 0) summaryLines.push("");
|
|
752
|
+
summaryLines.push(chalk.bold.cyan(getSkillDisplayName(skill)));
|
|
753
|
+
summaryLines.push("");
|
|
754
|
+
summaryLines.push(` ${chalk.bold("Agent".padEnd(maxAgentLen + 2))}${chalk.bold("Directory")}`);
|
|
755
|
+
for (const agent of targetAgents) {
|
|
756
|
+
const fullPath = getInstallPath(skill.name, agent, { global: installGlobally });
|
|
757
|
+
const basePath = fullPath.replace(/\/[^/]+$/, "/");
|
|
758
|
+
const installed = overwriteStatus.get(skill.name)?.get(agent) ?? false;
|
|
759
|
+
const status = installed ? chalk.yellow(" (overwrite)") : "";
|
|
760
|
+
const agentName = agents[agent].displayName.padEnd(maxAgentLen + 2);
|
|
761
|
+
summaryLines.push(` ${agentName}${chalk.dim(basePath)}${status}`);
|
|
718
762
|
}
|
|
719
763
|
}
|
|
720
764
|
console.log();
|
|
765
|
+
p.note(summaryLines.join("\n"), "Installation Summary");
|
|
721
766
|
if (!options.yes) {
|
|
722
767
|
const confirmed = await p.confirm({ message: "Proceed with installation?" });
|
|
723
768
|
if (p.isCancel(confirmed) || !confirmed) {
|
|
@@ -744,8 +789,15 @@ async function main(source, options) {
|
|
|
744
789
|
const failed = results.filter((r) => !r.success);
|
|
745
790
|
const skillFiles = {};
|
|
746
791
|
for (const skill of selectedSkills) {
|
|
747
|
-
|
|
748
|
-
|
|
792
|
+
let relativePath;
|
|
793
|
+
if (tempDir && skill.path === tempDir) {
|
|
794
|
+
relativePath = "SKILL.md";
|
|
795
|
+
} else if (tempDir && skill.path.startsWith(tempDir + "/")) {
|
|
796
|
+
relativePath = skill.path.slice(tempDir.length + 1) + "/SKILL.md";
|
|
797
|
+
} else {
|
|
798
|
+
relativePath = skill.name + "/SKILL.md";
|
|
799
|
+
}
|
|
800
|
+
skillFiles[skill.name] = relativePath;
|
|
749
801
|
}
|
|
750
802
|
track({
|
|
751
803
|
event: "install",
|
|
@@ -758,16 +810,25 @@ async function main(source, options) {
|
|
|
758
810
|
if (successful.length > 0) {
|
|
759
811
|
const bySkill = /* @__PURE__ */ new Map();
|
|
760
812
|
for (const r of successful) {
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
bySkill.set(r.skill,
|
|
813
|
+
const skillAgents = bySkill.get(r.skill) || [];
|
|
814
|
+
skillAgents.push(r.agent);
|
|
815
|
+
bySkill.set(r.skill, skillAgents);
|
|
764
816
|
}
|
|
765
817
|
const skillCount = bySkill.size;
|
|
766
818
|
const agentCount = new Set(successful.map((r) => r.agent)).size;
|
|
767
|
-
|
|
768
|
-
for (const [skill,
|
|
769
|
-
|
|
819
|
+
const resultLines = [];
|
|
820
|
+
for (const [skill, skillAgents] of bySkill) {
|
|
821
|
+
resultLines.push(`${chalk.green("\u2713")} ${chalk.bold(skill)}`);
|
|
822
|
+
for (const agent of skillAgents) {
|
|
823
|
+
resultLines.push(` ${chalk.dim(agent)}`);
|
|
824
|
+
}
|
|
825
|
+
resultLines.push("");
|
|
826
|
+
}
|
|
827
|
+
if (resultLines.length > 0 && resultLines[resultLines.length - 1] === "") {
|
|
828
|
+
resultLines.pop();
|
|
770
829
|
}
|
|
830
|
+
const title = chalk.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""} to ${agentCount} agent${agentCount !== 1 ? "s" : ""}`);
|
|
831
|
+
p.note(resultLines.join("\n"), title);
|
|
771
832
|
}
|
|
772
833
|
if (failed.length > 0) {
|
|
773
834
|
console.log();
|