skills 1.5.25 → 1.6.0
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 +12 -1
- package/dist/cli.mjs +218 -48
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,13 @@ The CLI for the open agent skills ecosystem.
|
|
|
6
6
|
Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [75 more](#supported-agents).
|
|
7
7
|
<!-- agent-list:end -->
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
<p>
|
|
10
|
+
<a href="https://vercel.com/labs#labs-products"><img alt="Vercel Labs Product" src="https://img.shields.io/badge/LABS-PRODUCT-0a0a0a.svg?style=for-the-badge&logo=Vercel&labelColor=000000" height="28"></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/skills"><img alt="npm version: skills" src="https://img.shields.io/npm/v/skills.svg?style=for-the-badge&labelColor=000000" height="28"></a>
|
|
12
|
+
<a href="https://github.com/vercel-labs/skills/blob/main/LICENSE"><img alt="License: MIT" src="https://img.shields.io/github/license/vercel-labs/skills.svg?style=for-the-badge&labelColor=000000" height="28"></a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/skills"><img alt="npm downloads per month: skills" src="https://img.shields.io/npm/dm/skills.svg?style=for-the-badge&labelColor=000000&label=npm%20downloads" height="28"></a>
|
|
14
|
+
<a href="https://skills.sh/vercel-labs/skills"><img alt="skills.sh" src="https://skills.sh/b/vercel-labs/skills?style=for-the-badge" height="28"></a>
|
|
15
|
+
</p>
|
|
10
16
|
|
|
11
17
|
## Install a Skill
|
|
12
18
|
|
|
@@ -40,6 +46,10 @@ npx skills add https://github.com/vercel-labs/agent-skills/tree/main/skills/web-
|
|
|
40
46
|
# GitLab URL
|
|
41
47
|
npx skills add https://gitlab.com/org/repo
|
|
42
48
|
|
|
49
|
+
# Azure Repos (Azure DevOps Services or Server)
|
|
50
|
+
npx skills add https://dev.azure.com/org/project/_git/repo
|
|
51
|
+
npx skills add https://dev.azure.com/org/project/_git/repo?path=/skills/web-design&version=GBmain
|
|
52
|
+
|
|
43
53
|
# Any git URL
|
|
44
54
|
npx skills add git@github.com:vercel-labs/agent-skills.git
|
|
45
55
|
|
|
@@ -61,6 +71,7 @@ npx skills add ssh://git@git.example.com/acme/private-skills.git
|
|
|
61
71
|
|
|
62
72
|
# HTTPS on any Git host (uses your configured Git credential helper)
|
|
63
73
|
npx skills add https://git.example.com/acme/private-skills.git
|
|
74
|
+
npx skills add https://dev.azure.com/org/project/_git/private-skills
|
|
64
75
|
```
|
|
65
76
|
|
|
66
77
|
For GitHub HTTPS and shorthand sources, `skills` first uses normal Git credentials. If that fails and GitHub CLI is authenticated, it tries `gh repo clone`, followed by SSH. It does not execute `gh auth token` or copy the stored GitHub CLI credential into the Node.js process.
|
package/dist/cli.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import { Writable } from "stream";
|
|
|
18
18
|
import { promisify } from "util";
|
|
19
19
|
import { execFile, spawn, spawnSync } from "child_process";
|
|
20
20
|
import { access, chmod, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
|
|
21
|
-
import { parse } from "yaml";
|
|
21
|
+
import { parse, stringify } from "yaml";
|
|
22
22
|
import { createHash } from "crypto";
|
|
23
23
|
import { mkdir as mkdir$1, mkdtemp as mkdtemp$1, readFile as readFile$1, rm as rm$1, stat as stat$1, writeFile as writeFile$1 } from "node:fs/promises";
|
|
24
24
|
import { createHash as createHash$1 } from "node:crypto";
|
|
@@ -91,6 +91,46 @@ function sanitizeSubpath(subpath) {
|
|
|
91
91
|
for (const segment of segments) if (segment === "..") throw new Error(`Unsafe subpath: "${subpath}" contains path traversal segments. Subpaths must not contain ".." components.`);
|
|
92
92
|
return subpath;
|
|
93
93
|
}
|
|
94
|
+
function azureReposVersionToRef(version) {
|
|
95
|
+
if (!version) return;
|
|
96
|
+
return /^(?:GB|GT)(.+)$/i.exec(version)?.[1] || void 0;
|
|
97
|
+
}
|
|
98
|
+
function parseAzureReposSource(input, fragmentRef, fragmentSkillFilter) {
|
|
99
|
+
if (!input.startsWith("http://") && !input.startsWith("https://")) return null;
|
|
100
|
+
let parsed;
|
|
101
|
+
try {
|
|
102
|
+
parsed = new URL(input);
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
107
|
+
const gitIndex = segments.indexOf("_git");
|
|
108
|
+
if (gitIndex < 0 || gitIndex === segments.length - 1) return null;
|
|
109
|
+
const repo = segments[gitIndex + 1].replace(/\.git$/, "");
|
|
110
|
+
if (!repo) return null;
|
|
111
|
+
const clonePath = "/" + [
|
|
112
|
+
...segments.slice(0, gitIndex),
|
|
113
|
+
"_git",
|
|
114
|
+
repo
|
|
115
|
+
].map((segment) => {
|
|
116
|
+
try {
|
|
117
|
+
return encodeURIComponent(decodeURIComponent(segment));
|
|
118
|
+
} catch {
|
|
119
|
+
return encodeURIComponent(segment);
|
|
120
|
+
}
|
|
121
|
+
}).join("/");
|
|
122
|
+
const cloneUrl = `${parsed.protocol}//${parsed.host}${clonePath}`;
|
|
123
|
+
const ref = azureReposVersionToRef(parsed.searchParams.get("version")) || fragmentRef;
|
|
124
|
+
const pathParam = parsed.searchParams.get("path");
|
|
125
|
+
const subpath = pathParam ? sanitizeSubpath(pathParam.replace(/^\/+/, "")) : void 0;
|
|
126
|
+
return {
|
|
127
|
+
type: "git",
|
|
128
|
+
url: cloneUrl,
|
|
129
|
+
...ref ? { ref } : {},
|
|
130
|
+
...subpath ? { subpath } : {},
|
|
131
|
+
...fragmentSkillFilter ? { skillFilter: fragmentSkillFilter } : {}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
94
134
|
function isLocalPath(input) {
|
|
95
135
|
return isAbsolute(input) || input.startsWith("./") || input.startsWith("../") || input === "." || input === ".." || /^[a-zA-Z]:[/\\]/.test(input);
|
|
96
136
|
}
|
|
@@ -113,6 +153,9 @@ function looksLikeGitSource(input) {
|
|
|
113
153
|
const pathname = parsed.pathname;
|
|
114
154
|
if (isGitHubHost(parsed.host)) return /^\/[^/]+\/[^/]+(?:\.git)?(?:\/tree\/[^/]+(?:\/.*)?)?\/?$/.test(pathname);
|
|
115
155
|
if (parsed.hostname === "gitlab.com") return /^\/.+?\/[^/]+(?:\.git)?(?:\/-\/tree\/[^/]+(?:\/.*)?)?\/?$/.test(pathname);
|
|
156
|
+
const azureSegments = pathname.split("/").filter(Boolean);
|
|
157
|
+
const azureGitIndex = azureSegments.indexOf("_git");
|
|
158
|
+
if (azureGitIndex >= 0 && azureGitIndex < azureSegments.length - 1) return true;
|
|
116
159
|
} catch {}
|
|
117
160
|
if (/^https?:\/\/.+\.git(?:$|[/?])/i.test(input)) return true;
|
|
118
161
|
return !input.includes(":") && !input.startsWith(".") && !input.startsWith("/") && /^([^/]+)\/([^/]+)(?:\/(.+)|@(.+))?$/.test(input);
|
|
@@ -245,6 +288,8 @@ function parseSource(input) {
|
|
|
245
288
|
...fragmentRef ? { ref: fragmentRef } : {}
|
|
246
289
|
};
|
|
247
290
|
}
|
|
291
|
+
const azureRepos = parseAzureReposSource(input, fragmentRef, fragmentSkillFilter);
|
|
292
|
+
if (azureRepos) return azureRepos;
|
|
248
293
|
const githubHost = getGitHubHost();
|
|
249
294
|
const shorthandSourceType = githubHost === "github.com" ? "github" : "git";
|
|
250
295
|
const atSkillMatch = input.match(/^([^/]+)\/([^/@]+)@(.+)$/);
|
|
@@ -1464,6 +1509,7 @@ const agents = {
|
|
|
1464
1509
|
displayName: "Claude Code",
|
|
1465
1510
|
skillsDir: ".claude/skills",
|
|
1466
1511
|
globalSkillsDir: join(claudeHome, "skills"),
|
|
1512
|
+
createProjectSkillsDirByDefault: true,
|
|
1467
1513
|
detectInstalled: async () => {
|
|
1468
1514
|
return existsSync(claudeHome);
|
|
1469
1515
|
}
|
|
@@ -2145,6 +2191,11 @@ function isPathSafe$2(basePath, targetPath) {
|
|
|
2145
2191
|
function pathsOverlap(pathA, pathB) {
|
|
2146
2192
|
return isPathSafe$2(pathA, pathB) || isPathSafe$2(pathB, pathA);
|
|
2147
2193
|
}
|
|
2194
|
+
function shouldSkipProjectAgentSymlink(agentType, isGlobal, cwd, createMissingAgentRoot) {
|
|
2195
|
+
if (isGlobal || isUniversalAgent(agentType) || createMissingAgentRoot || agents[agentType].createProjectSkillsDirByDefault) return false;
|
|
2196
|
+
const agentRoot = agents[agentType].skillsDir.split("/")[0];
|
|
2197
|
+
return !existsSync(join(cwd, agentRoot));
|
|
2198
|
+
}
|
|
2148
2199
|
async function isDirEntryOrSymlinkToDir(entry, entryPath) {
|
|
2149
2200
|
if (entry.isDirectory()) return true;
|
|
2150
2201
|
if (!entry.isSymbolicLink()) return false;
|
|
@@ -2280,15 +2331,14 @@ async function installSkillForAgent(skill, agentType, options = {}) {
|
|
|
2280
2331
|
canonicalPath: canonicalDir,
|
|
2281
2332
|
mode: "symlink"
|
|
2282
2333
|
};
|
|
2283
|
-
if (
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
}
|
|
2334
|
+
if (shouldSkipProjectAgentSymlink(agentType, isGlobal, cwd, options.createMissingAgentRoot ?? false)) return {
|
|
2335
|
+
success: true,
|
|
2336
|
+
path: canonicalDir,
|
|
2337
|
+
canonicalPath: canonicalDir,
|
|
2338
|
+
mode: "symlink",
|
|
2339
|
+
skipped: true,
|
|
2340
|
+
skipReason: "missing-agent-project-directory"
|
|
2341
|
+
};
|
|
2292
2342
|
if (!await createSymlink(canonicalDir, agentDir)) {
|
|
2293
2343
|
await cleanAndCreateDirectory(agentDir);
|
|
2294
2344
|
await copyDirectory(skill.path, agentDir, agentType);
|
|
@@ -2329,15 +2379,14 @@ const isExcluded$1 = (name, isDirectory = false) => {
|
|
|
2329
2379
|
function stripIgnoredEveFrontmatter(raw) {
|
|
2330
2380
|
const { data, content } = parseFrontmatter(raw);
|
|
2331
2381
|
const eveData = {};
|
|
2382
|
+
if (typeof data.name === "string") eveData.name = data.name;
|
|
2332
2383
|
if (typeof data.description === "string") eveData.description = data.description;
|
|
2333
2384
|
if (typeof data.license === "string") eveData.license = data.license;
|
|
2334
|
-
if (data.
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
if (keys.length === 0) return content.replace(/^\r?\n/u, "");
|
|
2340
|
-
return `---\n${keys.map((key) => `${key}: ${JSON.stringify(eveData[key])}`).join("\n")}\n---\n${content.replace(/^\r?\n/u, "")}`;
|
|
2385
|
+
if (data.compatibility !== void 0) eveData.compatibility = data.compatibility;
|
|
2386
|
+
if (typeof data.version === "string" || typeof data.version === "number") eveData.version = data.version;
|
|
2387
|
+
if (data.metadata && typeof data.metadata === "object" && !Array.isArray(data.metadata)) eveData.metadata = data.metadata;
|
|
2388
|
+
if (Object.keys(eveData).length === 0) return content.replace(/^\r?\n/u, "");
|
|
2389
|
+
return `---\n${stringify(eveData).trimEnd()}\n---\n${content.replace(/^\r?\n/u, "")}`;
|
|
2341
2390
|
}
|
|
2342
2391
|
async function copyDirectory(src, dest, agentType) {
|
|
2343
2392
|
await mkdir(dest, { recursive: true });
|
|
@@ -2571,15 +2620,14 @@ async function installBlobSkillForAgent(skill, agentType, options = {}) {
|
|
|
2571
2620
|
canonicalPath: canonicalDir,
|
|
2572
2621
|
mode: "symlink"
|
|
2573
2622
|
};
|
|
2574
|
-
if (
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
}
|
|
2623
|
+
if (shouldSkipProjectAgentSymlink(agentType, isGlobal, cwd, options.createMissingAgentRoot ?? false)) return {
|
|
2624
|
+
success: true,
|
|
2625
|
+
path: canonicalDir,
|
|
2626
|
+
canonicalPath: canonicalDir,
|
|
2627
|
+
mode: "symlink",
|
|
2628
|
+
skipped: true,
|
|
2629
|
+
skipReason: "missing-agent-project-directory"
|
|
2630
|
+
};
|
|
2583
2631
|
if (!await createSymlink(canonicalDir, agentDir)) {
|
|
2584
2632
|
await cleanAndCreateDirectory(agentDir);
|
|
2585
2633
|
await writeSkillFiles(agentDir);
|
|
@@ -4121,7 +4169,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
|
|
|
4121
4169
|
tree
|
|
4122
4170
|
};
|
|
4123
4171
|
}
|
|
4124
|
-
var version$1 = "1.
|
|
4172
|
+
var version$1 = "1.6.0";
|
|
4125
4173
|
const isCancelled$1 = (value) => typeof value === "symbol";
|
|
4126
4174
|
const EVE_AGENT_LABEL = "eve agent";
|
|
4127
4175
|
async function isSourcePrivate(source) {
|
|
@@ -4269,9 +4317,11 @@ function buildResultLines(results, targetAgents) {
|
|
|
4269
4317
|
const { universal, symlinked: symlinkAgents } = splitAgentsByType(targetAgents);
|
|
4270
4318
|
const successfulSymlinks = results.filter((r) => !r.symlinkFailed && !r.skipped && !universal.includes(r.agent)).map((r) => r.agent);
|
|
4271
4319
|
const failedSymlinks = results.filter((r) => r.symlinkFailed && !r.skipped).map((r) => r.agent);
|
|
4320
|
+
const skippedSymlinks = results.filter((r) => r.skipped && r.skipReason === "missing-agent-project-directory" && symlinkAgents.includes(r.agent)).map((r) => r.agent);
|
|
4272
4321
|
if (universal.length > 0) lines.push(` ${import_picocolors.default.green("universal:")} ${formatList$1(universal)}`);
|
|
4273
4322
|
if (successfulSymlinks.length > 0) lines.push(` ${import_picocolors.default.dim("symlinked:")} ${formatList$1(successfulSymlinks)}`);
|
|
4274
4323
|
if (failedSymlinks.length > 0) lines.push(` ${import_picocolors.default.yellow("copied:")} ${formatList$1(failedSymlinks)}`);
|
|
4324
|
+
if (skippedSymlinks.length > 0) lines.push(` ${import_picocolors.default.yellow("skipped:")} ${formatList$1(skippedSymlinks)} ${import_picocolors.default.dim("(project directory not found)")}`);
|
|
4275
4325
|
return lines;
|
|
4276
4326
|
}
|
|
4277
4327
|
function exitInstallationCancelled() {
|
|
@@ -4341,6 +4391,17 @@ async function selectAgentsInteractive(options) {
|
|
|
4341
4391
|
return selected;
|
|
4342
4392
|
}
|
|
4343
4393
|
setVersion(version$1);
|
|
4394
|
+
function buildJsonSecurity(auditData, skillName, source) {
|
|
4395
|
+
const data = auditData?.[skillName];
|
|
4396
|
+
if (!data || Object.keys(data).length === 0) return null;
|
|
4397
|
+
const socketAlerts = data.socket?.alerts ?? 0;
|
|
4398
|
+
return {
|
|
4399
|
+
...data.ath && { gen: data.ath.risk },
|
|
4400
|
+
...data.socket && { socket: `${socketAlerts} alert${socketAlerts !== 1 ? "s" : ""}` },
|
|
4401
|
+
...data.snyk && { snyk: data.snyk.risk },
|
|
4402
|
+
...source && { details: `https://skills.sh/${source}` }
|
|
4403
|
+
};
|
|
4404
|
+
}
|
|
4344
4405
|
function isSkillsShPackUrl(url) {
|
|
4345
4406
|
try {
|
|
4346
4407
|
const parsed = new URL(url);
|
|
@@ -4544,6 +4605,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
4544
4605
|
console.log();
|
|
4545
4606
|
const successful = results.filter((r) => r.success);
|
|
4546
4607
|
const failed = results.filter((r) => !r.success);
|
|
4608
|
+
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
4547
4609
|
const skillFiles = {};
|
|
4548
4610
|
for (const skill of selectedSkills) skillFiles[skill.installName] = skill.sourceUrl;
|
|
4549
4611
|
if (await wellKnownPrivacyPromise !== true) track({
|
|
@@ -4558,7 +4620,6 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
4558
4620
|
sourceType: "well-known"
|
|
4559
4621
|
});
|
|
4560
4622
|
if (successful.length > 0 && installGlobally) {
|
|
4561
|
-
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
4562
4623
|
for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
|
|
4563
4624
|
await addSkillToLock(skill.installName, {
|
|
4564
4625
|
source: sourceIdentifier,
|
|
@@ -4571,7 +4632,6 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
4571
4632
|
} catch {}
|
|
4572
4633
|
}
|
|
4573
4634
|
if (successful.length > 0 && !installGlobally) {
|
|
4574
|
-
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
4575
4635
|
for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
|
|
4576
4636
|
const matchingResult = successful.find((r) => r.skill === skill.installName);
|
|
4577
4637
|
const installDir = matchingResult?.canonicalPath || matchingResult?.path;
|
|
@@ -4638,6 +4698,40 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
4638
4698
|
async function runAdd(args, options = {}) {
|
|
4639
4699
|
const source = args[0];
|
|
4640
4700
|
let installTipShown = false;
|
|
4701
|
+
const jsonMode = options.json === true;
|
|
4702
|
+
const jsonResults = [];
|
|
4703
|
+
const originalStdoutWrite = process.stdout.write;
|
|
4704
|
+
let stdoutSuppressed = false;
|
|
4705
|
+
if (jsonMode) {
|
|
4706
|
+
process.stdout.write = process.stderr.write.bind(process.stderr);
|
|
4707
|
+
stdoutSuppressed = true;
|
|
4708
|
+
}
|
|
4709
|
+
const restoreStdout = () => {
|
|
4710
|
+
if (stdoutSuppressed) {
|
|
4711
|
+
process.stdout.write = originalStdoutWrite;
|
|
4712
|
+
stdoutSuppressed = false;
|
|
4713
|
+
}
|
|
4714
|
+
};
|
|
4715
|
+
let jsonEmitted = false;
|
|
4716
|
+
const emitJson = () => {
|
|
4717
|
+
if (!jsonMode || jsonEmitted) return;
|
|
4718
|
+
jsonEmitted = true;
|
|
4719
|
+
restoreStdout();
|
|
4720
|
+
console.log(JSON.stringify(jsonResults, null, 2));
|
|
4721
|
+
};
|
|
4722
|
+
const emitJsonAndExit = (code, errorMessage) => {
|
|
4723
|
+
if (jsonMode) {
|
|
4724
|
+
if (errorMessage !== void 0) console.error(errorMessage);
|
|
4725
|
+
if (code !== 0 && jsonResults.length === 0) jsonResults.push({
|
|
4726
|
+
status: "failed",
|
|
4727
|
+
error: errorMessage ?? "Installation failed"
|
|
4728
|
+
});
|
|
4729
|
+
emitJson();
|
|
4730
|
+
}
|
|
4731
|
+
process.exit(code);
|
|
4732
|
+
};
|
|
4733
|
+
const emitJsonOnExit = () => emitJson();
|
|
4734
|
+
if (jsonMode) process.once("exit", emitJsonOnExit);
|
|
4641
4735
|
const showInstallTip = () => {
|
|
4642
4736
|
if (installTipShown) return;
|
|
4643
4737
|
log.message(import_picocolors.default.dim("Tip: use the --yes (-y) and --global (-g) flags to install without prompts."));
|
|
@@ -4653,8 +4747,9 @@ async function runAdd(args, options = {}) {
|
|
|
4653
4747
|
console.log(import_picocolors.default.dim(" Example:"));
|
|
4654
4748
|
console.log(` ${import_picocolors.default.cyan("npx skills add")} ${import_picocolors.default.yellow("vercel-labs/agent-skills")}`);
|
|
4655
4749
|
console.log();
|
|
4656
|
-
|
|
4750
|
+
emitJsonAndExit(1, "Missing required argument: source");
|
|
4657
4751
|
}
|
|
4752
|
+
const explicitlySelectedAgents = new Set(options.agent?.includes("*") ? [] : options.agent ?? []);
|
|
4658
4753
|
if (options.all) {
|
|
4659
4754
|
options.skill = ["*"];
|
|
4660
4755
|
options.agent = ["*"];
|
|
@@ -4668,13 +4763,19 @@ async function runAdd(args, options = {}) {
|
|
|
4668
4763
|
if (mappedAgent) options.agent = ensureUniversalAgents([mappedAgent]);
|
|
4669
4764
|
}
|
|
4670
4765
|
}
|
|
4766
|
+
if (jsonMode && !options.yes) emitJsonAndExit(1, "The --json flag requires --yes (or --all) to run non-interactively.");
|
|
4767
|
+
if (jsonMode && options.list) emitJsonAndExit(1, "The --json flag cannot be combined with --list.");
|
|
4671
4768
|
console.log();
|
|
4672
4769
|
if (!agentResult.isAgent) intro(import_picocolors.default.bgCyan(import_picocolors.default.black(" skills ")));
|
|
4673
4770
|
if (agentResult.isAgent) log.info(import_picocolors.default.bgCyan(import_picocolors.default.black(import_picocolors.default.bold(` ${agentResult.agent.name} `))) + " Agent detected — installing non-interactively");
|
|
4674
4771
|
else if (!process.stdin.isTTY) showInstallTip();
|
|
4675
4772
|
let tempDir = null;
|
|
4676
4773
|
try {
|
|
4677
|
-
const spinner$3 =
|
|
4774
|
+
const spinner$3 = jsonMode ? {
|
|
4775
|
+
start: () => {},
|
|
4776
|
+
stop: () => {},
|
|
4777
|
+
message: () => {}
|
|
4778
|
+
} : spinner();
|
|
4678
4779
|
spinner$3.start("Parsing source…");
|
|
4679
4780
|
const parsed = parseSource(source);
|
|
4680
4781
|
let directDownload = parsed.type === "download";
|
|
@@ -4688,6 +4789,7 @@ async function runAdd(args, options = {}) {
|
|
|
4688
4789
|
return isRepoPrivate(ownerRepo.owner, ownerRepo.repo).catch(() => null);
|
|
4689
4790
|
})();
|
|
4690
4791
|
if (parsed.type === "well-known") {
|
|
4792
|
+
if (jsonMode) emitJsonAndExit(1, "--json is not yet supported for well-known skill sources.");
|
|
4691
4793
|
if (await handleWellKnownSkills(source, parsed.url, options, spinner$3)) return;
|
|
4692
4794
|
directDownload = true;
|
|
4693
4795
|
}
|
|
@@ -4703,7 +4805,7 @@ async function runAdd(args, options = {}) {
|
|
|
4703
4805
|
if (!existsSync(parsed.localPath)) {
|
|
4704
4806
|
spinner$3.stop(import_picocolors.default.red("Path not found"));
|
|
4705
4807
|
outro(import_picocolors.default.red(`Local path does not exist: ${parsed.localPath}`));
|
|
4706
|
-
|
|
4808
|
+
emitJsonAndExit(1, `Local path does not exist: ${parsed.localPath}`);
|
|
4707
4809
|
}
|
|
4708
4810
|
spinner$3.stop("Local path validated");
|
|
4709
4811
|
spinner$3.start("Discovering skills…");
|
|
@@ -4722,15 +4824,18 @@ async function runAdd(args, options = {}) {
|
|
|
4722
4824
|
fullDepth: options.fullDepth
|
|
4723
4825
|
});
|
|
4724
4826
|
} else if (parsed.type === "github" && !options.fullDepth) {
|
|
4827
|
+
let attemptedBlobInstall = false;
|
|
4725
4828
|
const BLOB_ALLOWED_OWNERS = [
|
|
4726
4829
|
"vercel",
|
|
4727
4830
|
"vercel-labs",
|
|
4728
|
-
"heygen-com"
|
|
4831
|
+
"heygen-com",
|
|
4832
|
+
"remotion-dev"
|
|
4729
4833
|
];
|
|
4730
4834
|
const ownerRepo = getOwnerRepo(parsed);
|
|
4731
4835
|
const owner = ownerRepo?.split("/")[0]?.toLowerCase();
|
|
4732
4836
|
const isSelfHostedRepo = !!ownerRepo && Object.hasOwn(BLOB_ALLOWED_REPOS, ownerRepo.toLowerCase());
|
|
4733
4837
|
if (ownerRepo && owner && (isSelfHostedRepo || BLOB_ALLOWED_OWNERS.includes(owner))) {
|
|
4838
|
+
attemptedBlobInstall = true;
|
|
4734
4839
|
spinner$3.start("Fetching skills…");
|
|
4735
4840
|
blobResult = await tryBlobInstall(ownerRepo, {
|
|
4736
4841
|
subpath: parsed.subpath,
|
|
@@ -4739,13 +4844,13 @@ async function runAdd(args, options = {}) {
|
|
|
4739
4844
|
getToken: getGitHubToken,
|
|
4740
4845
|
includeInternal
|
|
4741
4846
|
});
|
|
4742
|
-
if (!blobResult) spinner$3.stop(import_picocolors.default.dim("Falling back to clone…"));
|
|
4743
4847
|
}
|
|
4744
4848
|
if (blobResult) {
|
|
4745
4849
|
skills = blobResult.skills;
|
|
4746
4850
|
spinner$3.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
|
|
4747
4851
|
} else {
|
|
4748
|
-
spinner$3.
|
|
4852
|
+
if (attemptedBlobInstall) spinner$3.message("Cloning repository…");
|
|
4853
|
+
else spinner$3.start("Cloning repository…");
|
|
4749
4854
|
tempDir = await cloneRepo(parsed.url, parsed.ref);
|
|
4750
4855
|
spinner$3.stop("Repository cloned");
|
|
4751
4856
|
spinner$3.start("Discovering skills…");
|
|
@@ -4768,7 +4873,7 @@ async function runAdd(args, options = {}) {
|
|
|
4768
4873
|
spinner$3.stop(import_picocolors.default.red("No skills found"));
|
|
4769
4874
|
outro(import_picocolors.default.red("No valid skills found. Skills require a SKILL.md with name and description."));
|
|
4770
4875
|
await cleanup(tempDir);
|
|
4771
|
-
|
|
4876
|
+
emitJsonAndExit(1, "No valid skills found. Skills require a SKILL.md with name and description.");
|
|
4772
4877
|
}
|
|
4773
4878
|
if (!blobResult) spinner$3.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
|
|
4774
4879
|
if (options.list) {
|
|
@@ -4801,7 +4906,7 @@ async function runAdd(args, options = {}) {
|
|
|
4801
4906
|
console.log();
|
|
4802
4907
|
outro("Use --skill <name> to install specific skills");
|
|
4803
4908
|
await cleanup(tempDir);
|
|
4804
|
-
|
|
4909
|
+
emitJsonAndExit(0);
|
|
4805
4910
|
}
|
|
4806
4911
|
let selectedSkills;
|
|
4807
4912
|
if (options.skill?.includes("*")) {
|
|
@@ -4809,12 +4914,19 @@ async function runAdd(args, options = {}) {
|
|
|
4809
4914
|
log.info(`Installing all ${skills.length} skills`);
|
|
4810
4915
|
} else if (options.skill && options.skill.length > 0) {
|
|
4811
4916
|
selectedSkills = filterSkills(skills, options.skill);
|
|
4917
|
+
if (jsonMode) {
|
|
4918
|
+
for (const requested of options.skill) if (filterSkills(skills, [requested]).length === 0) jsonResults.push({
|
|
4919
|
+
name: requested,
|
|
4920
|
+
status: "skipped",
|
|
4921
|
+
reason: "No matching skill found in source"
|
|
4922
|
+
});
|
|
4923
|
+
}
|
|
4812
4924
|
if (selectedSkills.length === 0) {
|
|
4813
4925
|
log.error(`No matching skills found for: ${options.skill.join(", ")}`);
|
|
4814
4926
|
log.info("Available skills:");
|
|
4815
4927
|
for (const s of skills) log.message(` - ${getSkillDisplayName(s)}`);
|
|
4816
4928
|
await cleanup(tempDir);
|
|
4817
|
-
|
|
4929
|
+
emitJsonAndExit(1, `No matching skills found for: ${options.skill.join(", ")}`);
|
|
4818
4930
|
}
|
|
4819
4931
|
log.info(`Selected ${selectedSkills.length} skill${selectedSkills.length !== 1 ? "s" : ""}: ${selectedSkills.map((s) => import_picocolors.default.cyan(getSkillDisplayName(s))).join(", ")}`);
|
|
4820
4932
|
} else if (skills.length === 1) {
|
|
@@ -4870,7 +4982,7 @@ async function runAdd(args, options = {}) {
|
|
|
4870
4982
|
log.error(`Invalid agents: ${invalidAgents.join(", ")}`);
|
|
4871
4983
|
log.info(`Valid agents: ${validAgents.join(", ")}`);
|
|
4872
4984
|
await cleanup(tempDir);
|
|
4873
|
-
|
|
4985
|
+
emitJsonAndExit(1, `Invalid agents: ${invalidAgents.join(", ")}`);
|
|
4874
4986
|
}
|
|
4875
4987
|
targetAgents = options.agent;
|
|
4876
4988
|
} else {
|
|
@@ -4889,6 +5001,7 @@ async function runAdd(args, options = {}) {
|
|
|
4889
5001
|
}
|
|
4890
5002
|
if (useEve) {
|
|
4891
5003
|
targetAgents = ["eve"];
|
|
5004
|
+
if (!options.yes) explicitlySelectedAgents.add("eve");
|
|
4892
5005
|
log.info(`Installing to: ${import_picocolors.default.cyan(EVE_AGENT_LABEL)}`);
|
|
4893
5006
|
} else {
|
|
4894
5007
|
const selected = await selectAgentsInteractive({ global: options.global });
|
|
@@ -4897,6 +5010,7 @@ async function runAdd(args, options = {}) {
|
|
|
4897
5010
|
exitInstallationCancelled();
|
|
4898
5011
|
}
|
|
4899
5012
|
targetAgents = selected;
|
|
5013
|
+
for (const agent of targetAgents) explicitlySelectedAgents.add(agent);
|
|
4900
5014
|
}
|
|
4901
5015
|
} else if (installedAgents.length === 0) if (options.yes) {
|
|
4902
5016
|
targetAgents = validAgents;
|
|
@@ -4912,6 +5026,7 @@ async function runAdd(args, options = {}) {
|
|
|
4912
5026
|
exitInstallationCancelled();
|
|
4913
5027
|
}
|
|
4914
5028
|
targetAgents = selected;
|
|
5029
|
+
for (const agent of targetAgents) explicitlySelectedAgents.add(agent);
|
|
4915
5030
|
}
|
|
4916
5031
|
else if (installedAgents.length === 1 || options.yes) {
|
|
4917
5032
|
targetAgents = ensureUniversalAgents(installedAgents);
|
|
@@ -4926,9 +5041,13 @@ async function runAdd(args, options = {}) {
|
|
|
4926
5041
|
exitInstallationCancelled();
|
|
4927
5042
|
}
|
|
4928
5043
|
targetAgents = selected;
|
|
5044
|
+
for (const agent of targetAgents) explicitlySelectedAgents.add(agent);
|
|
4929
5045
|
}
|
|
4930
5046
|
}
|
|
4931
|
-
if (options.subagent && options.subagent.length > 0
|
|
5047
|
+
if (options.subagent && options.subagent.length > 0) {
|
|
5048
|
+
explicitlySelectedAgents.add("eve");
|
|
5049
|
+
if (!targetAgents.includes("eve")) targetAgents = [...targetAgents, "eve"];
|
|
5050
|
+
}
|
|
4932
5051
|
let eveSubagentTargets = [void 0];
|
|
4933
5052
|
if (targetAgents.includes("eve")) {
|
|
4934
5053
|
const availableSubagents = getEveSubagents(process.cwd());
|
|
@@ -5052,8 +5171,10 @@ async function runAdd(args, options = {}) {
|
|
|
5052
5171
|
}
|
|
5053
5172
|
console.log();
|
|
5054
5173
|
note(summaryLines.join("\n"), "Installation Summary");
|
|
5174
|
+
let auditDataForJson = null;
|
|
5055
5175
|
try {
|
|
5056
5176
|
const auditData = await auditPromise;
|
|
5177
|
+
auditDataForJson = auditData;
|
|
5057
5178
|
if (auditData && ownerRepoForAudit) {
|
|
5058
5179
|
const securityLines = buildSecurityLines(auditData, selectedSkills.map((s) => ({
|
|
5059
5180
|
slug: getSkillDisplayName(s),
|
|
@@ -5082,12 +5203,14 @@ async function runAdd(args, options = {}) {
|
|
|
5082
5203
|
}, agent, {
|
|
5083
5204
|
global: installGlobally,
|
|
5084
5205
|
mode: installMode,
|
|
5085
|
-
eveSubagent: subagent
|
|
5206
|
+
eveSubagent: subagent,
|
|
5207
|
+
createMissingAgentRoot: explicitlySelectedAgents.has(agent)
|
|
5086
5208
|
});
|
|
5087
5209
|
} else result = await installSkillForAgent(skill, agent, {
|
|
5088
5210
|
global: installGlobally,
|
|
5089
5211
|
mode: installMode,
|
|
5090
|
-
eveSubagent: subagent
|
|
5212
|
+
eveSubagent: subagent,
|
|
5213
|
+
createMissingAgentRoot: explicitlySelectedAgents.has(agent)
|
|
5091
5214
|
});
|
|
5092
5215
|
results.push({
|
|
5093
5216
|
skill: getSkillDisplayName(skill),
|
|
@@ -5100,6 +5223,7 @@ async function runAdd(args, options = {}) {
|
|
|
5100
5223
|
console.log();
|
|
5101
5224
|
const successful = results.filter((r) => r.success);
|
|
5102
5225
|
const failed = results.filter((r) => !r.success);
|
|
5226
|
+
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
5103
5227
|
const skillFiles = {};
|
|
5104
5228
|
for (const skill of selectedSkills) if (blobResult && "repoPath" in skill) skillFiles[skill.name] = skill.repoPath;
|
|
5105
5229
|
else if (tempDir && skill.path === tempDir) skillFiles[skill.name] = "SKILL.md";
|
|
@@ -5127,8 +5251,16 @@ async function runAdd(args, options = {}) {
|
|
|
5127
5251
|
skillFiles: JSON.stringify(skillFiles),
|
|
5128
5252
|
metadata: options.metadata
|
|
5129
5253
|
});
|
|
5254
|
+
const installedSkillHashes = /* @__PURE__ */ new Map();
|
|
5255
|
+
if (successful.length > 0 && (jsonMode || !installGlobally)) for (const skill of selectedSkills) {
|
|
5256
|
+
const skillDisplayName = getSkillDisplayName(skill);
|
|
5257
|
+
if (!successfulSkillNames.has(skillDisplayName)) continue;
|
|
5258
|
+
try {
|
|
5259
|
+
const computedHash = blobResult && "snapshotHash" in skill ? skill.snapshotHash : await computeSkillFolderHash(skill.path);
|
|
5260
|
+
installedSkillHashes.set(skillDisplayName, computedHash);
|
|
5261
|
+
} catch {}
|
|
5262
|
+
}
|
|
5130
5263
|
if (successful.length > 0 && installGlobally && normalizedSource) {
|
|
5131
|
-
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
5132
5264
|
let cachedTree;
|
|
5133
5265
|
if (parsed.type === "github" && !blobResult) cachedTree = await fetchRepoTree(normalizedSource, parsed.ref, getGitHubToken);
|
|
5134
5266
|
for (const skill of selectedSkills) {
|
|
@@ -5159,13 +5291,13 @@ async function runAdd(args, options = {}) {
|
|
|
5159
5291
|
}
|
|
5160
5292
|
}
|
|
5161
5293
|
if (successful.length > 0 && !installGlobally && !directDownload) {
|
|
5162
|
-
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
5163
5294
|
const eveSubagents = targetAgents.includes("eve") ? eveSubagentTargets.map((s) => s ?? "") : void 0;
|
|
5164
5295
|
const recordSubagents = eveSubagents && (eveSubagents.length > 1 || eveSubagents.some((s) => s !== ""));
|
|
5165
5296
|
for (const skill of selectedSkills) {
|
|
5166
5297
|
const skillDisplayName = getSkillDisplayName(skill);
|
|
5167
5298
|
if (successfulSkillNames.has(skillDisplayName)) try {
|
|
5168
|
-
const computedHash =
|
|
5299
|
+
const computedHash = installedSkillHashes.get(skillDisplayName);
|
|
5300
|
+
if (computedHash === void 0) continue;
|
|
5169
5301
|
const skillPathValue = skillFiles[skill.name];
|
|
5170
5302
|
await addSkillToLocalLock(skill.name, {
|
|
5171
5303
|
source: lockSource || parsed.url,
|
|
@@ -5179,6 +5311,37 @@ async function runAdd(args, options = {}) {
|
|
|
5179
5311
|
} catch {}
|
|
5180
5312
|
}
|
|
5181
5313
|
}
|
|
5314
|
+
if (jsonMode) {
|
|
5315
|
+
const jsonSource = normalizedSource ?? (parsed.type === "local" ? parsed.localPath : parsed.url);
|
|
5316
|
+
for (const skill of selectedSkills) {
|
|
5317
|
+
const name = getSkillDisplayName(skill);
|
|
5318
|
+
const skillResults = results.filter((r) => r.skill === name);
|
|
5319
|
+
const failures = skillResults.filter((r) => !r.success);
|
|
5320
|
+
if (failures.length > 0) {
|
|
5321
|
+
jsonResults.push({
|
|
5322
|
+
name,
|
|
5323
|
+
status: "failed",
|
|
5324
|
+
error: failures[0].error ?? "Installation failed"
|
|
5325
|
+
});
|
|
5326
|
+
continue;
|
|
5327
|
+
}
|
|
5328
|
+
jsonResults.push({
|
|
5329
|
+
name,
|
|
5330
|
+
status: "installed",
|
|
5331
|
+
source: jsonSource,
|
|
5332
|
+
ref: parsed.ref ?? null,
|
|
5333
|
+
hash: installedSkillHashes.get(name) ?? null,
|
|
5334
|
+
path: skillResults[0]?.canonicalPath ?? skillResults[0]?.path,
|
|
5335
|
+
scope: installGlobally ? "global" : "project",
|
|
5336
|
+
agents: skillResults.filter((r) => !r.skipped).map((r) => r.agent),
|
|
5337
|
+
mode: skillResults[0]?.mode ?? installMode,
|
|
5338
|
+
security: buildJsonSecurity(auditDataForJson, name, ownerRepoForAudit)
|
|
5339
|
+
});
|
|
5340
|
+
}
|
|
5341
|
+
emitJson();
|
|
5342
|
+
if (failed.length > 0 || jsonResults.some((result) => result.status === "skipped")) process.exitCode = 1;
|
|
5343
|
+
return;
|
|
5344
|
+
}
|
|
5182
5345
|
if (successful.length > 0) {
|
|
5183
5346
|
const bySkill = /* @__PURE__ */ new Map();
|
|
5184
5347
|
const groupedResults = {};
|
|
@@ -5256,8 +5419,10 @@ async function runAdd(args, options = {}) {
|
|
|
5256
5419
|
} else log.error(error instanceof Error ? error.message : "Unknown error occurred");
|
|
5257
5420
|
showInstallTip();
|
|
5258
5421
|
outro(import_picocolors.default.red("Installation failed"));
|
|
5259
|
-
|
|
5422
|
+
emitJsonAndExit(1, error instanceof GitCloneError ? `Failed to clone repository\n${error.message}` : error instanceof Error ? error.message : "Unknown error occurred");
|
|
5260
5423
|
} finally {
|
|
5424
|
+
if (jsonMode) process.removeListener("exit", emitJsonOnExit);
|
|
5425
|
+
restoreStdout();
|
|
5261
5426
|
await cleanup(tempDir);
|
|
5262
5427
|
}
|
|
5263
5428
|
}
|
|
@@ -5345,6 +5510,7 @@ function parseAddOptions(args) {
|
|
|
5345
5510
|
errors.push("--metadata must be valid JSON");
|
|
5346
5511
|
}
|
|
5347
5512
|
} else if (arg === "--full-depth") options.fullDepth = true;
|
|
5513
|
+
else if (arg === "--json") options.json = true;
|
|
5348
5514
|
else if (arg === "--copy") options.copy = true;
|
|
5349
5515
|
else if (arg === "--subagent") {
|
|
5350
5516
|
options.subagent = options.subagent || [];
|
|
@@ -7179,7 +7345,8 @@ async function runUpdate(args = []) {
|
|
|
7179
7345
|
const BLOB_ALLOWED_OWNERS = [
|
|
7180
7346
|
"vercel",
|
|
7181
7347
|
"vercel-labs",
|
|
7182
|
-
"heygen-com"
|
|
7348
|
+
"heygen-com",
|
|
7349
|
+
"remotion-dev"
|
|
7183
7350
|
];
|
|
7184
7351
|
const EXCLUDE_FILES = /* @__PURE__ */ new Set(["metadata.json"]);
|
|
7185
7352
|
const EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -7661,6 +7828,7 @@ ${BOLD}Add Options:${RESET}
|
|
|
7661
7828
|
--subagent <names> Install to Eve subagents (use 'root' for the root agent)
|
|
7662
7829
|
--all Shorthand for --skill '*' --agent '*' -y
|
|
7663
7830
|
--full-depth Search all subdirectories even when a root SKILL.md exists
|
|
7831
|
+
--json Output results as JSON (machine-readable, no ANSI codes)
|
|
7664
7832
|
|
|
7665
7833
|
${BOLD}Use Options:${RESET}
|
|
7666
7834
|
-s, --skill <skill> Specify the skill to use
|
|
@@ -7694,6 +7862,7 @@ ${BOLD}Examples:${RESET}
|
|
|
7694
7862
|
${DIM}$${RESET} skills add vercel-labs/agent-skills -g
|
|
7695
7863
|
${DIM}$${RESET} skills add vercel-labs/agent-skills --agent claude-code cursor
|
|
7696
7864
|
${DIM}$${RESET} skills add vercel-labs/agent-skills --skill pr-review commit
|
|
7865
|
+
${DIM}$${RESET} skills add vercel-labs/agent-skills --json -y ${DIM}# JSON output${RESET}
|
|
7697
7866
|
${DIM}$${RESET} skills remove ${DIM}# interactive remove${RESET}
|
|
7698
7867
|
${DIM}$${RESET} skills remove web-design ${DIM}# remove by name${RESET}
|
|
7699
7868
|
${DIM}$${RESET} skills rm --global frontend-design
|
|
@@ -7828,10 +7997,11 @@ async function main() {
|
|
|
7828
7997
|
case "install":
|
|
7829
7998
|
case "a":
|
|
7830
7999
|
case "add": {
|
|
7831
|
-
if (!inAgent) showLogo();
|
|
7832
8000
|
const { source: addSource, options: addOpts, errors } = parseAddOptions(restArgs);
|
|
8001
|
+
if (!inAgent && !addOpts.json) showLogo();
|
|
7833
8002
|
if (errors.length > 0) {
|
|
7834
8003
|
for (const error of errors) console.error(`Error: ${error}`);
|
|
8004
|
+
if (addOpts.json) console.log("[]");
|
|
7835
8005
|
process.exitCode = 1;
|
|
7836
8006
|
break;
|
|
7837
8007
|
}
|