skills 1.4.0 → 1.4.2
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 +29 -26
- package/dist/_chunks/libs/@clack/prompts.mjs +60 -1
- package/dist/cli.mjs +806 -125
- package/package.json +4 -2
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { r as __toESM } from "./_chunks/rolldown-runtime.mjs";
|
|
3
3
|
import { l as pD, u as require_picocolors } from "./_chunks/libs/@clack/core.mjs";
|
|
4
|
-
import { a as Y, c as
|
|
4
|
+
import { a as Y, c as ve, i as Se, l as xe, n as M, o as be, r as Me, s as fe, t as Ie, u as ye } from "./_chunks/libs/@clack/prompts.mjs";
|
|
5
5
|
import "./_chunks/libs/@kwsites/file-exists.mjs";
|
|
6
6
|
import "./_chunks/libs/@kwsites/promise-deferred.mjs";
|
|
7
7
|
import { t as esm_default } from "./_chunks/libs/simple-git.mjs";
|
|
@@ -13,7 +13,7 @@ import { execSync, spawn, spawnSync } from "child_process";
|
|
|
13
13
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
14
14
|
import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "path";
|
|
15
15
|
import { homedir, platform, tmpdir } from "os";
|
|
16
|
-
import "crypto";
|
|
16
|
+
import { createHash } from "crypto";
|
|
17
17
|
import { fileURLToPath } from "url";
|
|
18
18
|
import * as readline from "readline";
|
|
19
19
|
import { Writable } from "stream";
|
|
@@ -180,7 +180,8 @@ const S_STEP_CANCEL = import_picocolors.default.red("■");
|
|
|
180
180
|
const S_STEP_SUBMIT = import_picocolors.default.green("◇");
|
|
181
181
|
const S_RADIO_ACTIVE = import_picocolors.default.green("●");
|
|
182
182
|
const S_RADIO_INACTIVE = import_picocolors.default.dim("○");
|
|
183
|
-
|
|
183
|
+
import_picocolors.default.green("✓");
|
|
184
|
+
const S_BULLET = import_picocolors.default.green("•");
|
|
184
185
|
const S_BAR = import_picocolors.default.dim("│");
|
|
185
186
|
const S_BAR_H = import_picocolors.default.dim("─");
|
|
186
187
|
const cancelSymbol = Symbol("cancel");
|
|
@@ -223,10 +224,11 @@ async function searchMultiselect(options) {
|
|
|
223
224
|
if (state === "active") {
|
|
224
225
|
if (lockedSection && lockedSection.items.length > 0) {
|
|
225
226
|
lines.push(`${S_BAR}`);
|
|
226
|
-
|
|
227
|
-
|
|
227
|
+
const lockedTitle = `${import_picocolors.default.bold(lockedSection.title)} ${import_picocolors.default.dim("── always included")}`;
|
|
228
|
+
lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${lockedTitle} ${S_BAR_H.repeat(12)}`);
|
|
229
|
+
for (const item of lockedSection.items) lines.push(`${S_BAR} ${S_BULLET} ${import_picocolors.default.bold(item.label)}`);
|
|
228
230
|
lines.push(`${S_BAR}`);
|
|
229
|
-
lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${import_picocolors.default.bold("
|
|
231
|
+
lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${import_picocolors.default.bold("Additional agents")} ${S_BAR_H.repeat(29)}`);
|
|
230
232
|
}
|
|
231
233
|
const searchLine = `${S_BAR} ${import_picocolors.default.dim("Search:")} ${query}${import_picocolors.default.inverse(" ")}`;
|
|
232
234
|
lines.push(searchLine);
|
|
@@ -348,7 +350,13 @@ var GitCloneError = class extends Error {
|
|
|
348
350
|
};
|
|
349
351
|
async function cloneRepo(url, ref) {
|
|
350
352
|
const tempDir = await mkdtemp(join(tmpdir(), "skills-"));
|
|
351
|
-
const git = esm_default({
|
|
353
|
+
const git = esm_default({
|
|
354
|
+
timeout: { block: CLONE_TIMEOUT_MS },
|
|
355
|
+
env: {
|
|
356
|
+
...process.env,
|
|
357
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
358
|
+
}
|
|
359
|
+
});
|
|
352
360
|
const cloneOptions = ref ? [
|
|
353
361
|
"--depth",
|
|
354
362
|
"1",
|
|
@@ -416,6 +424,36 @@ async function getPluginSkillPaths(basePath) {
|
|
|
416
424
|
} catch {}
|
|
417
425
|
return searchDirs;
|
|
418
426
|
}
|
|
427
|
+
async function getPluginGroupings(basePath) {
|
|
428
|
+
const groupings = /* @__PURE__ */ new Map();
|
|
429
|
+
try {
|
|
430
|
+
const content = await readFile(join(basePath, ".claude-plugin/marketplace.json"), "utf-8");
|
|
431
|
+
const manifest = JSON.parse(content);
|
|
432
|
+
const pluginRoot = manifest.metadata?.pluginRoot;
|
|
433
|
+
if (pluginRoot === void 0 || isValidRelativePath(pluginRoot)) for (const plugin of manifest.plugins ?? []) {
|
|
434
|
+
if (!plugin.name) continue;
|
|
435
|
+
if (typeof plugin.source !== "string" && plugin.source !== void 0) continue;
|
|
436
|
+
if (plugin.source !== void 0 && !isValidRelativePath(plugin.source)) continue;
|
|
437
|
+
const pluginBase = join(basePath, pluginRoot ?? "", plugin.source ?? "");
|
|
438
|
+
if (!isContainedIn(pluginBase, basePath)) continue;
|
|
439
|
+
if (plugin.skills && plugin.skills.length > 0) for (const skillPath of plugin.skills) {
|
|
440
|
+
if (!isValidRelativePath(skillPath)) continue;
|
|
441
|
+
const skillDir = join(pluginBase, skillPath);
|
|
442
|
+
if (isContainedIn(skillDir, basePath)) groupings.set(resolve(skillDir), plugin.name);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
} catch {}
|
|
446
|
+
try {
|
|
447
|
+
const content = await readFile(join(basePath, ".claude-plugin/plugin.json"), "utf-8");
|
|
448
|
+
const manifest = JSON.parse(content);
|
|
449
|
+
if (manifest.name && manifest.skills && manifest.skills.length > 0) for (const skillPath of manifest.skills) {
|
|
450
|
+
if (!isValidRelativePath(skillPath)) continue;
|
|
451
|
+
const skillDir = join(basePath, skillPath);
|
|
452
|
+
if (isContainedIn(skillDir, basePath)) groupings.set(resolve(skillDir), manifest.name);
|
|
453
|
+
}
|
|
454
|
+
} catch {}
|
|
455
|
+
return groupings;
|
|
456
|
+
}
|
|
419
457
|
const SKIP_DIRS = [
|
|
420
458
|
"node_modules",
|
|
421
459
|
".git",
|
|
@@ -467,9 +505,16 @@ async function discoverSkills(basePath, subpath, options) {
|
|
|
467
505
|
const skills = [];
|
|
468
506
|
const seenNames = /* @__PURE__ */ new Set();
|
|
469
507
|
const searchPath = subpath ? join(basePath, subpath) : basePath;
|
|
508
|
+
const pluginGroupings = await getPluginGroupings(searchPath);
|
|
509
|
+
const enhanceSkill = (skill) => {
|
|
510
|
+
const resolvedPath = resolve(skill.path);
|
|
511
|
+
if (pluginGroupings.has(resolvedPath)) skill.pluginName = pluginGroupings.get(resolvedPath);
|
|
512
|
+
return skill;
|
|
513
|
+
};
|
|
470
514
|
if (await hasSkillMd(searchPath)) {
|
|
471
|
-
|
|
515
|
+
let skill = await parseSkillMd(join(searchPath, "SKILL.md"), options);
|
|
472
516
|
if (skill) {
|
|
517
|
+
skill = enhanceSkill(skill);
|
|
473
518
|
skills.push(skill);
|
|
474
519
|
seenNames.add(skill.name);
|
|
475
520
|
if (!options?.fullDepth) return skills;
|
|
@@ -489,7 +534,6 @@ async function discoverSkills(basePath, subpath, options) {
|
|
|
489
534
|
join(searchPath, ".codex/skills"),
|
|
490
535
|
join(searchPath, ".commandcode/skills"),
|
|
491
536
|
join(searchPath, ".continue/skills"),
|
|
492
|
-
join(searchPath, ".cursor/skills"),
|
|
493
537
|
join(searchPath, ".github/skills"),
|
|
494
538
|
join(searchPath, ".goose/skills"),
|
|
495
539
|
join(searchPath, ".iflow/skills"),
|
|
@@ -513,8 +557,9 @@ async function discoverSkills(basePath, subpath, options) {
|
|
|
513
557
|
for (const entry of entries) if (entry.isDirectory()) {
|
|
514
558
|
const skillDir = join(dir, entry.name);
|
|
515
559
|
if (await hasSkillMd(skillDir)) {
|
|
516
|
-
|
|
560
|
+
let skill = await parseSkillMd(join(skillDir, "SKILL.md"), options);
|
|
517
561
|
if (skill && !seenNames.has(skill.name)) {
|
|
562
|
+
skill = enhanceSkill(skill);
|
|
518
563
|
skills.push(skill);
|
|
519
564
|
seenNames.add(skill.name);
|
|
520
565
|
}
|
|
@@ -524,8 +569,9 @@ async function discoverSkills(basePath, subpath, options) {
|
|
|
524
569
|
if (skills.length === 0 || options?.fullDepth) {
|
|
525
570
|
const allSkillDirs = await findSkillDirs(searchPath);
|
|
526
571
|
for (const skillDir of allSkillDirs) {
|
|
527
|
-
|
|
572
|
+
let skill = await parseSkillMd(join(skillDir, "SKILL.md"), options);
|
|
528
573
|
if (skill && !seenNames.has(skill.name)) {
|
|
574
|
+
skill = enhanceSkill(skill);
|
|
529
575
|
skills.push(skill);
|
|
530
576
|
seenNames.add(skill.name);
|
|
531
577
|
}
|
|
@@ -570,7 +616,7 @@ const agents = {
|
|
|
570
616
|
skillsDir: ".agent/skills",
|
|
571
617
|
globalSkillsDir: join(home, ".gemini/antigravity/skills"),
|
|
572
618
|
detectInstalled: async () => {
|
|
573
|
-
return existsSync(join(
|
|
619
|
+
return existsSync(join(home, ".gemini/antigravity"));
|
|
574
620
|
}
|
|
575
621
|
},
|
|
576
622
|
augment: {
|
|
@@ -603,8 +649,8 @@ const agents = {
|
|
|
603
649
|
cline: {
|
|
604
650
|
name: "cline",
|
|
605
651
|
displayName: "Cline",
|
|
606
|
-
skillsDir: ".
|
|
607
|
-
globalSkillsDir: join(home, ".
|
|
652
|
+
skillsDir: ".agents/skills",
|
|
653
|
+
globalSkillsDir: join(home, ".agents", "skills"),
|
|
608
654
|
detectInstalled: async () => {
|
|
609
655
|
return existsSync(join(home, ".cline"));
|
|
610
656
|
}
|
|
@@ -645,6 +691,15 @@ const agents = {
|
|
|
645
691
|
return existsSync(join(process.cwd(), ".continue")) || existsSync(join(home, ".continue"));
|
|
646
692
|
}
|
|
647
693
|
},
|
|
694
|
+
cortex: {
|
|
695
|
+
name: "cortex",
|
|
696
|
+
displayName: "Cortex Code",
|
|
697
|
+
skillsDir: ".cortex/skills",
|
|
698
|
+
globalSkillsDir: join(home, ".snowflake/cortex/skills"),
|
|
699
|
+
detectInstalled: async () => {
|
|
700
|
+
return existsSync(join(home, ".snowflake/cortex"));
|
|
701
|
+
}
|
|
702
|
+
},
|
|
648
703
|
crush: {
|
|
649
704
|
name: "crush",
|
|
650
705
|
displayName: "Crush",
|
|
@@ -657,7 +712,7 @@ const agents = {
|
|
|
657
712
|
cursor: {
|
|
658
713
|
name: "cursor",
|
|
659
714
|
displayName: "Cursor",
|
|
660
|
-
skillsDir: ".
|
|
715
|
+
skillsDir: ".agents/skills",
|
|
661
716
|
globalSkillsDir: join(home, ".cursor/skills"),
|
|
662
717
|
detectInstalled: async () => {
|
|
663
718
|
return existsSync(join(home, ".cursor"));
|
|
@@ -687,7 +742,7 @@ const agents = {
|
|
|
687
742
|
skillsDir: ".agents/skills",
|
|
688
743
|
globalSkillsDir: join(home, ".copilot/skills"),
|
|
689
744
|
detectInstalled: async () => {
|
|
690
|
-
return existsSync(join(
|
|
745
|
+
return existsSync(join(home, ".copilot"));
|
|
691
746
|
}
|
|
692
747
|
},
|
|
693
748
|
goose: {
|
|
@@ -786,7 +841,7 @@ const agents = {
|
|
|
786
841
|
skillsDir: ".agents/skills",
|
|
787
842
|
globalSkillsDir: join(configHome, "opencode/skills"),
|
|
788
843
|
detectInstalled: async () => {
|
|
789
|
-
return existsSync(join(configHome, "opencode"))
|
|
844
|
+
return existsSync(join(configHome, "opencode"));
|
|
790
845
|
}
|
|
791
846
|
},
|
|
792
847
|
openhands: {
|
|
@@ -832,7 +887,7 @@ const agents = {
|
|
|
832
887
|
globalSkillsDir: join(configHome, "agents/skills"),
|
|
833
888
|
showInUniversalList: false,
|
|
834
889
|
detectInstalled: async () => {
|
|
835
|
-
return existsSync(join(process.cwd(), ".
|
|
890
|
+
return existsSync(join(process.cwd(), ".replit"));
|
|
836
891
|
}
|
|
837
892
|
},
|
|
838
893
|
roo: {
|
|
@@ -906,6 +961,14 @@ const agents = {
|
|
|
906
961
|
detectInstalled: async () => {
|
|
907
962
|
return existsSync(join(home, ".adal"));
|
|
908
963
|
}
|
|
964
|
+
},
|
|
965
|
+
universal: {
|
|
966
|
+
name: "universal",
|
|
967
|
+
displayName: "Universal",
|
|
968
|
+
skillsDir: ".agents/skills",
|
|
969
|
+
globalSkillsDir: join(configHome, "agents/skills"),
|
|
970
|
+
showInUniversalList: false,
|
|
971
|
+
detectInstalled: async () => false
|
|
909
972
|
}
|
|
910
973
|
};
|
|
911
974
|
async function detectInstalledAgents() {
|
|
@@ -936,6 +999,16 @@ function isPathSafe(basePath, targetPath) {
|
|
|
936
999
|
function getCanonicalSkillsDir(global, cwd) {
|
|
937
1000
|
return join(global ? homedir() : cwd || process.cwd(), AGENTS_DIR$2, SKILLS_SUBDIR);
|
|
938
1001
|
}
|
|
1002
|
+
function getAgentBaseDir(agentType, global, cwd) {
|
|
1003
|
+
if (isUniversalAgent(agentType)) return getCanonicalSkillsDir(global, cwd);
|
|
1004
|
+
const agent = agents[agentType];
|
|
1005
|
+
const baseDir = global ? homedir() : cwd || process.cwd();
|
|
1006
|
+
if (global) {
|
|
1007
|
+
if (agent.globalSkillsDir === void 0) return join(baseDir, agent.skillsDir);
|
|
1008
|
+
return agent.globalSkillsDir;
|
|
1009
|
+
}
|
|
1010
|
+
return join(baseDir, agent.skillsDir);
|
|
1011
|
+
}
|
|
939
1012
|
function resolveSymlinkTarget(linkPath, linkTarget) {
|
|
940
1013
|
return resolve(dirname(linkPath), linkTarget);
|
|
941
1014
|
}
|
|
@@ -961,7 +1034,9 @@ async function resolveParentSymlinks(path) {
|
|
|
961
1034
|
async function createSymlink(target, linkPath) {
|
|
962
1035
|
try {
|
|
963
1036
|
const resolvedTarget = resolve(target);
|
|
964
|
-
|
|
1037
|
+
const resolvedLinkPath = resolve(linkPath);
|
|
1038
|
+
const [realTarget, realLinkPath] = await Promise.all([realpath(resolvedTarget).catch(() => resolvedTarget), realpath(resolvedLinkPath).catch(() => resolvedLinkPath)]);
|
|
1039
|
+
if (realTarget === realLinkPath) return true;
|
|
965
1040
|
if (await resolveParentSymlinks(target) === await resolveParentSymlinks(linkPath)) return true;
|
|
966
1041
|
try {
|
|
967
1042
|
if ((await lstat(linkPath)).isSymbolicLink()) {
|
|
@@ -994,7 +1069,7 @@ async function installSkillForAgent(skill, agentType, options = {}) {
|
|
|
994
1069
|
const skillName = sanitizeName(skill.name || basename(skill.path));
|
|
995
1070
|
const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
|
|
996
1071
|
const canonicalDir = join(canonicalBase, skillName);
|
|
997
|
-
const agentBase = isGlobal
|
|
1072
|
+
const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
|
|
998
1073
|
const agentDir = join(agentBase, skillName);
|
|
999
1074
|
const installMode = options.mode ?? "symlink";
|
|
1000
1075
|
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
@@ -1053,7 +1128,7 @@ async function installSkillForAgent(skill, agentType, options = {}) {
|
|
|
1053
1128
|
};
|
|
1054
1129
|
}
|
|
1055
1130
|
}
|
|
1056
|
-
const EXCLUDE_FILES = new Set(["
|
|
1131
|
+
const EXCLUDE_FILES = new Set(["metadata.json"]);
|
|
1057
1132
|
const EXCLUDE_DIRS = new Set([".git"]);
|
|
1058
1133
|
const isExcluded = (name, isDirectory = false) => {
|
|
1059
1134
|
if (EXCLUDE_FILES.has(name)) return true;
|
|
@@ -1089,10 +1164,10 @@ async function isSkillInstalled(skillName, agentType, options = {}) {
|
|
|
1089
1164
|
}
|
|
1090
1165
|
}
|
|
1091
1166
|
function getInstallPath(skillName, agentType, options = {}) {
|
|
1092
|
-
|
|
1093
|
-
|
|
1167
|
+
agents[agentType];
|
|
1168
|
+
options.cwd || process.cwd();
|
|
1094
1169
|
const sanitized = sanitizeName(skillName);
|
|
1095
|
-
const targetBase = options.global
|
|
1170
|
+
const targetBase = getAgentBaseDir(agentType, options.global ?? false, options.cwd);
|
|
1096
1171
|
const installPath = join(targetBase, sanitized);
|
|
1097
1172
|
if (!isPathSafe(targetBase, installPath)) throw new Error("Invalid skill name: potential path traversal detected");
|
|
1098
1173
|
return installPath;
|
|
@@ -1118,7 +1193,7 @@ async function installRemoteSkillForAgent(skill, agentType, options = {}) {
|
|
|
1118
1193
|
const skillName = sanitizeName(skill.installName);
|
|
1119
1194
|
const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
|
|
1120
1195
|
const canonicalDir = join(canonicalBase, skillName);
|
|
1121
|
-
const agentBase = isGlobal
|
|
1196
|
+
const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
|
|
1122
1197
|
const agentDir = join(agentBase, skillName);
|
|
1123
1198
|
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
1124
1199
|
success: false,
|
|
@@ -1190,7 +1265,7 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
|
1190
1265
|
const skillName = sanitizeName(skill.installName);
|
|
1191
1266
|
const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
|
|
1192
1267
|
const canonicalDir = join(canonicalBase, skillName);
|
|
1193
|
-
const agentBase = isGlobal
|
|
1268
|
+
const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
|
|
1194
1269
|
const agentDir = join(agentBase, skillName);
|
|
1195
1270
|
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
1196
1271
|
success: false,
|
|
@@ -1718,7 +1793,7 @@ async function fetchMintlifySkill(url) {
|
|
|
1718
1793
|
}
|
|
1719
1794
|
const AGENTS_DIR$1 = ".agents";
|
|
1720
1795
|
const LOCK_FILE$1 = ".skill-lock.json";
|
|
1721
|
-
const CURRENT_VERSION = 3;
|
|
1796
|
+
const CURRENT_VERSION$1 = 3;
|
|
1722
1797
|
function getSkillLockPath$1() {
|
|
1723
1798
|
return join(homedir(), AGENTS_DIR$1, LOCK_FILE$1);
|
|
1724
1799
|
}
|
|
@@ -1728,7 +1803,7 @@ async function readSkillLock$1() {
|
|
|
1728
1803
|
const content = await readFile(lockPath, "utf-8");
|
|
1729
1804
|
const parsed = JSON.parse(content);
|
|
1730
1805
|
if (typeof parsed.version !== "number" || !parsed.skills) return createEmptyLockFile();
|
|
1731
|
-
if (parsed.version < CURRENT_VERSION) return createEmptyLockFile();
|
|
1806
|
+
if (parsed.version < CURRENT_VERSION$1) return createEmptyLockFile();
|
|
1732
1807
|
return parsed;
|
|
1733
1808
|
} catch (error) {
|
|
1734
1809
|
return createEmptyLockFile();
|
|
@@ -1799,9 +1874,12 @@ async function removeSkillFromLock(skillName) {
|
|
|
1799
1874
|
async function getSkillFromLock(skillName) {
|
|
1800
1875
|
return (await readSkillLock$1()).skills[skillName] ?? null;
|
|
1801
1876
|
}
|
|
1877
|
+
async function getAllLockedSkills() {
|
|
1878
|
+
return (await readSkillLock$1()).skills;
|
|
1879
|
+
}
|
|
1802
1880
|
function createEmptyLockFile() {
|
|
1803
1881
|
return {
|
|
1804
|
-
version: CURRENT_VERSION,
|
|
1882
|
+
version: CURRENT_VERSION$1,
|
|
1805
1883
|
skills: {},
|
|
1806
1884
|
dismissed: {}
|
|
1807
1885
|
};
|
|
@@ -1823,8 +1901,74 @@ async function saveSelectedAgents(agents) {
|
|
|
1823
1901
|
lock.lastSelectedAgents = agents;
|
|
1824
1902
|
await writeSkillLock(lock);
|
|
1825
1903
|
}
|
|
1826
|
-
|
|
1827
|
-
const
|
|
1904
|
+
const LOCAL_LOCK_FILE = "skills-lock.json";
|
|
1905
|
+
const CURRENT_VERSION = 1;
|
|
1906
|
+
function getLocalLockPath(cwd) {
|
|
1907
|
+
return join(cwd || process.cwd(), LOCAL_LOCK_FILE);
|
|
1908
|
+
}
|
|
1909
|
+
async function readLocalLock(cwd) {
|
|
1910
|
+
const lockPath = getLocalLockPath(cwd);
|
|
1911
|
+
try {
|
|
1912
|
+
const content = await readFile(lockPath, "utf-8");
|
|
1913
|
+
const parsed = JSON.parse(content);
|
|
1914
|
+
if (typeof parsed.version !== "number" || !parsed.skills) return createEmptyLocalLock();
|
|
1915
|
+
if (parsed.version < CURRENT_VERSION) return createEmptyLocalLock();
|
|
1916
|
+
return parsed;
|
|
1917
|
+
} catch {
|
|
1918
|
+
return createEmptyLocalLock();
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
async function writeLocalLock(lock, cwd) {
|
|
1922
|
+
const lockPath = getLocalLockPath(cwd);
|
|
1923
|
+
const sortedSkills = {};
|
|
1924
|
+
for (const key of Object.keys(lock.skills).sort()) sortedSkills[key] = lock.skills[key];
|
|
1925
|
+
const sorted = {
|
|
1926
|
+
version: lock.version,
|
|
1927
|
+
skills: sortedSkills
|
|
1928
|
+
};
|
|
1929
|
+
await writeFile(lockPath, JSON.stringify(sorted, null, 2) + "\n", "utf-8");
|
|
1930
|
+
}
|
|
1931
|
+
async function computeSkillFolderHash(skillDir) {
|
|
1932
|
+
const files = [];
|
|
1933
|
+
await collectFiles(skillDir, skillDir, files);
|
|
1934
|
+
files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
1935
|
+
const hash = createHash("sha256");
|
|
1936
|
+
for (const file of files) {
|
|
1937
|
+
hash.update(file.relativePath);
|
|
1938
|
+
hash.update(file.content);
|
|
1939
|
+
}
|
|
1940
|
+
return hash.digest("hex");
|
|
1941
|
+
}
|
|
1942
|
+
async function collectFiles(baseDir, currentDir, results) {
|
|
1943
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
1944
|
+
await Promise.all(entries.map(async (entry) => {
|
|
1945
|
+
const fullPath = join(currentDir, entry.name);
|
|
1946
|
+
if (entry.isDirectory()) {
|
|
1947
|
+
if (entry.name === ".git" || entry.name === "node_modules") return;
|
|
1948
|
+
await collectFiles(baseDir, fullPath, results);
|
|
1949
|
+
} else if (entry.isFile()) {
|
|
1950
|
+
const content = await readFile(fullPath);
|
|
1951
|
+
const relativePath = relative(baseDir, fullPath).split("\\").join("/");
|
|
1952
|
+
results.push({
|
|
1953
|
+
relativePath,
|
|
1954
|
+
content
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
}));
|
|
1958
|
+
}
|
|
1959
|
+
async function addSkillToLocalLock(skillName, entry, cwd) {
|
|
1960
|
+
const lock = await readLocalLock(cwd);
|
|
1961
|
+
lock.skills[skillName] = entry;
|
|
1962
|
+
await writeLocalLock(lock, cwd);
|
|
1963
|
+
}
|
|
1964
|
+
function createEmptyLocalLock() {
|
|
1965
|
+
return {
|
|
1966
|
+
version: CURRENT_VERSION,
|
|
1967
|
+
skills: {}
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
var version$1 = "1.4.2";
|
|
1971
|
+
const isCancelled$1 = (value) => typeof value === "symbol";
|
|
1828
1972
|
async function isSourcePrivate(source) {
|
|
1829
1973
|
const ownerRepo = parseOwnerRepo(source);
|
|
1830
1974
|
if (!ownerRepo) return false;
|
|
@@ -1875,7 +2019,7 @@ function buildSecurityLines(auditData, skills, source) {
|
|
|
1875
2019
|
lines.push(`${import_picocolors.default.dim("Details:")} ${import_picocolors.default.dim(`https://skills.sh/${source}`)}`);
|
|
1876
2020
|
return lines;
|
|
1877
2021
|
}
|
|
1878
|
-
function shortenPath$
|
|
2022
|
+
function shortenPath$2(fullPath, cwd) {
|
|
1879
2023
|
const home = homedir();
|
|
1880
2024
|
if (fullPath === home || fullPath.startsWith(home + sep)) return "~" + fullPath.slice(home.length);
|
|
1881
2025
|
if (fullPath === cwd || fullPath.startsWith(cwd + sep)) return "." + fullPath.slice(cwd.length);
|
|
@@ -1952,7 +2096,7 @@ async function promptForAgents(message, choices) {
|
|
|
1952
2096
|
initialSelected: initialValues,
|
|
1953
2097
|
required: true
|
|
1954
2098
|
});
|
|
1955
|
-
if (!isCancelled(selected)) try {
|
|
2099
|
+
if (!isCancelled$1(selected)) try {
|
|
1956
2100
|
await saveSelectedAgents(selected);
|
|
1957
2101
|
} catch {}
|
|
1958
2102
|
return selected;
|
|
@@ -1983,7 +2127,7 @@ async function selectAgentsInteractive(options) {
|
|
|
1983
2127
|
initialSelected: lastSelected ? lastSelected.filter((a) => otherAgents.includes(a) && !universalAgents.includes(a)) : [],
|
|
1984
2128
|
lockedSection: universalSection
|
|
1985
2129
|
});
|
|
1986
|
-
if (!isCancelled(selected)) try {
|
|
2130
|
+
if (!isCancelled$1(selected)) try {
|
|
1987
2131
|
await saveSelectedAgents(selected);
|
|
1988
2132
|
} catch {}
|
|
1989
2133
|
return selected;
|
|
@@ -2040,7 +2184,7 @@ async function handleRemoteSkill(source, url, options, spinner) {
|
|
|
2040
2184
|
M.info(`Valid agents: ${validAgents.join(", ")}`);
|
|
2041
2185
|
process.exit(1);
|
|
2042
2186
|
}
|
|
2043
|
-
targetAgents =
|
|
2187
|
+
targetAgents = options.agent;
|
|
2044
2188
|
} else {
|
|
2045
2189
|
spinner.start("Loading agents...");
|
|
2046
2190
|
const installedAgents = await detectInstalledAgents();
|
|
@@ -2092,8 +2236,8 @@ async function handleRemoteSkill(source, url, options, spinner) {
|
|
|
2092
2236
|
}
|
|
2093
2237
|
installGlobally = scope;
|
|
2094
2238
|
}
|
|
2095
|
-
let installMode = "symlink";
|
|
2096
|
-
if (!options.yes) {
|
|
2239
|
+
let installMode = options.copy ? "copy" : "symlink";
|
|
2240
|
+
if (!options.copy && !options.yes) {
|
|
2097
2241
|
const modeChoice = await ve({
|
|
2098
2242
|
message: "Installation method",
|
|
2099
2243
|
options: [{
|
|
@@ -2119,7 +2263,7 @@ async function handleRemoteSkill(source, url, options, spinner) {
|
|
|
2119
2263
|
})));
|
|
2120
2264
|
const overwriteStatus = new Map(overwriteChecks.map(({ agent, installed }) => [agent, installed]));
|
|
2121
2265
|
const summaryLines = [];
|
|
2122
|
-
const shortCanonical = shortenPath$
|
|
2266
|
+
const shortCanonical = shortenPath$2(getCanonicalPath(remoteSkill.installName, { global: installGlobally }), cwd);
|
|
2123
2267
|
summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
|
|
2124
2268
|
summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
|
|
2125
2269
|
const overwriteAgents = targetAgents.filter((a) => overwriteStatus.get(a)).map((a) => agents[a].displayName);
|
|
@@ -2172,18 +2316,27 @@ async function handleRemoteSkill(source, url, options, spinner) {
|
|
|
2172
2316
|
skillFolderHash
|
|
2173
2317
|
});
|
|
2174
2318
|
} catch {}
|
|
2319
|
+
if (successful.length > 0 && !installGlobally) try {
|
|
2320
|
+
const firstResult = successful[0];
|
|
2321
|
+
const computedHash = await computeSkillFolderHash(firstResult.canonicalPath || firstResult.path);
|
|
2322
|
+
await addSkillToLocalLock(remoteSkill.installName, {
|
|
2323
|
+
source: remoteSkill.sourceIdentifier,
|
|
2324
|
+
sourceType: remoteSkill.providerId,
|
|
2325
|
+
computedHash
|
|
2326
|
+
}, cwd);
|
|
2327
|
+
} catch {}
|
|
2175
2328
|
if (successful.length > 0) {
|
|
2176
2329
|
const resultLines = [];
|
|
2177
2330
|
const firstResult = successful[0];
|
|
2178
2331
|
if (firstResult.mode === "copy") {
|
|
2179
2332
|
resultLines.push(`${import_picocolors.default.green("✓")} ${remoteSkill.installName} ${import_picocolors.default.dim("(copied)")}`);
|
|
2180
2333
|
for (const r of successful) {
|
|
2181
|
-
const shortPath = shortenPath$
|
|
2334
|
+
const shortPath = shortenPath$2(r.path, cwd);
|
|
2182
2335
|
resultLines.push(` ${import_picocolors.default.dim("→")} ${shortPath}`);
|
|
2183
2336
|
}
|
|
2184
2337
|
} else {
|
|
2185
2338
|
if (firstResult.canonicalPath) {
|
|
2186
|
-
const shortPath = shortenPath$
|
|
2339
|
+
const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
|
|
2187
2340
|
resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
|
|
2188
2341
|
} else resultLines.push(`${import_picocolors.default.green("✓")} ${remoteSkill.installName}`);
|
|
2189
2342
|
resultLines.push(...buildResultLines(successful, targetAgents));
|
|
@@ -2244,7 +2397,6 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2244
2397
|
for (const s of skills) M.message(` - ${s.installName}`);
|
|
2245
2398
|
process.exit(1);
|
|
2246
2399
|
}
|
|
2247
|
-
M.info(`Selected ${selectedSkills.length} skill${selectedSkills.length !== 1 ? "s" : ""}: ${selectedSkills.map((s) => import_picocolors.default.cyan(s.installName)).join(", ")}`);
|
|
2248
2400
|
} else if (skills.length === 1) {
|
|
2249
2401
|
selectedSkills = skills;
|
|
2250
2402
|
const firstSkill = skills[0];
|
|
@@ -2302,7 +2454,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2302
2454
|
targetAgents = selected;
|
|
2303
2455
|
}
|
|
2304
2456
|
else if (installedAgents.length === 1 || options.yes) {
|
|
2305
|
-
targetAgents = installedAgents;
|
|
2457
|
+
targetAgents = ensureUniversalAgents(installedAgents);
|
|
2306
2458
|
if (installedAgents.length === 1) {
|
|
2307
2459
|
const firstAgent = installedAgents[0];
|
|
2308
2460
|
M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
|
|
@@ -2337,8 +2489,8 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2337
2489
|
}
|
|
2338
2490
|
installGlobally = scope;
|
|
2339
2491
|
}
|
|
2340
|
-
let installMode = "symlink";
|
|
2341
|
-
if (!options.yes) {
|
|
2492
|
+
let installMode = options.copy ? "copy" : "symlink";
|
|
2493
|
+
if (!options.copy && !options.yes) {
|
|
2342
2494
|
const modeChoice = await ve({
|
|
2343
2495
|
message: "Installation method",
|
|
2344
2496
|
options: [{
|
|
@@ -2372,7 +2524,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2372
2524
|
}
|
|
2373
2525
|
for (const skill of selectedSkills) {
|
|
2374
2526
|
if (summaryLines.length > 0) summaryLines.push("");
|
|
2375
|
-
const shortCanonical = shortenPath$
|
|
2527
|
+
const shortCanonical = shortenPath$2(getCanonicalPath(skill.installName, { global: installGlobally }), cwd);
|
|
2376
2528
|
summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
|
|
2377
2529
|
summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
|
|
2378
2530
|
if (skill.files.size > 1) summaryLines.push(` ${import_picocolors.default.dim("files:")} ${skill.files.size}`);
|
|
@@ -2429,6 +2581,21 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2429
2581
|
});
|
|
2430
2582
|
} catch {}
|
|
2431
2583
|
}
|
|
2584
|
+
if (successful.length > 0 && !installGlobally) {
|
|
2585
|
+
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
2586
|
+
for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
|
|
2587
|
+
const matchingResult = successful.find((r) => r.skill === skill.installName);
|
|
2588
|
+
const installDir = matchingResult?.canonicalPath || matchingResult?.path;
|
|
2589
|
+
if (installDir) {
|
|
2590
|
+
const computedHash = await computeSkillFolderHash(installDir);
|
|
2591
|
+
await addSkillToLocalLock(skill.installName, {
|
|
2592
|
+
source: sourceIdentifier,
|
|
2593
|
+
sourceType: "well-known",
|
|
2594
|
+
computedHash
|
|
2595
|
+
}, cwd);
|
|
2596
|
+
}
|
|
2597
|
+
} catch {}
|
|
2598
|
+
}
|
|
2432
2599
|
if (successful.length > 0) {
|
|
2433
2600
|
const bySkill = /* @__PURE__ */ new Map();
|
|
2434
2601
|
for (const r of successful) {
|
|
@@ -2445,12 +2612,12 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2445
2612
|
if (firstResult.mode === "copy") {
|
|
2446
2613
|
resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim("(copied)")}`);
|
|
2447
2614
|
for (const r of skillResults) {
|
|
2448
|
-
const shortPath = shortenPath$
|
|
2615
|
+
const shortPath = shortenPath$2(r.path, cwd);
|
|
2449
2616
|
resultLines.push(` ${import_picocolors.default.dim("→")} ${shortPath}`);
|
|
2450
2617
|
}
|
|
2451
2618
|
} else {
|
|
2452
2619
|
if (firstResult.canonicalPath) {
|
|
2453
|
-
const shortPath = shortenPath$
|
|
2620
|
+
const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
|
|
2454
2621
|
resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
|
|
2455
2622
|
} else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName}`);
|
|
2456
2623
|
resultLines.push(...buildResultLines(skillResults, targetAgents));
|
|
@@ -2536,7 +2703,7 @@ async function handleDirectUrlSkillLegacy(source, url, options, spinner) {
|
|
|
2536
2703
|
targetAgents = selected;
|
|
2537
2704
|
}
|
|
2538
2705
|
else if (installedAgents.length === 1 || options.yes) {
|
|
2539
|
-
targetAgents = installedAgents;
|
|
2706
|
+
targetAgents = ensureUniversalAgents(installedAgents);
|
|
2540
2707
|
if (installedAgents.length === 1) {
|
|
2541
2708
|
const firstAgent = installedAgents[0];
|
|
2542
2709
|
M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
|
|
@@ -2580,7 +2747,7 @@ async function handleDirectUrlSkillLegacy(source, url, options, spinner) {
|
|
|
2580
2747
|
const overwriteStatus = new Map(overwriteChecks.map(({ agent, installed }) => [agent, installed]));
|
|
2581
2748
|
const summaryLines = [];
|
|
2582
2749
|
targetAgents.map((a) => agents[a].displayName);
|
|
2583
|
-
const shortCanonical = shortenPath$
|
|
2750
|
+
const shortCanonical = shortenPath$2(getCanonicalPath(remoteSkill.installName, { global: installGlobally }), cwd);
|
|
2584
2751
|
summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
|
|
2585
2752
|
summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
|
|
2586
2753
|
const overwriteAgents = targetAgents.filter((a) => overwriteStatus.get(a)).map((a) => agents[a].displayName);
|
|
@@ -2632,7 +2799,7 @@ async function handleDirectUrlSkillLegacy(source, url, options, spinner) {
|
|
|
2632
2799
|
const resultLines = [];
|
|
2633
2800
|
const firstResult = successful[0];
|
|
2634
2801
|
if (firstResult.canonicalPath) {
|
|
2635
|
-
const shortPath = shortenPath$
|
|
2802
|
+
const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
|
|
2636
2803
|
resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
|
|
2637
2804
|
} else resultLines.push(`${import_picocolors.default.green("✓")} ${remoteSkill.installName}`);
|
|
2638
2805
|
resultLines.push(...buildResultLines(successful, targetAgents));
|
|
@@ -2732,9 +2899,29 @@ async function runAdd(args, options = {}) {
|
|
|
2732
2899
|
if (options.list) {
|
|
2733
2900
|
console.log();
|
|
2734
2901
|
M.step(import_picocolors.default.bold("Available Skills"));
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2902
|
+
const groupedSkills = {};
|
|
2903
|
+
const ungroupedSkills = [];
|
|
2904
|
+
for (const skill of skills) if (skill.pluginName) {
|
|
2905
|
+
const group = skill.pluginName;
|
|
2906
|
+
if (!groupedSkills[group]) groupedSkills[group] = [];
|
|
2907
|
+
groupedSkills[group].push(skill);
|
|
2908
|
+
} else ungroupedSkills.push(skill);
|
|
2909
|
+
const sortedGroups = Object.keys(groupedSkills).sort();
|
|
2910
|
+
for (const group of sortedGroups) {
|
|
2911
|
+
const title = group.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
2912
|
+
console.log(import_picocolors.default.bold(title));
|
|
2913
|
+
for (const skill of groupedSkills[group]) {
|
|
2914
|
+
M.message(` ${import_picocolors.default.cyan(getSkillDisplayName(skill))}`);
|
|
2915
|
+
M.message(` ${import_picocolors.default.dim(skill.description)}`);
|
|
2916
|
+
}
|
|
2917
|
+
console.log();
|
|
2918
|
+
}
|
|
2919
|
+
if (ungroupedSkills.length > 0) {
|
|
2920
|
+
if (sortedGroups.length > 0) console.log(import_picocolors.default.bold("General"));
|
|
2921
|
+
for (const skill of ungroupedSkills) {
|
|
2922
|
+
M.message(` ${import_picocolors.default.cyan(getSkillDisplayName(skill))}`);
|
|
2923
|
+
M.message(` ${import_picocolors.default.dim(skill.description)}`);
|
|
2924
|
+
}
|
|
2738
2925
|
}
|
|
2739
2926
|
console.log();
|
|
2740
2927
|
Se("Use --skill <name> to install specific skills");
|
|
@@ -2764,9 +2951,34 @@ async function runAdd(args, options = {}) {
|
|
|
2764
2951
|
selectedSkills = skills;
|
|
2765
2952
|
M.info(`Installing all ${skills.length} skills`);
|
|
2766
2953
|
} else {
|
|
2767
|
-
const
|
|
2954
|
+
const sortedSkills = [...skills].sort((a, b) => {
|
|
2955
|
+
if (a.pluginName && !b.pluginName) return -1;
|
|
2956
|
+
if (!a.pluginName && b.pluginName) return 1;
|
|
2957
|
+
if (a.pluginName && b.pluginName && a.pluginName !== b.pluginName) return a.pluginName.localeCompare(b.pluginName);
|
|
2958
|
+
return getSkillDisplayName(a).localeCompare(getSkillDisplayName(b));
|
|
2959
|
+
});
|
|
2960
|
+
const hasGroups = sortedSkills.some((s) => s.pluginName);
|
|
2961
|
+
let selected;
|
|
2962
|
+
if (hasGroups) {
|
|
2963
|
+
const kebabToTitle = (s) => s.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
2964
|
+
const grouped = {};
|
|
2965
|
+
for (const s of sortedSkills) {
|
|
2966
|
+
const groupName = s.pluginName ? kebabToTitle(s.pluginName) : "Other";
|
|
2967
|
+
if (!grouped[groupName]) grouped[groupName] = [];
|
|
2968
|
+
grouped[groupName].push({
|
|
2969
|
+
value: s,
|
|
2970
|
+
label: getSkillDisplayName(s),
|
|
2971
|
+
hint: s.description.length > 60 ? s.description.slice(0, 57) + "..." : s.description
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
selected = await be({
|
|
2975
|
+
message: `Select skills to install ${import_picocolors.default.dim("(space to toggle)")}`,
|
|
2976
|
+
options: grouped,
|
|
2977
|
+
required: true
|
|
2978
|
+
});
|
|
2979
|
+
} else selected = await multiselect({
|
|
2768
2980
|
message: "Select skills to install",
|
|
2769
|
-
options:
|
|
2981
|
+
options: sortedSkills.map((s) => ({
|
|
2770
2982
|
value: s,
|
|
2771
2983
|
label: getSkillDisplayName(s),
|
|
2772
2984
|
hint: s.description.length > 60 ? s.description.slice(0, 57) + "..." : s.description
|
|
@@ -2818,7 +3030,7 @@ async function runAdd(args, options = {}) {
|
|
|
2818
3030
|
targetAgents = selected;
|
|
2819
3031
|
}
|
|
2820
3032
|
else if (installedAgents.length === 1 || options.yes) {
|
|
2821
|
-
targetAgents = installedAgents;
|
|
3033
|
+
targetAgents = ensureUniversalAgents(installedAgents);
|
|
2822
3034
|
if (installedAgents.length === 1) {
|
|
2823
3035
|
const firstAgent = installedAgents[0];
|
|
2824
3036
|
M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
|
|
@@ -2855,8 +3067,8 @@ async function runAdd(args, options = {}) {
|
|
|
2855
3067
|
}
|
|
2856
3068
|
installGlobally = scope;
|
|
2857
3069
|
}
|
|
2858
|
-
let installMode = "symlink";
|
|
2859
|
-
if (!options.yes) {
|
|
3070
|
+
let installMode = options.copy ? "copy" : "symlink";
|
|
3071
|
+
if (!options.copy && !options.yes) {
|
|
2860
3072
|
const modeChoice = await ve({
|
|
2861
3073
|
message: "Installation method",
|
|
2862
3074
|
options: [{
|
|
@@ -2889,14 +3101,37 @@ async function runAdd(args, options = {}) {
|
|
|
2889
3101
|
if (!overwriteStatus.has(skillName)) overwriteStatus.set(skillName, /* @__PURE__ */ new Map());
|
|
2890
3102
|
overwriteStatus.get(skillName).set(agent, installed);
|
|
2891
3103
|
}
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
3104
|
+
const groupedSummary = {};
|
|
3105
|
+
const ungroupedSummary = [];
|
|
3106
|
+
for (const skill of selectedSkills) if (skill.pluginName) {
|
|
3107
|
+
const group = skill.pluginName;
|
|
3108
|
+
if (!groupedSummary[group]) groupedSummary[group] = [];
|
|
3109
|
+
groupedSummary[group].push(skill);
|
|
3110
|
+
} else ungroupedSummary.push(skill);
|
|
3111
|
+
const printSkillSummary = (skills) => {
|
|
3112
|
+
for (const skill of skills) {
|
|
3113
|
+
if (summaryLines.length > 0) summaryLines.push("");
|
|
3114
|
+
const shortCanonical = shortenPath$2(getCanonicalPath(skill.name, { global: installGlobally }), cwd);
|
|
3115
|
+
summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
|
|
3116
|
+
summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
|
|
3117
|
+
const skillOverwrites = overwriteStatus.get(skill.name);
|
|
3118
|
+
const overwriteAgents = targetAgents.filter((a) => skillOverwrites?.get(a)).map((a) => agents[a].displayName);
|
|
3119
|
+
if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList$1(overwriteAgents)}`);
|
|
3120
|
+
}
|
|
3121
|
+
};
|
|
3122
|
+
const sortedGroups = Object.keys(groupedSummary).sort();
|
|
3123
|
+
for (const group of sortedGroups) {
|
|
3124
|
+
const title = group.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
3125
|
+
summaryLines.push("");
|
|
3126
|
+
summaryLines.push(import_picocolors.default.bold(title));
|
|
3127
|
+
printSkillSummary(groupedSummary[group]);
|
|
3128
|
+
}
|
|
3129
|
+
if (ungroupedSummary.length > 0) {
|
|
3130
|
+
if (sortedGroups.length > 0) {
|
|
3131
|
+
summaryLines.push("");
|
|
3132
|
+
summaryLines.push(import_picocolors.default.bold("General"));
|
|
3133
|
+
}
|
|
3134
|
+
printSkillSummary(ungroupedSummary);
|
|
2900
3135
|
}
|
|
2901
3136
|
console.log();
|
|
2902
3137
|
Me(summaryLines.join("\n"), "Installation Summary");
|
|
@@ -2928,6 +3163,7 @@ async function runAdd(args, options = {}) {
|
|
|
2928
3163
|
results.push({
|
|
2929
3164
|
skill: getSkillDisplayName(skill),
|
|
2930
3165
|
agent: agents[agent].displayName,
|
|
3166
|
+
pluginName: skill.pluginName,
|
|
2931
3167
|
...result
|
|
2932
3168
|
});
|
|
2933
3169
|
}
|
|
@@ -2980,37 +3216,76 @@ async function runAdd(args, options = {}) {
|
|
|
2980
3216
|
sourceType: parsed.type,
|
|
2981
3217
|
sourceUrl: parsed.url,
|
|
2982
3218
|
skillPath: skillPathValue,
|
|
2983
|
-
skillFolderHash
|
|
3219
|
+
skillFolderHash,
|
|
3220
|
+
pluginName: skill.pluginName
|
|
2984
3221
|
});
|
|
2985
3222
|
} catch {}
|
|
2986
3223
|
}
|
|
2987
3224
|
}
|
|
3225
|
+
if (successful.length > 0 && !installGlobally) {
|
|
3226
|
+
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
3227
|
+
for (const skill of selectedSkills) {
|
|
3228
|
+
const skillDisplayName = getSkillDisplayName(skill);
|
|
3229
|
+
if (successfulSkillNames.has(skillDisplayName)) try {
|
|
3230
|
+
const computedHash = await computeSkillFolderHash(skill.path);
|
|
3231
|
+
await addSkillToLocalLock(skill.name, {
|
|
3232
|
+
source: normalizedSource || parsed.url,
|
|
3233
|
+
sourceType: parsed.type,
|
|
3234
|
+
computedHash
|
|
3235
|
+
}, cwd);
|
|
3236
|
+
} catch {}
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
2988
3239
|
if (successful.length > 0) {
|
|
2989
3240
|
const bySkill = /* @__PURE__ */ new Map();
|
|
3241
|
+
const groupedResults = {};
|
|
3242
|
+
const ungroupedResults = [];
|
|
2990
3243
|
for (const r of successful) {
|
|
2991
3244
|
const skillResults = bySkill.get(r.skill) || [];
|
|
2992
3245
|
skillResults.push(r);
|
|
2993
3246
|
bySkill.set(r.skill, skillResults);
|
|
3247
|
+
if (skillResults.length === 1) if (r.pluginName) {
|
|
3248
|
+
const group = r.pluginName;
|
|
3249
|
+
if (!groupedResults[group]) groupedResults[group] = [];
|
|
3250
|
+
groupedResults[group].push(r);
|
|
3251
|
+
} else ungroupedResults.push(r);
|
|
2994
3252
|
}
|
|
2995
3253
|
const skillCount = bySkill.size;
|
|
2996
3254
|
const symlinkFailures = successful.filter((r) => r.mode === "symlink" && r.symlinkFailed);
|
|
2997
3255
|
const copiedAgents = symlinkFailures.map((r) => r.agent);
|
|
2998
3256
|
const resultLines = [];
|
|
2999
|
-
|
|
3000
|
-
const
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3257
|
+
const printSkillResults = (entries) => {
|
|
3258
|
+
for (const entry of entries) {
|
|
3259
|
+
const skillResults = bySkill.get(entry.skill) || [];
|
|
3260
|
+
const firstResult = skillResults[0];
|
|
3261
|
+
if (firstResult.mode === "copy") {
|
|
3262
|
+
resultLines.push(`${import_picocolors.default.green("✓")} ${entry.skill} ${import_picocolors.default.dim("(copied)")}`);
|
|
3263
|
+
for (const r of skillResults) {
|
|
3264
|
+
const shortPath = shortenPath$2(r.path, cwd);
|
|
3265
|
+
resultLines.push(` ${import_picocolors.default.dim("→")} ${shortPath}`);
|
|
3266
|
+
}
|
|
3267
|
+
} else {
|
|
3268
|
+
if (firstResult.canonicalPath) {
|
|
3269
|
+
const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
|
|
3270
|
+
resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
|
|
3271
|
+
} else resultLines.push(`${import_picocolors.default.green("✓")} ${entry.skill}`);
|
|
3272
|
+
resultLines.push(...buildResultLines(skillResults, targetAgents));
|
|
3006
3273
|
}
|
|
3007
|
-
} else {
|
|
3008
|
-
if (firstResult.canonicalPath) {
|
|
3009
|
-
const shortPath = shortenPath$1(firstResult.canonicalPath, cwd);
|
|
3010
|
-
resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
|
|
3011
|
-
} else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName}`);
|
|
3012
|
-
resultLines.push(...buildResultLines(skillResults, targetAgents));
|
|
3013
3274
|
}
|
|
3275
|
+
};
|
|
3276
|
+
const sortedResultGroups = Object.keys(groupedResults).sort();
|
|
3277
|
+
for (const group of sortedResultGroups) {
|
|
3278
|
+
const title = group.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
3279
|
+
resultLines.push("");
|
|
3280
|
+
resultLines.push(import_picocolors.default.bold(title));
|
|
3281
|
+
printSkillResults(groupedResults[group]);
|
|
3282
|
+
}
|
|
3283
|
+
if (ungroupedResults.length > 0) {
|
|
3284
|
+
if (sortedResultGroups.length > 0) {
|
|
3285
|
+
resultLines.push("");
|
|
3286
|
+
resultLines.push(import_picocolors.default.bold("General"));
|
|
3287
|
+
}
|
|
3288
|
+
printSkillResults(ungroupedResults);
|
|
3014
3289
|
}
|
|
3015
3290
|
const title = import_picocolors.default.green(`Installed ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
|
|
3016
3291
|
Me(resultLines.join("\n"), title);
|
|
@@ -3113,6 +3388,7 @@ function parseAddOptions(args) {
|
|
|
3113
3388
|
}
|
|
3114
3389
|
i--;
|
|
3115
3390
|
} else if (arg === "--full-depth") options.fullDepth = true;
|
|
3391
|
+
else if (arg === "--copy") options.copy = true;
|
|
3116
3392
|
else if (arg && !arg.startsWith("-")) source.push(arg);
|
|
3117
3393
|
}
|
|
3118
3394
|
return {
|
|
@@ -3342,6 +3618,340 @@ ${DIM$2} 2) npx skills add <owner/repo@skill>${RESET$2}`;
|
|
|
3342
3618
|
else console.log(`${DIM$2}Discover more skills at${RESET$2} ${TEXT$1}https://skills.sh${RESET$2}`);
|
|
3343
3619
|
console.log();
|
|
3344
3620
|
}
|
|
3621
|
+
const isCancelled = (value) => typeof value === "symbol";
|
|
3622
|
+
function shortenPath$1(fullPath, cwd) {
|
|
3623
|
+
const home = homedir();
|
|
3624
|
+
if (fullPath === home || fullPath.startsWith(home + sep)) return "~" + fullPath.slice(home.length);
|
|
3625
|
+
if (fullPath === cwd || fullPath.startsWith(cwd + sep)) return "." + fullPath.slice(cwd.length);
|
|
3626
|
+
return fullPath;
|
|
3627
|
+
}
|
|
3628
|
+
async function discoverNodeModuleSkills(cwd) {
|
|
3629
|
+
const nodeModulesDir = join(cwd, "node_modules");
|
|
3630
|
+
const skills = [];
|
|
3631
|
+
let topNames;
|
|
3632
|
+
try {
|
|
3633
|
+
topNames = await readdir(nodeModulesDir);
|
|
3634
|
+
} catch {
|
|
3635
|
+
return skills;
|
|
3636
|
+
}
|
|
3637
|
+
const processPackageDir = async (pkgDir, packageName) => {
|
|
3638
|
+
const rootSkill = await parseSkillMd(join(pkgDir, "SKILL.md"));
|
|
3639
|
+
if (rootSkill) {
|
|
3640
|
+
skills.push({
|
|
3641
|
+
...rootSkill,
|
|
3642
|
+
packageName
|
|
3643
|
+
});
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3646
|
+
const searchDirs = [
|
|
3647
|
+
pkgDir,
|
|
3648
|
+
join(pkgDir, "skills"),
|
|
3649
|
+
join(pkgDir, ".agents", "skills")
|
|
3650
|
+
];
|
|
3651
|
+
for (const searchDir of searchDirs) try {
|
|
3652
|
+
const entries = await readdir(searchDir);
|
|
3653
|
+
for (const name of entries) {
|
|
3654
|
+
const skillDir = join(searchDir, name);
|
|
3655
|
+
try {
|
|
3656
|
+
if (!(await stat(skillDir)).isDirectory()) continue;
|
|
3657
|
+
} catch {
|
|
3658
|
+
continue;
|
|
3659
|
+
}
|
|
3660
|
+
const skill = await parseSkillMd(join(skillDir, "SKILL.md"));
|
|
3661
|
+
if (skill) skills.push({
|
|
3662
|
+
...skill,
|
|
3663
|
+
packageName
|
|
3664
|
+
});
|
|
3665
|
+
}
|
|
3666
|
+
} catch {}
|
|
3667
|
+
};
|
|
3668
|
+
await Promise.all(topNames.map(async (name) => {
|
|
3669
|
+
if (name.startsWith(".")) return;
|
|
3670
|
+
const fullPath = join(nodeModulesDir, name);
|
|
3671
|
+
try {
|
|
3672
|
+
if (!(await stat(fullPath)).isDirectory()) return;
|
|
3673
|
+
} catch {
|
|
3674
|
+
return;
|
|
3675
|
+
}
|
|
3676
|
+
if (name.startsWith("@")) try {
|
|
3677
|
+
const scopeNames = await readdir(fullPath);
|
|
3678
|
+
await Promise.all(scopeNames.map(async (scopedName) => {
|
|
3679
|
+
const scopedPath = join(fullPath, scopedName);
|
|
3680
|
+
try {
|
|
3681
|
+
if (!(await stat(scopedPath)).isDirectory()) return;
|
|
3682
|
+
} catch {
|
|
3683
|
+
return;
|
|
3684
|
+
}
|
|
3685
|
+
await processPackageDir(scopedPath, `${name}/${scopedName}`);
|
|
3686
|
+
}));
|
|
3687
|
+
} catch {}
|
|
3688
|
+
else await processPackageDir(fullPath, name);
|
|
3689
|
+
}));
|
|
3690
|
+
return skills;
|
|
3691
|
+
}
|
|
3692
|
+
async function runSync(args, options = {}) {
|
|
3693
|
+
const cwd = process.cwd();
|
|
3694
|
+
console.log();
|
|
3695
|
+
Ie(import_picocolors.default.bgCyan(import_picocolors.default.black(" skills experimental_sync ")));
|
|
3696
|
+
const spinner = Y();
|
|
3697
|
+
spinner.start("Scanning node_modules for skills...");
|
|
3698
|
+
const discoveredSkills = await discoverNodeModuleSkills(cwd);
|
|
3699
|
+
if (discoveredSkills.length === 0) {
|
|
3700
|
+
spinner.stop(import_picocolors.default.yellow("No skills found"));
|
|
3701
|
+
Se(import_picocolors.default.dim("No SKILL.md files found in node_modules."));
|
|
3702
|
+
return;
|
|
3703
|
+
}
|
|
3704
|
+
spinner.stop(`Found ${import_picocolors.default.green(String(discoveredSkills.length))} skill${discoveredSkills.length > 1 ? "s" : ""} in node_modules`);
|
|
3705
|
+
for (const skill of discoveredSkills) {
|
|
3706
|
+
M.info(`${import_picocolors.default.cyan(skill.name)} ${import_picocolors.default.dim(`from ${skill.packageName}`)}`);
|
|
3707
|
+
if (skill.description) M.message(import_picocolors.default.dim(` ${skill.description}`));
|
|
3708
|
+
}
|
|
3709
|
+
const localLock = await readLocalLock(cwd);
|
|
3710
|
+
const toInstall = [];
|
|
3711
|
+
const upToDate = [];
|
|
3712
|
+
if (options.force) {
|
|
3713
|
+
toInstall.push(...discoveredSkills);
|
|
3714
|
+
M.info(import_picocolors.default.dim("Force mode: reinstalling all skills"));
|
|
3715
|
+
} else {
|
|
3716
|
+
for (const skill of discoveredSkills) {
|
|
3717
|
+
const existingEntry = localLock.skills[skill.name];
|
|
3718
|
+
if (existingEntry) {
|
|
3719
|
+
if (await computeSkillFolderHash(skill.path) === existingEntry.computedHash) {
|
|
3720
|
+
upToDate.push(skill.name);
|
|
3721
|
+
continue;
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
toInstall.push(skill);
|
|
3725
|
+
}
|
|
3726
|
+
if (upToDate.length > 0) M.info(import_picocolors.default.dim(`${upToDate.length} skill${upToDate.length !== 1 ? "s" : ""} already up to date`));
|
|
3727
|
+
if (toInstall.length === 0) {
|
|
3728
|
+
console.log();
|
|
3729
|
+
Se(import_picocolors.default.green("All skills are up to date."));
|
|
3730
|
+
return;
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
M.info(`${toInstall.length} skill${toInstall.length !== 1 ? "s" : ""} to install/update`);
|
|
3734
|
+
let targetAgents;
|
|
3735
|
+
const validAgents = Object.keys(agents);
|
|
3736
|
+
const universalAgents = getUniversalAgents();
|
|
3737
|
+
if (options.agent?.includes("*")) {
|
|
3738
|
+
targetAgents = validAgents;
|
|
3739
|
+
M.info(`Installing to all ${targetAgents.length} agents`);
|
|
3740
|
+
} else if (options.agent && options.agent.length > 0) {
|
|
3741
|
+
const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
|
|
3742
|
+
if (invalidAgents.length > 0) {
|
|
3743
|
+
M.error(`Invalid agents: ${invalidAgents.join(", ")}`);
|
|
3744
|
+
M.info(`Valid agents: ${validAgents.join(", ")}`);
|
|
3745
|
+
process.exit(1);
|
|
3746
|
+
}
|
|
3747
|
+
targetAgents = options.agent;
|
|
3748
|
+
} else {
|
|
3749
|
+
spinner.start("Loading agents...");
|
|
3750
|
+
const installedAgents = await detectInstalledAgents();
|
|
3751
|
+
const totalAgents = Object.keys(agents).length;
|
|
3752
|
+
spinner.stop(`${totalAgents} agents`);
|
|
3753
|
+
if (installedAgents.length === 0) if (options.yes) {
|
|
3754
|
+
targetAgents = universalAgents;
|
|
3755
|
+
M.info("Installing to universal agents");
|
|
3756
|
+
} else {
|
|
3757
|
+
const selected = await searchMultiselect({
|
|
3758
|
+
message: "Which agents do you want to install to?",
|
|
3759
|
+
items: getNonUniversalAgents().map((a) => ({
|
|
3760
|
+
value: a,
|
|
3761
|
+
label: agents[a].displayName,
|
|
3762
|
+
hint: agents[a].skillsDir
|
|
3763
|
+
})),
|
|
3764
|
+
initialSelected: [],
|
|
3765
|
+
lockedSection: {
|
|
3766
|
+
title: "Universal (.agents/skills)",
|
|
3767
|
+
items: universalAgents.map((a) => ({
|
|
3768
|
+
value: a,
|
|
3769
|
+
label: agents[a].displayName
|
|
3770
|
+
}))
|
|
3771
|
+
}
|
|
3772
|
+
});
|
|
3773
|
+
if (isCancelled(selected)) {
|
|
3774
|
+
xe("Sync cancelled");
|
|
3775
|
+
process.exit(0);
|
|
3776
|
+
}
|
|
3777
|
+
targetAgents = selected;
|
|
3778
|
+
}
|
|
3779
|
+
else if (installedAgents.length === 1 || options.yes) {
|
|
3780
|
+
targetAgents = [...installedAgents];
|
|
3781
|
+
for (const ua of universalAgents) if (!targetAgents.includes(ua)) targetAgents.push(ua);
|
|
3782
|
+
} else {
|
|
3783
|
+
const selected = await searchMultiselect({
|
|
3784
|
+
message: "Which agents do you want to install to?",
|
|
3785
|
+
items: getNonUniversalAgents().filter((a) => installedAgents.includes(a)).map((a) => ({
|
|
3786
|
+
value: a,
|
|
3787
|
+
label: agents[a].displayName,
|
|
3788
|
+
hint: agents[a].skillsDir
|
|
3789
|
+
})),
|
|
3790
|
+
initialSelected: installedAgents.filter((a) => !universalAgents.includes(a)),
|
|
3791
|
+
lockedSection: {
|
|
3792
|
+
title: "Universal (.agents/skills)",
|
|
3793
|
+
items: universalAgents.map((a) => ({
|
|
3794
|
+
value: a,
|
|
3795
|
+
label: agents[a].displayName
|
|
3796
|
+
}))
|
|
3797
|
+
}
|
|
3798
|
+
});
|
|
3799
|
+
if (isCancelled(selected)) {
|
|
3800
|
+
xe("Sync cancelled");
|
|
3801
|
+
process.exit(0);
|
|
3802
|
+
}
|
|
3803
|
+
targetAgents = selected;
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
const summaryLines = [];
|
|
3807
|
+
for (const skill of toInstall) {
|
|
3808
|
+
const shortCanonical = shortenPath$1(getCanonicalPath(skill.name, { global: false }), cwd);
|
|
3809
|
+
summaryLines.push(`${import_picocolors.default.cyan(skill.name)} ${import_picocolors.default.dim(`← ${skill.packageName}`)}`);
|
|
3810
|
+
summaryLines.push(` ${import_picocolors.default.dim(shortCanonical)}`);
|
|
3811
|
+
}
|
|
3812
|
+
console.log();
|
|
3813
|
+
Me(summaryLines.join("\n"), "Sync Summary");
|
|
3814
|
+
if (!options.yes) {
|
|
3815
|
+
const confirmed = await ye({ message: "Proceed with sync?" });
|
|
3816
|
+
if (pD(confirmed) || !confirmed) {
|
|
3817
|
+
xe("Sync cancelled");
|
|
3818
|
+
process.exit(0);
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
spinner.start("Syncing skills...");
|
|
3822
|
+
const results = [];
|
|
3823
|
+
for (const skill of toInstall) for (const agent of targetAgents) {
|
|
3824
|
+
const result = await installSkillForAgent(skill, agent, {
|
|
3825
|
+
global: false,
|
|
3826
|
+
cwd,
|
|
3827
|
+
mode: "symlink"
|
|
3828
|
+
});
|
|
3829
|
+
results.push({
|
|
3830
|
+
skill: skill.name,
|
|
3831
|
+
packageName: skill.packageName,
|
|
3832
|
+
agent: agents[agent].displayName,
|
|
3833
|
+
success: result.success,
|
|
3834
|
+
path: result.path,
|
|
3835
|
+
canonicalPath: result.canonicalPath,
|
|
3836
|
+
error: result.error
|
|
3837
|
+
});
|
|
3838
|
+
}
|
|
3839
|
+
spinner.stop("Sync complete");
|
|
3840
|
+
const successful = results.filter((r) => r.success);
|
|
3841
|
+
const failed = results.filter((r) => !r.success);
|
|
3842
|
+
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
3843
|
+
for (const skill of toInstall) if (successfulSkillNames.has(skill.name)) try {
|
|
3844
|
+
const computedHash = await computeSkillFolderHash(skill.path);
|
|
3845
|
+
await addSkillToLocalLock(skill.name, {
|
|
3846
|
+
source: skill.packageName,
|
|
3847
|
+
sourceType: "node_modules",
|
|
3848
|
+
computedHash
|
|
3849
|
+
}, cwd);
|
|
3850
|
+
} catch {}
|
|
3851
|
+
console.log();
|
|
3852
|
+
if (successful.length > 0) {
|
|
3853
|
+
const bySkill = /* @__PURE__ */ new Map();
|
|
3854
|
+
for (const r of successful) {
|
|
3855
|
+
const skillResults = bySkill.get(r.skill) || [];
|
|
3856
|
+
skillResults.push(r);
|
|
3857
|
+
bySkill.set(r.skill, skillResults);
|
|
3858
|
+
}
|
|
3859
|
+
const resultLines = [];
|
|
3860
|
+
for (const [skillName, skillResults] of bySkill) {
|
|
3861
|
+
const firstResult = skillResults[0];
|
|
3862
|
+
const pkg = toInstall.find((s) => s.name === skillName)?.packageName;
|
|
3863
|
+
if (firstResult.canonicalPath) {
|
|
3864
|
+
const shortPath = shortenPath$1(firstResult.canonicalPath, cwd);
|
|
3865
|
+
resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim(`← ${pkg}`)}`);
|
|
3866
|
+
resultLines.push(` ${import_picocolors.default.dim(shortPath)}`);
|
|
3867
|
+
} else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim(`← ${pkg}`)}`);
|
|
3868
|
+
}
|
|
3869
|
+
const skillCount = bySkill.size;
|
|
3870
|
+
const title = import_picocolors.default.green(`Synced ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
|
|
3871
|
+
Me(resultLines.join("\n"), title);
|
|
3872
|
+
}
|
|
3873
|
+
if (failed.length > 0) {
|
|
3874
|
+
console.log();
|
|
3875
|
+
M.error(import_picocolors.default.red(`Failed to install ${failed.length}`));
|
|
3876
|
+
for (const r of failed) M.message(` ${import_picocolors.default.red("✗")} ${r.skill} → ${r.agent}: ${import_picocolors.default.dim(r.error)}`);
|
|
3877
|
+
}
|
|
3878
|
+
track({
|
|
3879
|
+
event: "experimental_sync",
|
|
3880
|
+
skillCount: String(toInstall.length),
|
|
3881
|
+
successCount: String(successfulSkillNames.size),
|
|
3882
|
+
agents: targetAgents.join(",")
|
|
3883
|
+
});
|
|
3884
|
+
console.log();
|
|
3885
|
+
Se(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Review skills before use; they run with full agent permissions."));
|
|
3886
|
+
}
|
|
3887
|
+
function parseSyncOptions(args) {
|
|
3888
|
+
const options = {};
|
|
3889
|
+
for (let i = 0; i < args.length; i++) {
|
|
3890
|
+
const arg = args[i];
|
|
3891
|
+
if (arg === "-y" || arg === "--yes") options.yes = true;
|
|
3892
|
+
else if (arg === "-f" || arg === "--force") options.force = true;
|
|
3893
|
+
else if (arg === "-a" || arg === "--agent") {
|
|
3894
|
+
options.agent = options.agent || [];
|
|
3895
|
+
i++;
|
|
3896
|
+
let nextArg = args[i];
|
|
3897
|
+
while (i < args.length && nextArg && !nextArg.startsWith("-")) {
|
|
3898
|
+
options.agent.push(nextArg);
|
|
3899
|
+
i++;
|
|
3900
|
+
nextArg = args[i];
|
|
3901
|
+
}
|
|
3902
|
+
i--;
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3905
|
+
return { options };
|
|
3906
|
+
}
|
|
3907
|
+
async function runInstallFromLock(args) {
|
|
3908
|
+
const lock = await readLocalLock(process.cwd());
|
|
3909
|
+
const skillEntries = Object.entries(lock.skills);
|
|
3910
|
+
if (skillEntries.length === 0) {
|
|
3911
|
+
M.warn("No project skills found in skills-lock.json");
|
|
3912
|
+
M.info(`Add project-level skills with ${import_picocolors.default.cyan("npx skills add <package>")} (without ${import_picocolors.default.cyan("-g")})`);
|
|
3913
|
+
return;
|
|
3914
|
+
}
|
|
3915
|
+
const universalAgentNames = getUniversalAgents();
|
|
3916
|
+
const nodeModuleSkills = [];
|
|
3917
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
3918
|
+
for (const [skillName, entry] of skillEntries) {
|
|
3919
|
+
if (entry.sourceType === "node_modules") {
|
|
3920
|
+
nodeModuleSkills.push(skillName);
|
|
3921
|
+
continue;
|
|
3922
|
+
}
|
|
3923
|
+
const existing = bySource.get(entry.source);
|
|
3924
|
+
if (existing) existing.skills.push(skillName);
|
|
3925
|
+
else bySource.set(entry.source, {
|
|
3926
|
+
sourceType: entry.sourceType,
|
|
3927
|
+
skills: [skillName]
|
|
3928
|
+
});
|
|
3929
|
+
}
|
|
3930
|
+
const remoteCount = skillEntries.length - nodeModuleSkills.length;
|
|
3931
|
+
if (remoteCount > 0) M.info(`Restoring ${import_picocolors.default.cyan(String(remoteCount))} skill${remoteCount !== 1 ? "s" : ""} from skills-lock.json into ${import_picocolors.default.dim(".agents/skills/")}`);
|
|
3932
|
+
for (const [source, { skills }] of bySource) try {
|
|
3933
|
+
await runAdd([source], {
|
|
3934
|
+
skill: skills,
|
|
3935
|
+
agent: universalAgentNames,
|
|
3936
|
+
yes: true
|
|
3937
|
+
});
|
|
3938
|
+
} catch (error) {
|
|
3939
|
+
M.error(`Failed to install from ${import_picocolors.default.cyan(source)}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3940
|
+
}
|
|
3941
|
+
if (nodeModuleSkills.length > 0) {
|
|
3942
|
+
M.info(`${import_picocolors.default.cyan(String(nodeModuleSkills.length))} skill${nodeModuleSkills.length !== 1 ? "s" : ""} from node_modules`);
|
|
3943
|
+
try {
|
|
3944
|
+
const { options: syncOptions } = parseSyncOptions(args);
|
|
3945
|
+
await runSync(args, {
|
|
3946
|
+
...syncOptions,
|
|
3947
|
+
yes: true,
|
|
3948
|
+
agent: universalAgentNames
|
|
3949
|
+
});
|
|
3950
|
+
} catch (error) {
|
|
3951
|
+
M.error(`Failed to sync node_modules skills: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3345
3955
|
const RESET$1 = "\x1B[0m";
|
|
3346
3956
|
const BOLD$1 = "\x1B[1m";
|
|
3347
3957
|
const DIM$1 = "\x1B[38;5;102m";
|
|
@@ -3389,6 +3999,7 @@ async function runList(args) {
|
|
|
3389
3999
|
global: scope,
|
|
3390
4000
|
agentFilter
|
|
3391
4001
|
});
|
|
4002
|
+
const lockedSkills = await getAllLockedSkills();
|
|
3392
4003
|
const cwd = process.cwd();
|
|
3393
4004
|
const scopeLabel = scope ? "Global" : "Project";
|
|
3394
4005
|
if (installedSkills.length === 0) {
|
|
@@ -3397,17 +4008,44 @@ async function runList(args) {
|
|
|
3397
4008
|
else console.log(`${DIM$1}Try listing global skills with -g${RESET$1}`);
|
|
3398
4009
|
return;
|
|
3399
4010
|
}
|
|
3400
|
-
function printSkill(skill) {
|
|
4011
|
+
function printSkill(skill, indent = false) {
|
|
4012
|
+
const prefix = indent ? " " : "";
|
|
3401
4013
|
const shortPath = shortenPath(skill.canonicalPath, cwd);
|
|
3402
4014
|
const agentNames = skill.agents.map((a) => agents[a].displayName);
|
|
3403
4015
|
const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW}not linked${RESET$1}`;
|
|
3404
|
-
console.log(`${CYAN}${skill.name}${RESET$1} ${DIM$1}${shortPath}${RESET$1}`);
|
|
3405
|
-
console.log(
|
|
4016
|
+
console.log(`${prefix}${CYAN}${skill.name}${RESET$1} ${DIM$1}${shortPath}${RESET$1}`);
|
|
4017
|
+
console.log(`${prefix} ${DIM$1}Agents:${RESET$1} ${agentInfo}`);
|
|
3406
4018
|
}
|
|
3407
4019
|
console.log(`${BOLD$1}${scopeLabel} Skills${RESET$1}`);
|
|
3408
4020
|
console.log();
|
|
3409
|
-
|
|
3410
|
-
|
|
4021
|
+
const groupedSkills = {};
|
|
4022
|
+
const ungroupedSkills = [];
|
|
4023
|
+
for (const skill of installedSkills) {
|
|
4024
|
+
const lockEntry = lockedSkills[skill.name];
|
|
4025
|
+
if (lockEntry?.pluginName) {
|
|
4026
|
+
const group = lockEntry.pluginName;
|
|
4027
|
+
if (!groupedSkills[group]) groupedSkills[group] = [];
|
|
4028
|
+
groupedSkills[group].push(skill);
|
|
4029
|
+
} else ungroupedSkills.push(skill);
|
|
4030
|
+
}
|
|
4031
|
+
if (Object.keys(groupedSkills).length > 0) {
|
|
4032
|
+
const sortedGroups = Object.keys(groupedSkills).sort();
|
|
4033
|
+
for (const group of sortedGroups) {
|
|
4034
|
+
const title = group.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
4035
|
+
console.log(`${BOLD$1}${title}${RESET$1}`);
|
|
4036
|
+
const skills = groupedSkills[group];
|
|
4037
|
+
if (skills) for (const skill of skills) printSkill(skill, true);
|
|
4038
|
+
console.log();
|
|
4039
|
+
}
|
|
4040
|
+
if (ungroupedSkills.length > 0) {
|
|
4041
|
+
console.log(`${BOLD$1}General${RESET$1}`);
|
|
4042
|
+
for (const skill of ungroupedSkills) printSkill(skill, true);
|
|
4043
|
+
console.log();
|
|
4044
|
+
}
|
|
4045
|
+
} else {
|
|
4046
|
+
for (const skill of installedSkills) printSkill(skill);
|
|
4047
|
+
console.log();
|
|
4048
|
+
}
|
|
3411
4049
|
}
|
|
3412
4050
|
async function removeCommand(skillNames, options) {
|
|
3413
4051
|
const isGlobal = options.global ?? false;
|
|
@@ -3472,10 +4110,8 @@ async function removeCommand(skillNames, options) {
|
|
|
3472
4110
|
let targetAgents;
|
|
3473
4111
|
if (options.agent && options.agent.length > 0) targetAgents = options.agent;
|
|
3474
4112
|
else {
|
|
3475
|
-
|
|
3476
|
-
targetAgents
|
|
3477
|
-
if (targetAgents.length === 0) targetAgents = Object.keys(agents);
|
|
3478
|
-
spinner.stop(`Targeting ${targetAgents.length} installed agent(s)`);
|
|
4113
|
+
targetAgents = Object.keys(agents);
|
|
4114
|
+
spinner.stop(`Targeting ${targetAgents.length} potential agent(s)`);
|
|
3479
4115
|
}
|
|
3480
4116
|
if (!options.yes) {
|
|
3481
4117
|
console.log();
|
|
@@ -3491,25 +4127,42 @@ async function removeCommand(skillNames, options) {
|
|
|
3491
4127
|
spinner.start("Removing skills...");
|
|
3492
4128
|
const results = [];
|
|
3493
4129
|
for (const skillName of selectedSkills) try {
|
|
4130
|
+
const canonicalPath = getCanonicalPath(skillName, {
|
|
4131
|
+
global: isGlobal,
|
|
4132
|
+
cwd
|
|
4133
|
+
});
|
|
3494
4134
|
for (const agentKey of targetAgents) {
|
|
3495
4135
|
const agent = agents[agentKey];
|
|
3496
4136
|
const skillPath = getInstallPath(skillName, agentKey, {
|
|
3497
4137
|
global: isGlobal,
|
|
3498
4138
|
cwd
|
|
3499
4139
|
});
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
4140
|
+
const pathsToCleanup = new Set([skillPath]);
|
|
4141
|
+
const sanitizedName = sanitizeName(skillName);
|
|
4142
|
+
if (isGlobal && agent.globalSkillsDir) pathsToCleanup.add(join(agent.globalSkillsDir, sanitizedName));
|
|
4143
|
+
else pathsToCleanup.add(join(cwd, agent.skillsDir, sanitizedName));
|
|
4144
|
+
for (const pathToCleanup of pathsToCleanup) {
|
|
4145
|
+
if (pathToCleanup === canonicalPath) continue;
|
|
4146
|
+
try {
|
|
4147
|
+
if (await lstat(pathToCleanup).catch(() => null)) await rm(pathToCleanup, {
|
|
4148
|
+
recursive: true,
|
|
4149
|
+
force: true
|
|
4150
|
+
});
|
|
4151
|
+
} catch (err) {
|
|
4152
|
+
M.warn(`Could not remove skill from ${agent.displayName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
4153
|
+
}
|
|
3507
4154
|
}
|
|
3508
4155
|
}
|
|
3509
|
-
await
|
|
4156
|
+
const remainingAgents = (await detectInstalledAgents()).filter((a) => !targetAgents.includes(a));
|
|
4157
|
+
let isStillUsed = false;
|
|
4158
|
+
for (const agentKey of remainingAgents) if (await lstat(getInstallPath(skillName, agentKey, {
|
|
3510
4159
|
global: isGlobal,
|
|
3511
4160
|
cwd
|
|
3512
|
-
})
|
|
4161
|
+
})).catch(() => null)) {
|
|
4162
|
+
isStillUsed = true;
|
|
4163
|
+
break;
|
|
4164
|
+
}
|
|
4165
|
+
if (!isStillUsed) await rm(canonicalPath, {
|
|
3513
4166
|
recursive: true,
|
|
3514
4167
|
force: true
|
|
3515
4168
|
});
|
|
@@ -3626,13 +4279,17 @@ function showBanner() {
|
|
|
3626
4279
|
console.log();
|
|
3627
4280
|
console.log(`${DIM}The open agent skills ecosystem${RESET}`);
|
|
3628
4281
|
console.log();
|
|
3629
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx skills add ${DIM}<package>${RESET}
|
|
3630
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx skills
|
|
3631
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx skills
|
|
3632
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx skills
|
|
3633
|
-
console.log(
|
|
3634
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx skills
|
|
3635
|
-
console.log(` ${DIM}$${RESET} ${TEXT}npx skills
|
|
4282
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills add ${DIM}<package>${RESET} ${DIM}Add a new skill${RESET}`);
|
|
4283
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills remove${RESET} ${DIM}Remove installed skills${RESET}`);
|
|
4284
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills list${RESET} ${DIM}List installed skills${RESET}`);
|
|
4285
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills find ${DIM}[query]${RESET} ${DIM}Search for skills${RESET}`);
|
|
4286
|
+
console.log();
|
|
4287
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills check${RESET} ${DIM}Check for updates${RESET}`);
|
|
4288
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills update${RESET} ${DIM}Update all skills${RESET}`);
|
|
4289
|
+
console.log();
|
|
4290
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills experimental_install${RESET} ${DIM}Restore from skills-lock.json${RESET}`);
|
|
4291
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills init ${DIM}[name]${RESET} ${DIM}Create a new skill${RESET}`);
|
|
4292
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills experimental_sync${RESET} ${DIM}Sync skills from node_modules${RESET}`);
|
|
3636
4293
|
console.log();
|
|
3637
4294
|
console.log(`${DIM}try:${RESET} npx skills add vercel-labs/agent-skills`);
|
|
3638
4295
|
console.log();
|
|
@@ -3643,16 +4300,22 @@ function showHelp() {
|
|
|
3643
4300
|
console.log(`
|
|
3644
4301
|
${BOLD}Usage:${RESET} skills <command> [options]
|
|
3645
4302
|
|
|
3646
|
-
${BOLD}
|
|
3647
|
-
add <package>
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
remove [skills]
|
|
3651
|
-
list, ls
|
|
3652
|
-
find [query]
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
4303
|
+
${BOLD}Manage Skills:${RESET}
|
|
4304
|
+
add <package> Add a skill package (alias: a)
|
|
4305
|
+
e.g. vercel-labs/agent-skills
|
|
4306
|
+
https://github.com/vercel-labs/agent-skills
|
|
4307
|
+
remove [skills] Remove installed skills
|
|
4308
|
+
list, ls List installed skills
|
|
4309
|
+
find [query] Search for skills interactively
|
|
4310
|
+
|
|
4311
|
+
${BOLD}Updates:${RESET}
|
|
4312
|
+
check Check for available skill updates
|
|
4313
|
+
update Update all skills to latest versions
|
|
4314
|
+
|
|
4315
|
+
${BOLD}Project:${RESET}
|
|
4316
|
+
experimental_install Restore skills from skills-lock.json
|
|
4317
|
+
init [name] Initialize a skill (creates <name>/SKILL.md or ./SKILL.md)
|
|
4318
|
+
experimental_sync Sync skills from node_modules into agent directories
|
|
3656
4319
|
|
|
3657
4320
|
${BOLD}Add Options:${RESET}
|
|
3658
4321
|
-g, --global Install skill globally (user-level) instead of project-level
|
|
@@ -3660,6 +4323,7 @@ ${BOLD}Add Options:${RESET}
|
|
|
3660
4323
|
-s, --skill <skills> Specify skill names to install (use '*' for all skills)
|
|
3661
4324
|
-l, --list List available skills in the repository without installing
|
|
3662
4325
|
-y, --yes Skip confirmation prompts
|
|
4326
|
+
--copy Copy files instead of symlinking to agent directories
|
|
3663
4327
|
--all Shorthand for --skill '*' --agent '*' -y
|
|
3664
4328
|
--full-depth Search all subdirectories even when a root SKILL.md exists
|
|
3665
4329
|
|
|
@@ -3670,6 +4334,10 @@ ${BOLD}Remove Options:${RESET}
|
|
|
3670
4334
|
-y, --yes Skip confirmation prompts
|
|
3671
4335
|
--all Shorthand for --skill '*' --agent '*' -y
|
|
3672
4336
|
|
|
4337
|
+
${BOLD}Experimental Sync Options:${RESET}
|
|
4338
|
+
-a, --agent <agents> Specify agents to install to (use '*' for all agents)
|
|
4339
|
+
-y, --yes Skip confirmation prompts
|
|
4340
|
+
|
|
3673
4341
|
${BOLD}List Options:${RESET}
|
|
3674
4342
|
-g, --global List global skills (default: project)
|
|
3675
4343
|
-a, --agent <agents> Filter by specific agents
|
|
@@ -3683,17 +4351,20 @@ ${BOLD}Examples:${RESET}
|
|
|
3683
4351
|
${DIM}$${RESET} skills add vercel-labs/agent-skills -g
|
|
3684
4352
|
${DIM}$${RESET} skills add vercel-labs/agent-skills --agent claude-code cursor
|
|
3685
4353
|
${DIM}$${RESET} skills add vercel-labs/agent-skills --skill pr-review commit
|
|
3686
|
-
${DIM}$${RESET} skills remove
|
|
3687
|
-
${DIM}$${RESET} skills remove web-design
|
|
4354
|
+
${DIM}$${RESET} skills remove ${DIM}# interactive remove${RESET}
|
|
4355
|
+
${DIM}$${RESET} skills remove web-design ${DIM}# remove by name${RESET}
|
|
3688
4356
|
${DIM}$${RESET} skills rm --global frontend-design
|
|
3689
|
-
${DIM}$${RESET} skills list
|
|
3690
|
-
${DIM}$${RESET} skills ls -g
|
|
3691
|
-
${DIM}$${RESET} skills ls -a claude-code
|
|
3692
|
-
${DIM}$${RESET} skills find
|
|
3693
|
-
${DIM}$${RESET} skills find typescript
|
|
3694
|
-
${DIM}$${RESET} skills init my-skill
|
|
4357
|
+
${DIM}$${RESET} skills list ${DIM}# list project skills${RESET}
|
|
4358
|
+
${DIM}$${RESET} skills ls -g ${DIM}# list global skills${RESET}
|
|
4359
|
+
${DIM}$${RESET} skills ls -a claude-code ${DIM}# filter by agent${RESET}
|
|
4360
|
+
${DIM}$${RESET} skills find ${DIM}# interactive search${RESET}
|
|
4361
|
+
${DIM}$${RESET} skills find typescript ${DIM}# search by keyword${RESET}
|
|
3695
4362
|
${DIM}$${RESET} skills check
|
|
3696
4363
|
${DIM}$${RESET} skills update
|
|
4364
|
+
${DIM}$${RESET} skills experimental_install ${DIM}# restore from skills-lock.json${RESET}
|
|
4365
|
+
${DIM}$${RESET} skills init my-skill
|
|
4366
|
+
${DIM}$${RESET} skills experimental_sync ${DIM}# sync from node_modules${RESET}
|
|
4367
|
+
${DIM}$${RESET} skills experimental_sync -y ${DIM}# sync without prompts${RESET}
|
|
3697
4368
|
|
|
3698
4369
|
Discover more skills at ${TEXT}https://skills.sh/${RESET}
|
|
3699
4370
|
`);
|
|
@@ -3984,13 +4655,17 @@ async function main() {
|
|
|
3984
4655
|
console.log();
|
|
3985
4656
|
runInit(restArgs);
|
|
3986
4657
|
break;
|
|
4658
|
+
case "experimental_install":
|
|
4659
|
+
showLogo();
|
|
4660
|
+
await runInstallFromLock(restArgs);
|
|
4661
|
+
break;
|
|
3987
4662
|
case "i":
|
|
3988
4663
|
case "install":
|
|
3989
4664
|
case "a":
|
|
3990
4665
|
case "add": {
|
|
3991
4666
|
showLogo();
|
|
3992
|
-
const { source, options } = parseAddOptions(restArgs);
|
|
3993
|
-
await runAdd(
|
|
4667
|
+
const { source: addSource, options: addOpts } = parseAddOptions(restArgs);
|
|
4668
|
+
await runAdd(addSource, addOpts);
|
|
3994
4669
|
break;
|
|
3995
4670
|
}
|
|
3996
4671
|
case "remove":
|
|
@@ -4003,6 +4678,12 @@ async function main() {
|
|
|
4003
4678
|
const { skills, options: removeOptions } = parseRemoveOptions(restArgs);
|
|
4004
4679
|
await removeCommand(skills, removeOptions);
|
|
4005
4680
|
break;
|
|
4681
|
+
case "experimental_sync": {
|
|
4682
|
+
showLogo();
|
|
4683
|
+
const { options: syncOptions } = parseSyncOptions(restArgs);
|
|
4684
|
+
await runSync(restArgs, syncOptions);
|
|
4685
|
+
break;
|
|
4686
|
+
}
|
|
4006
4687
|
case "list":
|
|
4007
4688
|
case "ls":
|
|
4008
4689
|
await runList(restArgs);
|