skills 1.5.19 → 1.5.20
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 +5 -1
- package/dist/cli.mjs +206 -62
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
The CLI for the open agent skills ecosystem.
|
|
4
4
|
|
|
5
5
|
<!-- agent-list:start -->
|
|
6
|
-
Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [
|
|
6
|
+
Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [70 more](#supported-agents).
|
|
7
7
|
<!-- agent-list:end -->
|
|
8
8
|
|
|
9
9
|
[](https://skills.sh/vercel-labs/skills)
|
|
@@ -272,12 +272,14 @@ Skills can be installed to any of these agents:
|
|
|
272
272
|
| Gemini CLI | `gemini-cli` | `.agents/skills/` | `~/.gemini/skills/` |
|
|
273
273
|
| GitHub Copilot | `github-copilot` | `.agents/skills/` | `~/.copilot/skills/` |
|
|
274
274
|
| Goose | `goose` | `.goose/skills/` | `~/.config/goose/skills/` |
|
|
275
|
+
| Grok Build | `grok` | `.grok/skills/` | `~/.grok/skills/` |
|
|
275
276
|
| Hermes Agent | `hermes-agent` | `.hermes/skills/` | `~/.hermes/skills/` |
|
|
276
277
|
| inference.sh | `inference-sh` | `.inferencesh/skills/` | `~/.inferencesh/skills/` |
|
|
277
278
|
| Jazz | `jazz` | `.jazz/skills/` | `~/.jazz/skills/` |
|
|
278
279
|
| Junie | `junie` | `.junie/skills/` | `~/.junie/skills/` |
|
|
279
280
|
| iFlow CLI | `iflow-cli` | `.iflow/skills/` | `~/.iflow/skills/` |
|
|
280
281
|
| Kilo Code | `kilo` | `.kilocode/skills/` | `~/.kilocode/skills/` |
|
|
282
|
+
| Kimchi | `kimchi` | `.kimchi/skills/` | `~/.config/kimchi/harness/skills/` |
|
|
281
283
|
| Kiro CLI | `kiro-cli` | `.kiro/skills/` | `~/.kiro/skills/` |
|
|
282
284
|
| Kode | `kode` | `.kode/skills/` | `~/.kode/skills/` |
|
|
283
285
|
| Lingma | `lingma` | `.lingma/skills/` | `~/.lingma/skills/` |
|
|
@@ -402,12 +404,14 @@ to also discover `SKILL.md` files outside these container directories
|
|
|
402
404
|
- `agent/skills/`
|
|
403
405
|
- `.forge/skills/`
|
|
404
406
|
- `.goose/skills/`
|
|
407
|
+
- `.grok/skills/`
|
|
405
408
|
- `.hermes/skills/`
|
|
406
409
|
- `.inferencesh/skills/`
|
|
407
410
|
- `.jazz/skills/`
|
|
408
411
|
- `.junie/skills/`
|
|
409
412
|
- `.iflow/skills/`
|
|
410
413
|
- `.kilocode/skills/`
|
|
414
|
+
- `.kimchi/skills/`
|
|
411
415
|
- `.kiro/skills/`
|
|
412
416
|
- `.kode/skills/`
|
|
413
417
|
- `.lingma/skills/`
|
package/dist/cli.mjs
CHANGED
|
@@ -21,6 +21,22 @@ import { createHash } from "crypto";
|
|
|
21
21
|
import { createHash as createHash$1 } from "node:crypto";
|
|
22
22
|
import { gunzipSync, inflateRawSync } from "node:zlib";
|
|
23
23
|
var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
|
|
24
|
+
const DEFAULT_GITHUB_HOST = "github.com";
|
|
25
|
+
function getGitHubHost() {
|
|
26
|
+
const configuredHost = process.env.GH_HOST?.trim();
|
|
27
|
+
if (!configuredHost) return DEFAULT_GITHUB_HOST;
|
|
28
|
+
try {
|
|
29
|
+
const parsed = new URL(`https://${configuredHost}`);
|
|
30
|
+
if (parsed.username || parsed.password || parsed.port || parsed.pathname !== "/" || parsed.search || parsed.hash) return DEFAULT_GITHUB_HOST;
|
|
31
|
+
return parsed.hostname;
|
|
32
|
+
} catch {
|
|
33
|
+
return DEFAULT_GITHUB_HOST;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function isGitHubHost(host) {
|
|
37
|
+
const normalizedHost = host.toLowerCase();
|
|
38
|
+
return normalizedHost === DEFAULT_GITHUB_HOST || normalizedHost === getGitHubHost().toLowerCase();
|
|
39
|
+
}
|
|
24
40
|
function getOwnerRepo(parsed) {
|
|
25
41
|
if (parsed.type === "local") return null;
|
|
26
42
|
const sshMatch = parsed.url.match(/^git@[^:]+:(.+)$/);
|
|
@@ -88,7 +104,7 @@ function looksLikeGitSource(input) {
|
|
|
88
104
|
if (input.startsWith("http://") || input.startsWith("https://")) try {
|
|
89
105
|
const parsed = new URL(input);
|
|
90
106
|
const pathname = parsed.pathname;
|
|
91
|
-
if (parsed.
|
|
107
|
+
if (isGitHubHost(parsed.host)) return /^\/[^/]+\/[^/]+(?:\.git)?(?:\/tree\/[^/]+(?:\/.*)?)?\/?$/.test(pathname);
|
|
92
108
|
if (parsed.hostname === "gitlab.com") return /^\/.+?\/[^/]+(?:\.git)?(?:\/-\/tree\/[^/]+(?:\/.*)?)?\/?$/.test(pathname);
|
|
93
109
|
} catch {}
|
|
94
110
|
if (/^https?:\/\/.+\.git(?:$|[/?])/i.test(input)) return true;
|
|
@@ -134,6 +150,22 @@ function parseSource(input) {
|
|
|
134
150
|
if (githubPrefixMatch) return parseSource(appendFragmentRef(githubPrefixMatch[1], fragmentRef, fragmentSkillFilter));
|
|
135
151
|
const gitlabPrefixMatch = input.match(/^gitlab:(.+)$/);
|
|
136
152
|
if (gitlabPrefixMatch) return parseSource(appendFragmentRef(`https://gitlab.com/${gitlabPrefixMatch[1]}`, fragmentRef, fragmentSkillFilter));
|
|
153
|
+
if (getGitHubHost() !== "github.com" && /^https?:\/\//.test(input)) try {
|
|
154
|
+
const parsedUrl = new URL(input);
|
|
155
|
+
if (isGitHubHost(parsedUrl.host) && parsedUrl.host !== "github.com") {
|
|
156
|
+
const [owner, rawRepo, marker, ref, ...subpathSegments] = parsedUrl.pathname.split("/").filter(Boolean);
|
|
157
|
+
if (owner && rawRepo) {
|
|
158
|
+
const repo = rawRepo.replace(/\.git$/, "");
|
|
159
|
+
const isTreeUrl = marker === "tree" && ref;
|
|
160
|
+
return {
|
|
161
|
+
type: "git",
|
|
162
|
+
url: `${parsedUrl.protocol}//${parsedUrl.host}/${owner}/${repo}.git`,
|
|
163
|
+
...isTreeUrl ? { ref } : fragmentRef ? { ref: fragmentRef } : {},
|
|
164
|
+
...isTreeUrl && subpathSegments.length > 0 ? { subpath: sanitizeSubpath(subpathSegments.join("/")) } : {}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} catch {}
|
|
137
169
|
const githubTreeWithPathMatch = input.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)/);
|
|
138
170
|
if (githubTreeWithPathMatch) {
|
|
139
171
|
const [, owner, repo, ref, subpath] = githubTreeWithPathMatch;
|
|
@@ -190,12 +222,14 @@ function parseSource(input) {
|
|
|
190
222
|
...fragmentRef ? { ref: fragmentRef } : {}
|
|
191
223
|
};
|
|
192
224
|
}
|
|
225
|
+
const githubHost = getGitHubHost();
|
|
226
|
+
const shorthandSourceType = githubHost === "github.com" ? "github" : "git";
|
|
193
227
|
const atSkillMatch = input.match(/^([^/]+)\/([^/@]+)@(.+)$/);
|
|
194
228
|
if (atSkillMatch && !input.includes(":") && !input.startsWith(".") && !input.startsWith("/")) {
|
|
195
229
|
const [, owner, repo, skillFilter] = atSkillMatch;
|
|
196
230
|
return {
|
|
197
|
-
type:
|
|
198
|
-
url: `https
|
|
231
|
+
type: shorthandSourceType,
|
|
232
|
+
url: `https://${githubHost}/${owner}/${repo}.git`,
|
|
199
233
|
...fragmentRef ? { ref: fragmentRef } : {},
|
|
200
234
|
skillFilter: fragmentSkillFilter || skillFilter
|
|
201
235
|
};
|
|
@@ -204,8 +238,8 @@ function parseSource(input) {
|
|
|
204
238
|
if (shorthandMatch && !input.includes(":") && !input.startsWith(".") && !input.startsWith("/")) {
|
|
205
239
|
const [, owner, repo, subpath] = shorthandMatch;
|
|
206
240
|
return {
|
|
207
|
-
type:
|
|
208
|
-
url: `https
|
|
241
|
+
type: shorthandSourceType,
|
|
242
|
+
url: `https://${githubHost}/${owner}/${repo}.git`,
|
|
209
243
|
...fragmentRef ? { ref: fragmentRef } : {},
|
|
210
244
|
subpath: subpath ? sanitizeSubpath(subpath) : subpath,
|
|
211
245
|
...fragmentSkillFilter ? { skillFilter: fragmentSkillFilter } : {}
|
|
@@ -573,20 +607,21 @@ var GitCloneError = class extends Error {
|
|
|
573
607
|
}
|
|
574
608
|
};
|
|
575
609
|
function parseGitHubRepoUrl(url) {
|
|
576
|
-
const sshMatch = url.match(/^git@
|
|
577
|
-
if (sshMatch) {
|
|
578
|
-
const
|
|
579
|
-
const
|
|
610
|
+
const sshMatch = url.match(/^git@([^:]+):([^/]+)\/([^/]+?)(?:\.git)?$/i);
|
|
611
|
+
if (sshMatch && isGitHubHost(sshMatch[1])) {
|
|
612
|
+
const host = sshMatch[1];
|
|
613
|
+
const owner = sshMatch[2];
|
|
614
|
+
const repo = sshMatch[3];
|
|
580
615
|
return {
|
|
581
616
|
owner,
|
|
582
617
|
repo,
|
|
583
618
|
slug: `${owner}/${repo}`,
|
|
584
|
-
sshUrl: `git
|
|
619
|
+
sshUrl: `git@${host}:${owner}/${repo}.git`
|
|
585
620
|
};
|
|
586
621
|
}
|
|
587
622
|
try {
|
|
588
623
|
const parsed = new URL(url);
|
|
589
|
-
if (parsed.
|
|
624
|
+
if (!isGitHubHost(parsed.host)) return null;
|
|
590
625
|
const match = parsed.pathname.match(/^\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
|
|
591
626
|
if (!match) return null;
|
|
592
627
|
const owner = match[1];
|
|
@@ -595,7 +630,7 @@ function parseGitHubRepoUrl(url) {
|
|
|
595
630
|
owner,
|
|
596
631
|
repo,
|
|
597
632
|
slug: `${owner}/${repo}`,
|
|
598
|
-
sshUrl: `git
|
|
633
|
+
sshUrl: `git@${parsed.host}:${owner}/${repo}.git`
|
|
599
634
|
};
|
|
600
635
|
} catch {
|
|
601
636
|
return null;
|
|
@@ -604,7 +639,7 @@ function parseGitHubRepoUrl(url) {
|
|
|
604
639
|
function isGitHubHttpsCloneUrl(url) {
|
|
605
640
|
try {
|
|
606
641
|
const parsed = new URL(url);
|
|
607
|
-
return parsed.protocol === "https:" && parsed.
|
|
642
|
+
return parsed.protocol === "https:" && isGitHubHost(parsed.host);
|
|
608
643
|
} catch {
|
|
609
644
|
return false;
|
|
610
645
|
}
|
|
@@ -665,12 +700,13 @@ async function resetTempDir(dir) {
|
|
|
665
700
|
}
|
|
666
701
|
async function tryGhClone(repo, tempDir, ref) {
|
|
667
702
|
let cloneTarget = repo.slug;
|
|
703
|
+
const host = repo.sshUrl.match(/^git@([^:]+):/)?.[1] || "github.com";
|
|
668
704
|
try {
|
|
669
705
|
const { stdout, stderr } = await execFileAsync("gh", [
|
|
670
706
|
"auth",
|
|
671
707
|
"status",
|
|
672
708
|
"-h",
|
|
673
|
-
|
|
709
|
+
host
|
|
674
710
|
], {
|
|
675
711
|
timeout: 5e3,
|
|
676
712
|
env: {
|
|
@@ -705,8 +741,9 @@ async function tryGhClone(repo, tempDir, ref) {
|
|
|
705
741
|
return true;
|
|
706
742
|
}
|
|
707
743
|
function buildGitHubAuthError(url, repo, message) {
|
|
708
|
-
|
|
709
|
-
if (repo) return `
|
|
744
|
+
const host = repo?.sshUrl.match(/^git@([^:]+):/)?.[1] || "github.com";
|
|
745
|
+
if (repo && isGitHubSsoAuthError(message)) return `GitHub blocked HTTPS access to ${url} because the organization enforces SAML SSO.\n skills tried your existing git credentials and available fallbacks, but none succeeded.\n - Re-authorize your GitHub credentials/app for that org's SSO policy\n - Or rerun with SSH: npx skills add ${repo.sshUrl}\n - Verify access with: gh auth status -h ${host} or ssh -T git@${host}`;
|
|
746
|
+
if (repo) return `Authentication failed for ${url}.\n - For private repos, ensure you have access\n - Retry with SSH: npx skills add ${repo.sshUrl}\n - Check access with: gh auth status -h ${host} or ssh -T git@${host}`;
|
|
710
747
|
return `Authentication failed for ${url}.\n - For private repos, ensure you have access\n - For SSH: Check your keys with 'ssh -T git@github.com'\n - For HTTPS: Run 'gh auth login' or configure git credentials`;
|
|
711
748
|
}
|
|
712
749
|
async function cloneRepo(url, ref) {
|
|
@@ -897,6 +934,13 @@ async function addSkillToLocalLock(skillName, entry, cwd) {
|
|
|
897
934
|
lock.skills[skillName] = entry;
|
|
898
935
|
await writeLocalLock(lock, cwd);
|
|
899
936
|
}
|
|
937
|
+
async function removeSkillFromLocalLock(skillName, cwd) {
|
|
938
|
+
const lock = await readLocalLock(cwd);
|
|
939
|
+
if (!(skillName in lock.skills)) return false;
|
|
940
|
+
delete lock.skills[skillName];
|
|
941
|
+
await writeLocalLock(lock, cwd);
|
|
942
|
+
return true;
|
|
943
|
+
}
|
|
900
944
|
function createEmptyLocalLock() {
|
|
901
945
|
return {
|
|
902
946
|
version: CURRENT_VERSION$1,
|
|
@@ -920,9 +964,11 @@ const AGENT_PROJECT_SKILL_DIRS = [
|
|
|
920
964
|
".continue/skills",
|
|
921
965
|
".github/skills",
|
|
922
966
|
".goose/skills",
|
|
967
|
+
".grok/skills",
|
|
923
968
|
".iflow/skills",
|
|
924
969
|
".junie/skills",
|
|
925
970
|
".kilocode/skills",
|
|
971
|
+
".kimchi/skills",
|
|
926
972
|
".kiro/skills",
|
|
927
973
|
".mux/skills",
|
|
928
974
|
".neovate/skills",
|
|
@@ -956,24 +1002,44 @@ async function hasSkillMd(dir) {
|
|
|
956
1002
|
return false;
|
|
957
1003
|
}
|
|
958
1004
|
}
|
|
1005
|
+
function warnSkippedSkill(skillMdPath, reason) {
|
|
1006
|
+
console.warn(`⚠ Skipped ${sanitizeMetadata(skillMdPath)} — ${stripTerminalEscapes(reason)}`);
|
|
1007
|
+
}
|
|
959
1008
|
async function parseSkillMd(skillMdPath, options) {
|
|
1009
|
+
let content;
|
|
960
1010
|
try {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1011
|
+
content = await readFile(skillMdPath, "utf-8");
|
|
1012
|
+
} catch (err) {
|
|
1013
|
+
warnSkippedSkill(skillMdPath, `failed to read file: ${err.message}`);
|
|
1014
|
+
return null;
|
|
1015
|
+
}
|
|
1016
|
+
let data;
|
|
1017
|
+
try {
|
|
1018
|
+
({data} = parseFrontmatter(content));
|
|
1019
|
+
} catch (err) {
|
|
1020
|
+
warnSkippedSkill(skillMdPath, `YAML parse error: ${err.message}`);
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
if (!data.name || !data.description) {
|
|
1024
|
+
const missing = [];
|
|
1025
|
+
if (!data.name) missing.push("name");
|
|
1026
|
+
if (!data.description) missing.push("description");
|
|
1027
|
+
warnSkippedSkill(skillMdPath, `missing required frontmatter field(s): ${missing.join(", ")}`);
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
if (typeof data.name !== "string" || typeof data.description !== "string") {
|
|
1031
|
+
warnSkippedSkill(skillMdPath, `frontmatter "name" and "description" must be strings (got ${typeof data.name} and ${typeof data.description})`);
|
|
975
1032
|
return null;
|
|
976
1033
|
}
|
|
1034
|
+
const metadata = isRecord(data.metadata) ? data.metadata : void 0;
|
|
1035
|
+
if (metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) return null;
|
|
1036
|
+
return {
|
|
1037
|
+
name: sanitizeMetadata(data.name),
|
|
1038
|
+
description: sanitizeMetadata(data.description),
|
|
1039
|
+
path: dirname(skillMdPath),
|
|
1040
|
+
rawContent: content,
|
|
1041
|
+
metadata
|
|
1042
|
+
};
|
|
977
1043
|
}
|
|
978
1044
|
async function findSkillDirs(dir, depth = 0, maxDepth = 5) {
|
|
979
1045
|
if (depth > maxDepth) return [];
|
|
@@ -994,6 +1060,7 @@ function isSubpathSafe(basePath, subpath) {
|
|
|
994
1060
|
async function discoverSkills(basePath, subpath, options) {
|
|
995
1061
|
const skills = [];
|
|
996
1062
|
const seenNames = /* @__PURE__ */ new Set();
|
|
1063
|
+
const parsedSkillPaths = /* @__PURE__ */ new Set();
|
|
997
1064
|
const localLock = await readLocalLock(basePath);
|
|
998
1065
|
const lockedSkillNames = new Set(Object.keys(localLock.skills).map(normalizeSkillName));
|
|
999
1066
|
if (subpath && !isSubpathSafe(basePath, subpath)) throw new Error(`Invalid subpath: "${subpath}" resolves outside the repository directory. Subpath must not contain ".." segments that escape the base path.`);
|
|
@@ -1012,8 +1079,14 @@ async function discoverSkills(basePath, subpath, options) {
|
|
|
1012
1079
|
const directoryName = normalizeSkillName(basename(skill.path));
|
|
1013
1080
|
return lockedSkillNames.has(skillName) || lockedSkillNames.has(directoryName);
|
|
1014
1081
|
};
|
|
1082
|
+
const parseSkillAt = async (skillDir) => {
|
|
1083
|
+
const skillMdPath = resolve(skillDir, "SKILL.md");
|
|
1084
|
+
if (parsedSkillPaths.has(skillMdPath)) return null;
|
|
1085
|
+
parsedSkillPaths.add(skillMdPath);
|
|
1086
|
+
return parseSkillMd(skillMdPath, options);
|
|
1087
|
+
};
|
|
1015
1088
|
if (await hasSkillMd(searchPath)) {
|
|
1016
|
-
let skill = await
|
|
1089
|
+
let skill = await parseSkillAt(searchPath);
|
|
1017
1090
|
if (skill) {
|
|
1018
1091
|
if (!isInstalledProjectSkill(skill)) {
|
|
1019
1092
|
skill = enhanceSkill(skill);
|
|
@@ -1035,7 +1108,7 @@ async function discoverSkills(basePath, subpath, options) {
|
|
|
1035
1108
|
prioritySearchDirs.push(...await getPluginSkillPaths(searchPath));
|
|
1036
1109
|
const tryAddSkillAt = async (skillDir) => {
|
|
1037
1110
|
if (!await hasSkillMd(skillDir)) return false;
|
|
1038
|
-
let skill = await
|
|
1111
|
+
let skill = await parseSkillAt(skillDir);
|
|
1039
1112
|
if (!skill || seenNames.has(skill.name)) return true;
|
|
1040
1113
|
if (isInstalledProjectSkill(skill)) return true;
|
|
1041
1114
|
skill = enhanceSkill(skill);
|
|
@@ -1065,7 +1138,7 @@ async function discoverSkills(basePath, subpath, options) {
|
|
|
1065
1138
|
if (skills.length === 0 || options?.fullDepth) {
|
|
1066
1139
|
const allSkillDirs = await findSkillDirs(searchPath);
|
|
1067
1140
|
for (const skillDir of allSkillDirs) {
|
|
1068
|
-
let skill = await
|
|
1141
|
+
let skill = await parseSkillAt(skillDir);
|
|
1069
1142
|
if (skill && !seenNames.has(skill.name) && !isInstalledProjectSkill(skill)) {
|
|
1070
1143
|
skill = enhanceSkill(skill);
|
|
1071
1144
|
skills.push(skill);
|
|
@@ -1093,6 +1166,7 @@ const claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude"
|
|
|
1093
1166
|
const vibeHome = process.env.VIBE_HOME?.trim() || join(home, ".vibe");
|
|
1094
1167
|
const hermesHome = process.env.HERMES_HOME?.trim() || join(home, ".hermes");
|
|
1095
1168
|
const autohandHome = process.env.AUTOHAND_HOME?.trim() || join(home, ".autohand");
|
|
1169
|
+
const grokHome = process.env.GROK_HOME?.trim() || join(home, ".grok");
|
|
1096
1170
|
const zedAppDataHome = process.env.APPDATA?.trim();
|
|
1097
1171
|
const zedFlatpakConfigHome = process.env.FLATPAK_XDG_CONFIG_HOME?.trim();
|
|
1098
1172
|
function packageJsonHasDependency(packageJsonPath, dependencyName) {
|
|
@@ -1112,6 +1186,9 @@ function getOpenClawGlobalSkillsDir(homeDir = home, pathExists = existsSync) {
|
|
|
1112
1186
|
function isZCodeInstalled(homeDir = home, pathExists = existsSync) {
|
|
1113
1187
|
return pathExists(join(homeDir, ".zcode")) || pathExists("/Applications/ZCode.app");
|
|
1114
1188
|
}
|
|
1189
|
+
function isKimchiInstalled(homeDir = home, pathExists = existsSync) {
|
|
1190
|
+
return pathExists(join(homeDir, ".config", "kimchi"));
|
|
1191
|
+
}
|
|
1115
1192
|
const agents = {
|
|
1116
1193
|
"aider-desk": {
|
|
1117
1194
|
name: "aider-desk",
|
|
@@ -1395,6 +1472,15 @@ const agents = {
|
|
|
1395
1472
|
return existsSync(join(configHome, "goose"));
|
|
1396
1473
|
}
|
|
1397
1474
|
},
|
|
1475
|
+
grok: {
|
|
1476
|
+
name: "grok",
|
|
1477
|
+
displayName: "Grok Build",
|
|
1478
|
+
skillsDir: ".grok/skills",
|
|
1479
|
+
globalSkillsDir: join(grokHome, "skills"),
|
|
1480
|
+
detectInstalled: async () => {
|
|
1481
|
+
return existsSync(grokHome);
|
|
1482
|
+
}
|
|
1483
|
+
},
|
|
1398
1484
|
"hermes-agent": {
|
|
1399
1485
|
name: "hermes-agent",
|
|
1400
1486
|
displayName: "Hermes Agent",
|
|
@@ -1449,6 +1535,15 @@ const agents = {
|
|
|
1449
1535
|
return existsSync(join(home, ".kilocode"));
|
|
1450
1536
|
}
|
|
1451
1537
|
},
|
|
1538
|
+
kimchi: {
|
|
1539
|
+
name: "kimchi",
|
|
1540
|
+
displayName: "Kimchi",
|
|
1541
|
+
skillsDir: ".kimchi/skills",
|
|
1542
|
+
globalSkillsDir: join(home, ".config", "kimchi", "harness", "skills"),
|
|
1543
|
+
detectInstalled: async () => {
|
|
1544
|
+
return isKimchiInstalled();
|
|
1545
|
+
}
|
|
1546
|
+
},
|
|
1452
1547
|
"kimi-code-cli": {
|
|
1453
1548
|
name: "kimi-code-cli",
|
|
1454
1549
|
displayName: "Kimi Code CLI",
|
|
@@ -2244,7 +2339,7 @@ async function installBlobSkillForAgent(skill, agentType, options = {}) {
|
|
|
2244
2339
|
mode: "symlink"
|
|
2245
2340
|
};
|
|
2246
2341
|
if (!isGlobal && !isUniversalAgent(agentType)) {
|
|
2247
|
-
if (!existsSync(join(cwd, agents[agentType].skillsDir.split("/")[0]))) return {
|
|
2342
|
+
if (!existsSync(join(cwd, agents[agentType].skillsDir.split("/")[0])) && agentType !== "claude-code") return {
|
|
2248
2343
|
success: true,
|
|
2249
2344
|
path: canonicalDir,
|
|
2250
2345
|
canonicalPath: canonicalDir,
|
|
@@ -3165,9 +3260,11 @@ const PRIORITY_PREFIXES = [
|
|
|
3165
3260
|
".continue/skills/",
|
|
3166
3261
|
".github/skills/",
|
|
3167
3262
|
".goose/skills/",
|
|
3263
|
+
".grok/skills/",
|
|
3168
3264
|
".iflow/skills/",
|
|
3169
3265
|
".junie/skills/",
|
|
3170
3266
|
".kilocode/skills/",
|
|
3267
|
+
".kimchi/skills/",
|
|
3171
3268
|
".kiro/skills/",
|
|
3172
3269
|
".mux/skills/",
|
|
3173
3270
|
".neovate/skills/",
|
|
@@ -3334,7 +3431,7 @@ async function tryBlobInstall(ownerRepo, options = {}) {
|
|
|
3334
3431
|
tree
|
|
3335
3432
|
};
|
|
3336
3433
|
}
|
|
3337
|
-
var version$1 = "1.5.
|
|
3434
|
+
var version$1 = "1.5.20";
|
|
3338
3435
|
const isCancelled$1 = (value) => typeof value === "symbol";
|
|
3339
3436
|
const EVE_AGENT_LABEL = "eve agent";
|
|
3340
3437
|
async function isSourcePrivate(source) {
|
|
@@ -3885,6 +3982,7 @@ async function runAdd(args, options = {}) {
|
|
|
3885
3982
|
spinner$3.stop(`Source: ${parsed.type === "local" ? parsed.localPath : parsed.url}${parsed.ref ? ` @ ${import_picocolors.default.yellow(parsed.ref)}` : ""}${parsed.subpath ? ` (${parsed.subpath})` : ""}${parsed.skillFilter ? ` ${import_picocolors.default.dim("@")}${import_picocolors.default.cyan(parsed.skillFilter)}` : ""}`);
|
|
3886
3983
|
const ownerRepoRaw = getOwnerRepo(parsed);
|
|
3887
3984
|
const repoPrivacyPromise = (() => {
|
|
3985
|
+
if (parsed.type !== "github") return Promise.resolve(null);
|
|
3888
3986
|
if (!ownerRepoRaw) return Promise.resolve(null);
|
|
3889
3987
|
const ownerRepo = parseOwnerRepo(ownerRepoRaw);
|
|
3890
3988
|
if (!ownerRepo) return Promise.resolve(null);
|
|
@@ -5155,7 +5253,7 @@ function deriveSkillFolder(skillPath) {
|
|
|
5155
5253
|
return folder;
|
|
5156
5254
|
}
|
|
5157
5255
|
function supportsAppendedSubpath(source) {
|
|
5158
|
-
if (source.startsWith("git@")) return false;
|
|
5256
|
+
if (source.startsWith("git@") || source.startsWith("ssh://")) return false;
|
|
5159
5257
|
if (source.endsWith(".git")) return false;
|
|
5160
5258
|
if (source.startsWith("http://") || source.startsWith("https://")) try {
|
|
5161
5259
|
const host = new URL(source).hostname;
|
|
@@ -5173,6 +5271,11 @@ function getLocalSource(entry) {
|
|
|
5173
5271
|
if ((entry.sourceType === "git" || entry.sourceType === "gitlab") && isBareShorthand(entry.source)) return null;
|
|
5174
5272
|
return entry.source;
|
|
5175
5273
|
}
|
|
5274
|
+
function shouldUseFullDepthForUpdate(entry) {
|
|
5275
|
+
if (!entry.skillPath) return false;
|
|
5276
|
+
const source = entry.sourceType && entry.sourceType !== "github" ? getLocalSource(entry) : entry.source;
|
|
5277
|
+
return source !== null && !supportsAppendedSubpath(source);
|
|
5278
|
+
}
|
|
5176
5279
|
function appendFolderAndRef(source, skillPath, ref) {
|
|
5177
5280
|
if (!supportsAppendedSubpath(source)) return formatSourceInput(source, ref);
|
|
5178
5281
|
const folder = deriveSkillFolder(skillPath);
|
|
@@ -5296,18 +5399,26 @@ async function runList(args) {
|
|
|
5296
5399
|
global: scope,
|
|
5297
5400
|
agentFilter
|
|
5298
5401
|
});
|
|
5402
|
+
const cwd = process.cwd();
|
|
5403
|
+
const lockedSkills = scope ? await getAllLockedSkills() : (await readLocalLock(cwd)).skills;
|
|
5404
|
+
const lockEntriesBySanitizedName = new Map(Object.entries(lockedSkills).map(([name, entry]) => [sanitizeName(name), entry]));
|
|
5405
|
+
const getLockEntry = (skillName) => lockedSkills[skillName] ?? lockEntriesBySanitizedName.get(sanitizeName(skillName));
|
|
5299
5406
|
if (options.json) {
|
|
5300
|
-
const jsonOutput = installedSkills.map((skill) =>
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5407
|
+
const jsonOutput = installedSkills.map((skill) => {
|
|
5408
|
+
const lockEntry = getLockEntry(skill.name);
|
|
5409
|
+
return {
|
|
5410
|
+
name: skill.name,
|
|
5411
|
+
path: skill.canonicalPath,
|
|
5412
|
+
scope: skill.scope,
|
|
5413
|
+
agents: skill.agents.map((a) => agents[a].displayName),
|
|
5414
|
+
source: lockEntry?.source ?? null,
|
|
5415
|
+
sourceUrl: lockEntry?.sourceUrl ?? null,
|
|
5416
|
+
sourceType: lockEntry?.sourceType ?? null
|
|
5417
|
+
};
|
|
5418
|
+
});
|
|
5306
5419
|
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
5307
5420
|
return;
|
|
5308
5421
|
}
|
|
5309
|
-
const lockedSkills = await getAllLockedSkills();
|
|
5310
|
-
const cwd = process.cwd();
|
|
5311
5422
|
const scopeLabel = scope ? "Global" : "Project";
|
|
5312
5423
|
if (installedSkills.length === 0) {
|
|
5313
5424
|
if (options.json) {
|
|
@@ -5326,14 +5437,17 @@ async function runList(args) {
|
|
|
5326
5437
|
const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW}not linked${RESET$2}`;
|
|
5327
5438
|
const paddedName = sanitizeMetadata(skill.name).padEnd(maxNameLength);
|
|
5328
5439
|
const paddedPath = shortPath.padEnd(maxPathLength);
|
|
5329
|
-
|
|
5440
|
+
const source = getLockEntry(skill.name)?.source ?? null;
|
|
5441
|
+
const sourceLabel = source ? sanitizeMetadata(source) : "local";
|
|
5442
|
+
console.log(`${prefix}${CYAN}${paddedName}${RESET$2} ${DIM$2}${paddedPath}${RESET$2}`);
|
|
5443
|
+
console.log(`${prefix} ${DIM$2}Agents:${RESET$2} ${agentInfo} ${DIM$2}Source:${RESET$2} ${sourceLabel}`);
|
|
5330
5444
|
}
|
|
5331
5445
|
console.log(`${BOLD$2}${scopeLabel} Skills${RESET$2}`);
|
|
5332
5446
|
console.log();
|
|
5333
5447
|
const groupedSkills = {};
|
|
5334
5448
|
const ungroupedSkills = [];
|
|
5335
5449
|
for (const skill of installedSkills) {
|
|
5336
|
-
const lockEntry =
|
|
5450
|
+
const lockEntry = getLockEntry(skill.name);
|
|
5337
5451
|
if (lockEntry?.pluginName) {
|
|
5338
5452
|
const group = lockEntry.pluginName;
|
|
5339
5453
|
if (!groupedSkills[group]) groupedSkills[group] = [];
|
|
@@ -5385,6 +5499,17 @@ async function runList(args) {
|
|
|
5385
5499
|
console.log();
|
|
5386
5500
|
}
|
|
5387
5501
|
}
|
|
5502
|
+
function resolveSkillsToRemove(requested, folderNames, lockKeys = []) {
|
|
5503
|
+
const identityBySanitized = /* @__PURE__ */ new Map();
|
|
5504
|
+
for (const folder of folderNames) identityBySanitized.set(sanitizeName(folder), folder);
|
|
5505
|
+
for (const key of lockKeys) identityBySanitized.set(sanitizeName(key), key);
|
|
5506
|
+
const matched = /* @__PURE__ */ new Set();
|
|
5507
|
+
for (const name of requested) {
|
|
5508
|
+
const hit = identityBySanitized.get(sanitizeName(name));
|
|
5509
|
+
if (hit) matched.add(hit);
|
|
5510
|
+
}
|
|
5511
|
+
return Array.from(matched);
|
|
5512
|
+
}
|
|
5388
5513
|
async function removeCommand(skillNames, options) {
|
|
5389
5514
|
const agentResult = await detectAgent();
|
|
5390
5515
|
if (agentResult.isAgent) {
|
|
@@ -5414,7 +5539,10 @@ async function removeCommand(skillNames, options) {
|
|
|
5414
5539
|
}
|
|
5415
5540
|
const installedSkills = Array.from(skillNamesSet).sort();
|
|
5416
5541
|
spinner$1.stop(`Found ${installedSkills.length} unique installed skill(s)`);
|
|
5417
|
-
|
|
5542
|
+
const lockSkillsKeys = isGlobal ? Object.keys((await readSkillLock()).skills) : Object.keys((await readLocalLock(cwd)).skills);
|
|
5543
|
+
const requestedSkills = options.all ? [...installedSkills, ...lockSkillsKeys] : skillNames;
|
|
5544
|
+
const resolvedRequestedSkills = options.all || skillNames.length > 0 ? resolveSkillsToRemove(requestedSkills, installedSkills, lockSkillsKeys) : [];
|
|
5545
|
+
if (installedSkills.length === 0 && resolvedRequestedSkills.length === 0) {
|
|
5418
5546
|
outro(import_picocolors.default.yellow("No skills found to remove."));
|
|
5419
5547
|
return;
|
|
5420
5548
|
}
|
|
@@ -5428,9 +5556,9 @@ async function removeCommand(skillNames, options) {
|
|
|
5428
5556
|
}
|
|
5429
5557
|
}
|
|
5430
5558
|
let selectedSkills = [];
|
|
5431
|
-
if (options.all) selectedSkills =
|
|
5559
|
+
if (options.all) selectedSkills = resolvedRequestedSkills;
|
|
5432
5560
|
else if (skillNames.length > 0) {
|
|
5433
|
-
selectedSkills =
|
|
5561
|
+
selectedSkills = resolvedRequestedSkills;
|
|
5434
5562
|
if (selectedSkills.length === 0) {
|
|
5435
5563
|
log.error(`No matching skills found for: ${skillNames.join(", ")}`);
|
|
5436
5564
|
return;
|
|
@@ -5449,7 +5577,7 @@ async function removeCommand(skillNames, options) {
|
|
|
5449
5577
|
cancel("Removal cancelled");
|
|
5450
5578
|
process.exit(0);
|
|
5451
5579
|
}
|
|
5452
|
-
selectedSkills = selected;
|
|
5580
|
+
selectedSkills = resolveSkillsToRemove(selected, installedSkills, lockSkillsKeys);
|
|
5453
5581
|
}
|
|
5454
5582
|
let targetAgents;
|
|
5455
5583
|
if (options.agent && options.agent.length > 0) targetAgents = options.agent;
|
|
@@ -5513,10 +5641,19 @@ async function removeCommand(skillNames, options) {
|
|
|
5513
5641
|
recursive: true,
|
|
5514
5642
|
force: true
|
|
5515
5643
|
});
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5644
|
+
let effectiveSource = "local";
|
|
5645
|
+
let effectiveSourceType = "local";
|
|
5646
|
+
if (isGlobal) {
|
|
5647
|
+
const lockEntry = await getSkillFromLock(skillName);
|
|
5648
|
+
effectiveSource = lockEntry?.source || "local";
|
|
5649
|
+
effectiveSourceType = lockEntry?.sourceType || "local";
|
|
5650
|
+
await removeSkillFromLock(skillName);
|
|
5651
|
+
} else {
|
|
5652
|
+
const lockEntry = (await readLocalLock(cwd)).skills[skillName];
|
|
5653
|
+
effectiveSource = lockEntry?.source || "local";
|
|
5654
|
+
effectiveSourceType = lockEntry?.sourceType || "local";
|
|
5655
|
+
await removeSkillFromLocalLock(skillName, cwd);
|
|
5656
|
+
}
|
|
5520
5657
|
results.push({
|
|
5521
5658
|
skill: skillName,
|
|
5522
5659
|
success: true,
|
|
@@ -5786,7 +5923,7 @@ async function updateGlobalSkills(options = {}) {
|
|
|
5786
5923
|
console.log(` ${DIM$1}✗ Failed to fetch tree for ${source}${RESET$1}`);
|
|
5787
5924
|
continue;
|
|
5788
5925
|
}
|
|
5789
|
-
const discoveredPaths =
|
|
5926
|
+
const discoveredPaths = tree.tree.filter((entry) => entry.type === "blob").map((entry) => entry.path);
|
|
5790
5927
|
const deletedSkills = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source).map(([name, _]) => name), lock.skills, true, options, discoveredPaths);
|
|
5791
5928
|
const deletedSkillSet = new Set(deletedSkills);
|
|
5792
5929
|
for (const { name: skillName, entry } of itemsForSource) {
|
|
@@ -5801,7 +5938,7 @@ async function updateGlobalSkills(options = {}) {
|
|
|
5801
5938
|
continue;
|
|
5802
5939
|
}
|
|
5803
5940
|
tempDir = await cloneRepo(sourceUrl, firstEntry.ref);
|
|
5804
|
-
const discoveredPaths = (await discoverSkills(tempDir)).map((skill) => {
|
|
5941
|
+
const discoveredPaths = (await discoverSkills(tempDir, void 0, { fullDepth: true })).map((skill) => {
|
|
5805
5942
|
return join(relative(tempDir, skill.path), "SKILL.md").split(sep).join("/");
|
|
5806
5943
|
});
|
|
5807
5944
|
const deletedSkills = await checkAndPromptForDeletions(source, Object.entries(lock.skills).filter(([_, entry]) => entry.source === source).map(([name, _]) => name), lock.skills, true, options, discoveredPaths);
|
|
@@ -5866,10 +6003,12 @@ async function updateGlobalSkills(options = {}) {
|
|
|
5866
6003
|
console.log(` ${DIM$1}✗ Failed to update ${safeName}: CLI entrypoint not found at ${cliEntry}${RESET$1}`);
|
|
5867
6004
|
continue;
|
|
5868
6005
|
}
|
|
6006
|
+
const fullDepthArgs = shouldUseFullDepthForUpdate(update.entry) ? ["--full-depth"] : [];
|
|
5869
6007
|
if (spawnSync(process.execPath, [
|
|
5870
6008
|
cliEntry,
|
|
5871
6009
|
"add",
|
|
5872
6010
|
installUrl,
|
|
6011
|
+
...fullDepthArgs,
|
|
5873
6012
|
"-g",
|
|
5874
6013
|
"-y"
|
|
5875
6014
|
], {
|
|
@@ -5967,7 +6106,7 @@ async function updateProjectSkills(options = {}) {
|
|
|
5967
6106
|
}
|
|
5968
6107
|
try {
|
|
5969
6108
|
tempDir = await cloneRepo(sourceUrl, ref);
|
|
5970
|
-
const discoveredPaths = (await discoverSkills(tempDir)).map((s) => {
|
|
6109
|
+
const discoveredPaths = (await discoverSkills(tempDir, void 0, { fullDepth: true })).map((s) => {
|
|
5971
6110
|
return join(relative(tempDir, s.path), "SKILL.md").split(sep).join("/");
|
|
5972
6111
|
});
|
|
5973
6112
|
deletedSkills = await checkAndPromptForDeletions(source, allLockedForSource, localLock.skills, false, options, discoveredPaths);
|
|
@@ -5987,6 +6126,7 @@ async function updateProjectSkills(options = {}) {
|
|
|
5987
6126
|
continue;
|
|
5988
6127
|
}
|
|
5989
6128
|
const subagentArgs = skill.entry.subagents?.length ? ["--subagent", ...skill.entry.subagents.map((s) => s === "" ? "root" : s)] : [];
|
|
6129
|
+
const fullDepthArgs = shouldUseFullDepthForUpdate(skill.entry) ? ["--full-depth"] : [];
|
|
5990
6130
|
if (spawnSync(process.execPath, [
|
|
5991
6131
|
cliEntry,
|
|
5992
6132
|
"add",
|
|
@@ -5994,6 +6134,7 @@ async function updateProjectSkills(options = {}) {
|
|
|
5994
6134
|
"--skill",
|
|
5995
6135
|
skill.name,
|
|
5996
6136
|
...subagentArgs,
|
|
6137
|
+
...fullDepthArgs,
|
|
5997
6138
|
"-y"
|
|
5998
6139
|
], {
|
|
5999
6140
|
stdio: [
|
|
@@ -6674,6 +6815,11 @@ async function main() {
|
|
|
6674
6815
|
}
|
|
6675
6816
|
const command = args[0];
|
|
6676
6817
|
const restArgs = args.slice(1);
|
|
6818
|
+
if (command !== "--help" && command !== "-h" && command !== "--version" && command !== "-v" && (restArgs.includes("--help") || restArgs.includes("-h"))) {
|
|
6819
|
+
if (command === "remove" || command === "rm" || command === "r") showRemoveHelp();
|
|
6820
|
+
else showHelp();
|
|
6821
|
+
return;
|
|
6822
|
+
}
|
|
6677
6823
|
switch (command) {
|
|
6678
6824
|
case "find":
|
|
6679
6825
|
case "search":
|
|
@@ -6713,14 +6859,11 @@ async function main() {
|
|
|
6713
6859
|
}
|
|
6714
6860
|
case "remove":
|
|
6715
6861
|
case "rm":
|
|
6716
|
-
case "r":
|
|
6717
|
-
if (restArgs.includes("--help") || restArgs.includes("-h")) {
|
|
6718
|
-
showRemoveHelp();
|
|
6719
|
-
break;
|
|
6720
|
-
}
|
|
6862
|
+
case "r": {
|
|
6721
6863
|
const { skills, options: removeOptions } = parseRemoveOptions(restArgs);
|
|
6722
6864
|
await removeCommand(skills, removeOptions);
|
|
6723
6865
|
break;
|
|
6866
|
+
}
|
|
6724
6867
|
case "experimental_sync": {
|
|
6725
6868
|
if (!inAgent) showLogo();
|
|
6726
6869
|
const { options: syncOptions } = parseSyncOptions(restArgs);
|
|
@@ -6747,6 +6890,7 @@ async function main() {
|
|
|
6747
6890
|
default:
|
|
6748
6891
|
console.log(`Unknown command: ${command}`);
|
|
6749
6892
|
console.log(`Run ${BOLD}skills --help${RESET} for usage.`);
|
|
6893
|
+
process.exitCode = 1;
|
|
6750
6894
|
}
|
|
6751
6895
|
}
|
|
6752
6896
|
main().finally(() => flushTelemetry().then(() => process.exit(process.exitCode ?? 0)));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skills",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.20",
|
|
4
4
|
"description": "The open agent skills ecosystem",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -67,12 +67,14 @@
|
|
|
67
67
|
"gemini-cli",
|
|
68
68
|
"github-copilot",
|
|
69
69
|
"goose",
|
|
70
|
+
"grok",
|
|
70
71
|
"hermes-agent",
|
|
71
72
|
"inference-sh",
|
|
72
73
|
"jazz",
|
|
73
74
|
"junie",
|
|
74
75
|
"iflow-cli",
|
|
75
76
|
"kilo",
|
|
77
|
+
"kimchi",
|
|
76
78
|
"kimi-code-cli",
|
|
77
79
|
"kiro-cli",
|
|
78
80
|
"kode",
|