add-skill 1.0.14 → 1.0.16
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 +92 -41
- 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";
|
|
@@ -318,15 +334,6 @@ var agents = {
|
|
|
318
334
|
return existsSync(join3(home, ".factory/skills"));
|
|
319
335
|
}
|
|
320
336
|
},
|
|
321
|
-
gemini: {
|
|
322
|
-
name: "gemini",
|
|
323
|
-
displayName: "Gemini CLI",
|
|
324
|
-
skillsDir: ".gemini/skills",
|
|
325
|
-
globalSkillsDir: join3(home, ".gemini/skills"),
|
|
326
|
-
detectInstalled: async () => {
|
|
327
|
-
return existsSync(join3(home, ".gemini"));
|
|
328
|
-
}
|
|
329
|
-
},
|
|
330
337
|
windsurf: {
|
|
331
338
|
name: "windsurf",
|
|
332
339
|
displayName: "Windsurf",
|
|
@@ -361,8 +368,8 @@ function sanitizeName(name) {
|
|
|
361
368
|
return sanitized;
|
|
362
369
|
}
|
|
363
370
|
function isPathSafe(basePath, targetPath) {
|
|
364
|
-
const normalizedBase = normalize2(
|
|
365
|
-
const normalizedTarget = normalize2(
|
|
371
|
+
const normalizedBase = normalize2(resolve3(basePath));
|
|
372
|
+
const normalizedTarget = normalize2(resolve3(targetPath));
|
|
366
373
|
return normalizedTarget.startsWith(normalizedBase + sep2) || normalizedTarget === normalizedBase;
|
|
367
374
|
}
|
|
368
375
|
async function installSkillForAgent(skill, agentType, options = {}) {
|
|
@@ -477,7 +484,7 @@ function track(data) {
|
|
|
477
484
|
// package.json
|
|
478
485
|
var package_default = {
|
|
479
486
|
name: "add-skill",
|
|
480
|
-
version: "1.0.
|
|
487
|
+
version: "1.0.16",
|
|
481
488
|
description: "Install agent skills onto coding agents (OpenCode, Claude Code, Codex, Cursor)",
|
|
482
489
|
type: "module",
|
|
483
490
|
bin: {
|
|
@@ -536,7 +543,7 @@ var package_default = {
|
|
|
536
543
|
// src/index.ts
|
|
537
544
|
var version = package_default.version;
|
|
538
545
|
setVersion(version);
|
|
539
|
-
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({
|
|
540
547
|
outputError: (str, write) => {
|
|
541
548
|
if (str.includes("missing required argument")) {
|
|
542
549
|
console.log();
|
|
@@ -566,12 +573,26 @@ async function main(source, options) {
|
|
|
566
573
|
const spinner2 = p.spinner();
|
|
567
574
|
spinner2.start("Parsing source...");
|
|
568
575
|
const parsed = parseSource(source);
|
|
569
|
-
spinner2.stop(`Source: ${chalk.cyan(parsed.url)}${parsed.subpath ? ` (${parsed.subpath})` : ""}`);
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
+
}
|
|
573
594
|
spinner2.start("Discovering skills...");
|
|
574
|
-
const skills = await discoverSkills(
|
|
595
|
+
const skills = await discoverSkills(skillsDir, parsed.subpath);
|
|
575
596
|
if (skills.length === 0) {
|
|
576
597
|
spinner2.stop(chalk.red("No skills found"));
|
|
577
598
|
p.outro(chalk.red("No valid skills found. Skills require a SKILL.md with name and description."));
|
|
@@ -662,7 +683,8 @@ async function main(source, options) {
|
|
|
662
683
|
const selected = await p.multiselect({
|
|
663
684
|
message: "Select agents to install skills to",
|
|
664
685
|
options: allAgentChoices,
|
|
665
|
-
required: true
|
|
686
|
+
required: true,
|
|
687
|
+
initialValues: Object.keys(agents)
|
|
666
688
|
});
|
|
667
689
|
if (p.isCancel(selected)) {
|
|
668
690
|
p.cancel("Installation cancelled");
|
|
@@ -715,18 +737,32 @@ async function main(source, options) {
|
|
|
715
737
|
}
|
|
716
738
|
installGlobally = scope;
|
|
717
739
|
}
|
|
718
|
-
|
|
719
|
-
|
|
740
|
+
const summaryLines = [];
|
|
741
|
+
const maxAgentLen = Math.max(...targetAgents.map((a) => agents[a].displayName.length));
|
|
742
|
+
const overwriteStatus = /* @__PURE__ */ new Map();
|
|
743
|
+
for (const skill of selectedSkills) {
|
|
744
|
+
const agentStatus = /* @__PURE__ */ new Map();
|
|
745
|
+
for (const agent of targetAgents) {
|
|
746
|
+
agentStatus.set(agent, await isSkillInstalled(skill.name, agent, { global: installGlobally }));
|
|
747
|
+
}
|
|
748
|
+
overwriteStatus.set(skill.name, agentStatus);
|
|
749
|
+
}
|
|
720
750
|
for (const skill of selectedSkills) {
|
|
721
|
-
|
|
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")}`);
|
|
722
755
|
for (const agent of targetAgents) {
|
|
723
|
-
const
|
|
724
|
-
const
|
|
725
|
-
const
|
|
726
|
-
|
|
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}`);
|
|
727
762
|
}
|
|
728
763
|
}
|
|
729
764
|
console.log();
|
|
765
|
+
p.note(summaryLines.join("\n"), "Installation Summary");
|
|
730
766
|
if (!options.yes) {
|
|
731
767
|
const confirmed = await p.confirm({ message: "Proceed with installation?" });
|
|
732
768
|
if (p.isCancel(confirmed) || !confirmed) {
|
|
@@ -765,18 +801,33 @@ async function main(source, options) {
|
|
|
765
801
|
skillFiles: JSON.stringify(skillFiles)
|
|
766
802
|
});
|
|
767
803
|
if (successful.length > 0) {
|
|
768
|
-
|
|
804
|
+
const bySkill = /* @__PURE__ */ new Map();
|
|
769
805
|
for (const r of successful) {
|
|
770
|
-
|
|
771
|
-
|
|
806
|
+
const skillAgents = bySkill.get(r.skill) || [];
|
|
807
|
+
skillAgents.push(r.agent);
|
|
808
|
+
bySkill.set(r.skill, skillAgents);
|
|
809
|
+
}
|
|
810
|
+
const skillCount = bySkill.size;
|
|
811
|
+
const agentCount = new Set(successful.map((r) => r.agent)).size;
|
|
812
|
+
const resultLines = [];
|
|
813
|
+
for (const [skill, skillAgents] of bySkill) {
|
|
814
|
+
resultLines.push(`${chalk.green("\u2713")} ${chalk.bold(skill)}`);
|
|
815
|
+
for (const agent of skillAgents) {
|
|
816
|
+
resultLines.push(` ${chalk.dim(agent)}`);
|
|
817
|
+
}
|
|
818
|
+
resultLines.push("");
|
|
819
|
+
}
|
|
820
|
+
if (resultLines.length > 0 && resultLines[resultLines.length - 1] === "") {
|
|
821
|
+
resultLines.pop();
|
|
772
822
|
}
|
|
823
|
+
const title = chalk.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""} to ${agentCount} agent${agentCount !== 1 ? "s" : ""}`);
|
|
824
|
+
p.note(resultLines.join("\n"), title);
|
|
773
825
|
}
|
|
774
826
|
if (failed.length > 0) {
|
|
775
827
|
console.log();
|
|
776
|
-
p.log.error(chalk.red(`Failed to install ${failed.length}
|
|
828
|
+
p.log.error(chalk.red(`Failed to install ${failed.length}`));
|
|
777
829
|
for (const r of failed) {
|
|
778
|
-
p.log.message(` ${chalk.red("\u2717")} ${r.skill} \u2192 ${r.agent}`);
|
|
779
|
-
p.log.message(` ${chalk.dim(r.error)}`);
|
|
830
|
+
p.log.message(` ${chalk.red("\u2717")} ${r.skill} \u2192 ${r.agent}: ${chalk.dim(r.error)}`);
|
|
780
831
|
}
|
|
781
832
|
}
|
|
782
833
|
console.log();
|