skills 1.4.0 → 1.4.1
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 +28 -24
- package/dist/cli.mjs +607 -89
- package/package.json +4 -2
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 [37 more](#available-agents).
|
|
7
7
|
<!-- agent-list:end -->
|
|
8
8
|
|
|
9
9
|
## Install a Skill
|
|
@@ -42,6 +42,7 @@ npx skills add ./my-local-skills
|
|
|
42
42
|
| `-a, --agent <agents...>` | <!-- agent-names:start -->Target specific agents (e.g., `claude-code`, `codex`). See [Available Agents](#available-agents)<!-- agent-names:end --> |
|
|
43
43
|
| `-s, --skill <skills...>` | Install specific skills by name (use `'*'` for all skills) |
|
|
44
44
|
| `-l, --list` | List available skills without installing |
|
|
45
|
+
| `--copy` | Copy files instead of symlinking to agent directories |
|
|
45
46
|
| `-y, --yes` | Skip all confirmation prompts |
|
|
46
47
|
| `--all` | Install all skills to all agents without prompts |
|
|
47
48
|
|
|
@@ -91,14 +92,14 @@ When installing interactively, you can choose:
|
|
|
91
92
|
|
|
92
93
|
## Other Commands
|
|
93
94
|
|
|
94
|
-
| Command | Description
|
|
95
|
-
| ---------------------------- |
|
|
96
|
-
| `npx skills list` | List installed skills (alias: `ls`)
|
|
97
|
-
| `npx skills find [query]` | Search for skills interactively or by keyword
|
|
98
|
-
| `npx skills remove [skills]` | Remove installed skills from agents
|
|
99
|
-
| `npx skills check` | Check for available skill updates
|
|
100
|
-
| `npx skills update` | Update all installed skills to latest versions
|
|
101
|
-
| `npx skills init [name]` | Create a new SKILL.md template
|
|
95
|
+
| Command | Description |
|
|
96
|
+
| ---------------------------- | ---------------------------------------------- |
|
|
97
|
+
| `npx skills list` | List installed skills (alias: `ls`) |
|
|
98
|
+
| `npx skills find [query]` | Search for skills interactively or by keyword |
|
|
99
|
+
| `npx skills remove [skills]` | Remove installed skills from agents |
|
|
100
|
+
| `npx skills check` | Check for available skill updates |
|
|
101
|
+
| `npx skills update` | Update all installed skills to latest versions |
|
|
102
|
+
| `npx skills init [name]` | Create a new SKILL.md template |
|
|
102
103
|
|
|
103
104
|
### `skills list`
|
|
104
105
|
|
|
@@ -180,13 +181,13 @@ npx skills remove my-skill --agent '*'
|
|
|
180
181
|
npx skills rm my-skill
|
|
181
182
|
```
|
|
182
183
|
|
|
183
|
-
| Option
|
|
184
|
-
|
|
|
185
|
-
| `-g, --global`
|
|
186
|
-
| `-a, --agent`
|
|
187
|
-
| `-s, --skill`
|
|
188
|
-
| `-y, --yes`
|
|
189
|
-
| `--all`
|
|
184
|
+
| Option | Description |
|
|
185
|
+
| -------------- | ------------------------------------------------ |
|
|
186
|
+
| `-g, --global` | Remove from global scope (~/) instead of project |
|
|
187
|
+
| `-a, --agent` | Remove from specific agents (use `'*'` for all) |
|
|
188
|
+
| `-s, --skill` | Specify skills to remove (use `'*'` for all) |
|
|
189
|
+
| `-y, --yes` | Skip confirmation prompts |
|
|
190
|
+
| `--all` | Shorthand for `--skill '*' --agent '*' -y` |
|
|
190
191
|
|
|
191
192
|
## What are Agent Skills?
|
|
192
193
|
|
|
@@ -208,7 +209,7 @@ Skills can be installed to any of these agents:
|
|
|
208
209
|
<!-- supported-agents:start -->
|
|
209
210
|
| Agent | `--agent` | Project Path | Global Path |
|
|
210
211
|
|-------|-----------|--------------|-------------|
|
|
211
|
-
| Amp, Kimi Code CLI, Replit | `amp`, `kimi-cli`, `replit` | `.agents/skills/` | `~/.config/agents/skills/` |
|
|
212
|
+
| Amp, Kimi Code CLI, Replit, Universal | `amp`, `kimi-cli`, `replit`, `universal` | `.agents/skills/` | `~/.config/agents/skills/` |
|
|
212
213
|
| Antigravity | `antigravity` | `.agent/skills/` | `~/.gemini/antigravity/skills/` |
|
|
213
214
|
| Augment | `augment` | `.augment/skills/` | `~/.augment/skills/` |
|
|
214
215
|
| Claude Code | `claude-code` | `.claude/skills/` | `~/.claude/skills/` |
|
|
@@ -218,8 +219,9 @@ Skills can be installed to any of these agents:
|
|
|
218
219
|
| Codex | `codex` | `.agents/skills/` | `~/.codex/skills/` |
|
|
219
220
|
| Command Code | `command-code` | `.commandcode/skills/` | `~/.commandcode/skills/` |
|
|
220
221
|
| Continue | `continue` | `.continue/skills/` | `~/.continue/skills/` |
|
|
222
|
+
| Cortex Code | `cortex` | `.cortex/skills/` | `~/.snowflake/cortex/skills/` |
|
|
221
223
|
| Crush | `crush` | `.crush/skills/` | `~/.config/crush/skills/` |
|
|
222
|
-
| Cursor | `cursor` | `.
|
|
224
|
+
| Cursor | `cursor` | `.agents/skills/` | `~/.cursor/skills/` |
|
|
223
225
|
| Droid | `droid` | `.factory/skills/` | `~/.factory/skills/` |
|
|
224
226
|
| Gemini CLI | `gemini-cli` | `.agents/skills/` | `~/.gemini/skills/` |
|
|
225
227
|
| GitHub Copilot | `github-copilot` | `.agents/skills/` | `~/.copilot/skills/` |
|
|
@@ -323,8 +325,8 @@ The CLI searches for skills in these locations within a repository:
|
|
|
323
325
|
- `.codebuddy/skills/`
|
|
324
326
|
- `.commandcode/skills/`
|
|
325
327
|
- `.continue/skills/`
|
|
328
|
+
- `.cortex/skills/`
|
|
326
329
|
- `.crush/skills/`
|
|
327
|
-
- `.cursor/skills/`
|
|
328
330
|
- `.factory/skills/`
|
|
329
331
|
- `.goose/skills/`
|
|
330
332
|
- `.junie/skills/`
|
|
@@ -356,11 +358,13 @@ If `.claude-plugin/marketplace.json` or `.claude-plugin/plugin.json` exists, ski
|
|
|
356
358
|
// .claude-plugin/marketplace.json
|
|
357
359
|
{
|
|
358
360
|
"metadata": { "pluginRoot": "./plugins" },
|
|
359
|
-
"plugins": [
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
361
|
+
"plugins": [
|
|
362
|
+
{
|
|
363
|
+
"name": "my-plugin",
|
|
364
|
+
"source": "my-plugin",
|
|
365
|
+
"skills": ["./skills/review", "./skills/test"]
|
|
366
|
+
}
|
|
367
|
+
]
|
|
364
368
|
}
|
|
365
369
|
```
|
|
366
370
|
|
package/dist/cli.mjs
CHANGED
|
@@ -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",
|
|
@@ -489,7 +497,6 @@ async function discoverSkills(basePath, subpath, options) {
|
|
|
489
497
|
join(searchPath, ".codex/skills"),
|
|
490
498
|
join(searchPath, ".commandcode/skills"),
|
|
491
499
|
join(searchPath, ".continue/skills"),
|
|
492
|
-
join(searchPath, ".cursor/skills"),
|
|
493
500
|
join(searchPath, ".github/skills"),
|
|
494
501
|
join(searchPath, ".goose/skills"),
|
|
495
502
|
join(searchPath, ".iflow/skills"),
|
|
@@ -570,7 +577,7 @@ const agents = {
|
|
|
570
577
|
skillsDir: ".agent/skills",
|
|
571
578
|
globalSkillsDir: join(home, ".gemini/antigravity/skills"),
|
|
572
579
|
detectInstalled: async () => {
|
|
573
|
-
return existsSync(join(
|
|
580
|
+
return existsSync(join(home, ".gemini/antigravity"));
|
|
574
581
|
}
|
|
575
582
|
},
|
|
576
583
|
augment: {
|
|
@@ -645,6 +652,15 @@ const agents = {
|
|
|
645
652
|
return existsSync(join(process.cwd(), ".continue")) || existsSync(join(home, ".continue"));
|
|
646
653
|
}
|
|
647
654
|
},
|
|
655
|
+
cortex: {
|
|
656
|
+
name: "cortex",
|
|
657
|
+
displayName: "Cortex Code",
|
|
658
|
+
skillsDir: ".cortex/skills",
|
|
659
|
+
globalSkillsDir: join(home, ".snowflake/cortex/skills"),
|
|
660
|
+
detectInstalled: async () => {
|
|
661
|
+
return existsSync(join(home, ".snowflake/cortex"));
|
|
662
|
+
}
|
|
663
|
+
},
|
|
648
664
|
crush: {
|
|
649
665
|
name: "crush",
|
|
650
666
|
displayName: "Crush",
|
|
@@ -657,7 +673,7 @@ const agents = {
|
|
|
657
673
|
cursor: {
|
|
658
674
|
name: "cursor",
|
|
659
675
|
displayName: "Cursor",
|
|
660
|
-
skillsDir: ".
|
|
676
|
+
skillsDir: ".agents/skills",
|
|
661
677
|
globalSkillsDir: join(home, ".cursor/skills"),
|
|
662
678
|
detectInstalled: async () => {
|
|
663
679
|
return existsSync(join(home, ".cursor"));
|
|
@@ -687,7 +703,7 @@ const agents = {
|
|
|
687
703
|
skillsDir: ".agents/skills",
|
|
688
704
|
globalSkillsDir: join(home, ".copilot/skills"),
|
|
689
705
|
detectInstalled: async () => {
|
|
690
|
-
return existsSync(join(
|
|
706
|
+
return existsSync(join(home, ".copilot"));
|
|
691
707
|
}
|
|
692
708
|
},
|
|
693
709
|
goose: {
|
|
@@ -786,7 +802,7 @@ const agents = {
|
|
|
786
802
|
skillsDir: ".agents/skills",
|
|
787
803
|
globalSkillsDir: join(configHome, "opencode/skills"),
|
|
788
804
|
detectInstalled: async () => {
|
|
789
|
-
return existsSync(join(configHome, "opencode"))
|
|
805
|
+
return existsSync(join(configHome, "opencode"));
|
|
790
806
|
}
|
|
791
807
|
},
|
|
792
808
|
openhands: {
|
|
@@ -832,7 +848,7 @@ const agents = {
|
|
|
832
848
|
globalSkillsDir: join(configHome, "agents/skills"),
|
|
833
849
|
showInUniversalList: false,
|
|
834
850
|
detectInstalled: async () => {
|
|
835
|
-
return existsSync(join(process.cwd(), ".
|
|
851
|
+
return existsSync(join(process.cwd(), ".replit"));
|
|
836
852
|
}
|
|
837
853
|
},
|
|
838
854
|
roo: {
|
|
@@ -906,6 +922,14 @@ const agents = {
|
|
|
906
922
|
detectInstalled: async () => {
|
|
907
923
|
return existsSync(join(home, ".adal"));
|
|
908
924
|
}
|
|
925
|
+
},
|
|
926
|
+
universal: {
|
|
927
|
+
name: "universal",
|
|
928
|
+
displayName: "Universal",
|
|
929
|
+
skillsDir: ".agents/skills",
|
|
930
|
+
globalSkillsDir: join(configHome, "agents/skills"),
|
|
931
|
+
showInUniversalList: false,
|
|
932
|
+
detectInstalled: async () => false
|
|
909
933
|
}
|
|
910
934
|
};
|
|
911
935
|
async function detectInstalledAgents() {
|
|
@@ -936,6 +960,16 @@ function isPathSafe(basePath, targetPath) {
|
|
|
936
960
|
function getCanonicalSkillsDir(global, cwd) {
|
|
937
961
|
return join(global ? homedir() : cwd || process.cwd(), AGENTS_DIR$2, SKILLS_SUBDIR);
|
|
938
962
|
}
|
|
963
|
+
function getAgentBaseDir(agentType, global, cwd) {
|
|
964
|
+
if (isUniversalAgent(agentType)) return getCanonicalSkillsDir(global, cwd);
|
|
965
|
+
const agent = agents[agentType];
|
|
966
|
+
const baseDir = global ? homedir() : cwd || process.cwd();
|
|
967
|
+
if (global) {
|
|
968
|
+
if (agent.globalSkillsDir === void 0) return join(baseDir, agent.skillsDir);
|
|
969
|
+
return agent.globalSkillsDir;
|
|
970
|
+
}
|
|
971
|
+
return join(baseDir, agent.skillsDir);
|
|
972
|
+
}
|
|
939
973
|
function resolveSymlinkTarget(linkPath, linkTarget) {
|
|
940
974
|
return resolve(dirname(linkPath), linkTarget);
|
|
941
975
|
}
|
|
@@ -961,7 +995,9 @@ async function resolveParentSymlinks(path) {
|
|
|
961
995
|
async function createSymlink(target, linkPath) {
|
|
962
996
|
try {
|
|
963
997
|
const resolvedTarget = resolve(target);
|
|
964
|
-
|
|
998
|
+
const resolvedLinkPath = resolve(linkPath);
|
|
999
|
+
const [realTarget, realLinkPath] = await Promise.all([realpath(resolvedTarget).catch(() => resolvedTarget), realpath(resolvedLinkPath).catch(() => resolvedLinkPath)]);
|
|
1000
|
+
if (realTarget === realLinkPath) return true;
|
|
965
1001
|
if (await resolveParentSymlinks(target) === await resolveParentSymlinks(linkPath)) return true;
|
|
966
1002
|
try {
|
|
967
1003
|
if ((await lstat(linkPath)).isSymbolicLink()) {
|
|
@@ -994,7 +1030,7 @@ async function installSkillForAgent(skill, agentType, options = {}) {
|
|
|
994
1030
|
const skillName = sanitizeName(skill.name || basename(skill.path));
|
|
995
1031
|
const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
|
|
996
1032
|
const canonicalDir = join(canonicalBase, skillName);
|
|
997
|
-
const agentBase = isGlobal
|
|
1033
|
+
const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
|
|
998
1034
|
const agentDir = join(agentBase, skillName);
|
|
999
1035
|
const installMode = options.mode ?? "symlink";
|
|
1000
1036
|
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
@@ -1053,7 +1089,7 @@ async function installSkillForAgent(skill, agentType, options = {}) {
|
|
|
1053
1089
|
};
|
|
1054
1090
|
}
|
|
1055
1091
|
}
|
|
1056
|
-
const EXCLUDE_FILES = new Set(["
|
|
1092
|
+
const EXCLUDE_FILES = new Set(["metadata.json"]);
|
|
1057
1093
|
const EXCLUDE_DIRS = new Set([".git"]);
|
|
1058
1094
|
const isExcluded = (name, isDirectory = false) => {
|
|
1059
1095
|
if (EXCLUDE_FILES.has(name)) return true;
|
|
@@ -1089,10 +1125,10 @@ async function isSkillInstalled(skillName, agentType, options = {}) {
|
|
|
1089
1125
|
}
|
|
1090
1126
|
}
|
|
1091
1127
|
function getInstallPath(skillName, agentType, options = {}) {
|
|
1092
|
-
|
|
1093
|
-
|
|
1128
|
+
agents[agentType];
|
|
1129
|
+
options.cwd || process.cwd();
|
|
1094
1130
|
const sanitized = sanitizeName(skillName);
|
|
1095
|
-
const targetBase = options.global
|
|
1131
|
+
const targetBase = getAgentBaseDir(agentType, options.global ?? false, options.cwd);
|
|
1096
1132
|
const installPath = join(targetBase, sanitized);
|
|
1097
1133
|
if (!isPathSafe(targetBase, installPath)) throw new Error("Invalid skill name: potential path traversal detected");
|
|
1098
1134
|
return installPath;
|
|
@@ -1118,7 +1154,7 @@ async function installRemoteSkillForAgent(skill, agentType, options = {}) {
|
|
|
1118
1154
|
const skillName = sanitizeName(skill.installName);
|
|
1119
1155
|
const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
|
|
1120
1156
|
const canonicalDir = join(canonicalBase, skillName);
|
|
1121
|
-
const agentBase = isGlobal
|
|
1157
|
+
const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
|
|
1122
1158
|
const agentDir = join(agentBase, skillName);
|
|
1123
1159
|
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
1124
1160
|
success: false,
|
|
@@ -1190,7 +1226,7 @@ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
|
|
|
1190
1226
|
const skillName = sanitizeName(skill.installName);
|
|
1191
1227
|
const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
|
|
1192
1228
|
const canonicalDir = join(canonicalBase, skillName);
|
|
1193
|
-
const agentBase = isGlobal
|
|
1229
|
+
const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
|
|
1194
1230
|
const agentDir = join(agentBase, skillName);
|
|
1195
1231
|
if (!isPathSafe(canonicalBase, canonicalDir)) return {
|
|
1196
1232
|
success: false,
|
|
@@ -1718,7 +1754,7 @@ async function fetchMintlifySkill(url) {
|
|
|
1718
1754
|
}
|
|
1719
1755
|
const AGENTS_DIR$1 = ".agents";
|
|
1720
1756
|
const LOCK_FILE$1 = ".skill-lock.json";
|
|
1721
|
-
const CURRENT_VERSION = 3;
|
|
1757
|
+
const CURRENT_VERSION$1 = 3;
|
|
1722
1758
|
function getSkillLockPath$1() {
|
|
1723
1759
|
return join(homedir(), AGENTS_DIR$1, LOCK_FILE$1);
|
|
1724
1760
|
}
|
|
@@ -1728,7 +1764,7 @@ async function readSkillLock$1() {
|
|
|
1728
1764
|
const content = await readFile(lockPath, "utf-8");
|
|
1729
1765
|
const parsed = JSON.parse(content);
|
|
1730
1766
|
if (typeof parsed.version !== "number" || !parsed.skills) return createEmptyLockFile();
|
|
1731
|
-
if (parsed.version < CURRENT_VERSION) return createEmptyLockFile();
|
|
1767
|
+
if (parsed.version < CURRENT_VERSION$1) return createEmptyLockFile();
|
|
1732
1768
|
return parsed;
|
|
1733
1769
|
} catch (error) {
|
|
1734
1770
|
return createEmptyLockFile();
|
|
@@ -1801,7 +1837,7 @@ async function getSkillFromLock(skillName) {
|
|
|
1801
1837
|
}
|
|
1802
1838
|
function createEmptyLockFile() {
|
|
1803
1839
|
return {
|
|
1804
|
-
version: CURRENT_VERSION,
|
|
1840
|
+
version: CURRENT_VERSION$1,
|
|
1805
1841
|
skills: {},
|
|
1806
1842
|
dismissed: {}
|
|
1807
1843
|
};
|
|
@@ -1823,8 +1859,74 @@ async function saveSelectedAgents(agents) {
|
|
|
1823
1859
|
lock.lastSelectedAgents = agents;
|
|
1824
1860
|
await writeSkillLock(lock);
|
|
1825
1861
|
}
|
|
1826
|
-
|
|
1827
|
-
const
|
|
1862
|
+
const LOCAL_LOCK_FILE = "skills-lock.json";
|
|
1863
|
+
const CURRENT_VERSION = 1;
|
|
1864
|
+
function getLocalLockPath(cwd) {
|
|
1865
|
+
return join(cwd || process.cwd(), LOCAL_LOCK_FILE);
|
|
1866
|
+
}
|
|
1867
|
+
async function readLocalLock(cwd) {
|
|
1868
|
+
const lockPath = getLocalLockPath(cwd);
|
|
1869
|
+
try {
|
|
1870
|
+
const content = await readFile(lockPath, "utf-8");
|
|
1871
|
+
const parsed = JSON.parse(content);
|
|
1872
|
+
if (typeof parsed.version !== "number" || !parsed.skills) return createEmptyLocalLock();
|
|
1873
|
+
if (parsed.version < CURRENT_VERSION) return createEmptyLocalLock();
|
|
1874
|
+
return parsed;
|
|
1875
|
+
} catch {
|
|
1876
|
+
return createEmptyLocalLock();
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
async function writeLocalLock(lock, cwd) {
|
|
1880
|
+
const lockPath = getLocalLockPath(cwd);
|
|
1881
|
+
const sortedSkills = {};
|
|
1882
|
+
for (const key of Object.keys(lock.skills).sort()) sortedSkills[key] = lock.skills[key];
|
|
1883
|
+
const sorted = {
|
|
1884
|
+
version: lock.version,
|
|
1885
|
+
skills: sortedSkills
|
|
1886
|
+
};
|
|
1887
|
+
await writeFile(lockPath, JSON.stringify(sorted, null, 2) + "\n", "utf-8");
|
|
1888
|
+
}
|
|
1889
|
+
async function computeSkillFolderHash(skillDir) {
|
|
1890
|
+
const files = [];
|
|
1891
|
+
await collectFiles(skillDir, skillDir, files);
|
|
1892
|
+
files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
1893
|
+
const hash = createHash("sha256");
|
|
1894
|
+
for (const file of files) {
|
|
1895
|
+
hash.update(file.relativePath);
|
|
1896
|
+
hash.update(file.content);
|
|
1897
|
+
}
|
|
1898
|
+
return hash.digest("hex");
|
|
1899
|
+
}
|
|
1900
|
+
async function collectFiles(baseDir, currentDir, results) {
|
|
1901
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
1902
|
+
await Promise.all(entries.map(async (entry) => {
|
|
1903
|
+
const fullPath = join(currentDir, entry.name);
|
|
1904
|
+
if (entry.isDirectory()) {
|
|
1905
|
+
if (entry.name === ".git" || entry.name === "node_modules") return;
|
|
1906
|
+
await collectFiles(baseDir, fullPath, results);
|
|
1907
|
+
} else if (entry.isFile()) {
|
|
1908
|
+
const content = await readFile(fullPath);
|
|
1909
|
+
const relativePath = relative(baseDir, fullPath).split("\\").join("/");
|
|
1910
|
+
results.push({
|
|
1911
|
+
relativePath,
|
|
1912
|
+
content
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
}));
|
|
1916
|
+
}
|
|
1917
|
+
async function addSkillToLocalLock(skillName, entry, cwd) {
|
|
1918
|
+
const lock = await readLocalLock(cwd);
|
|
1919
|
+
lock.skills[skillName] = entry;
|
|
1920
|
+
await writeLocalLock(lock, cwd);
|
|
1921
|
+
}
|
|
1922
|
+
function createEmptyLocalLock() {
|
|
1923
|
+
return {
|
|
1924
|
+
version: CURRENT_VERSION,
|
|
1925
|
+
skills: {}
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
var version$1 = "1.4.1";
|
|
1929
|
+
const isCancelled$1 = (value) => typeof value === "symbol";
|
|
1828
1930
|
async function isSourcePrivate(source) {
|
|
1829
1931
|
const ownerRepo = parseOwnerRepo(source);
|
|
1830
1932
|
if (!ownerRepo) return false;
|
|
@@ -1875,7 +1977,7 @@ function buildSecurityLines(auditData, skills, source) {
|
|
|
1875
1977
|
lines.push(`${import_picocolors.default.dim("Details:")} ${import_picocolors.default.dim(`https://skills.sh/${source}`)}`);
|
|
1876
1978
|
return lines;
|
|
1877
1979
|
}
|
|
1878
|
-
function shortenPath$
|
|
1980
|
+
function shortenPath$2(fullPath, cwd) {
|
|
1879
1981
|
const home = homedir();
|
|
1880
1982
|
if (fullPath === home || fullPath.startsWith(home + sep)) return "~" + fullPath.slice(home.length);
|
|
1881
1983
|
if (fullPath === cwd || fullPath.startsWith(cwd + sep)) return "." + fullPath.slice(cwd.length);
|
|
@@ -1952,7 +2054,7 @@ async function promptForAgents(message, choices) {
|
|
|
1952
2054
|
initialSelected: initialValues,
|
|
1953
2055
|
required: true
|
|
1954
2056
|
});
|
|
1955
|
-
if (!isCancelled(selected)) try {
|
|
2057
|
+
if (!isCancelled$1(selected)) try {
|
|
1956
2058
|
await saveSelectedAgents(selected);
|
|
1957
2059
|
} catch {}
|
|
1958
2060
|
return selected;
|
|
@@ -1983,7 +2085,7 @@ async function selectAgentsInteractive(options) {
|
|
|
1983
2085
|
initialSelected: lastSelected ? lastSelected.filter((a) => otherAgents.includes(a) && !universalAgents.includes(a)) : [],
|
|
1984
2086
|
lockedSection: universalSection
|
|
1985
2087
|
});
|
|
1986
|
-
if (!isCancelled(selected)) try {
|
|
2088
|
+
if (!isCancelled$1(selected)) try {
|
|
1987
2089
|
await saveSelectedAgents(selected);
|
|
1988
2090
|
} catch {}
|
|
1989
2091
|
return selected;
|
|
@@ -2040,7 +2142,7 @@ async function handleRemoteSkill(source, url, options, spinner) {
|
|
|
2040
2142
|
M.info(`Valid agents: ${validAgents.join(", ")}`);
|
|
2041
2143
|
process.exit(1);
|
|
2042
2144
|
}
|
|
2043
|
-
targetAgents =
|
|
2145
|
+
targetAgents = options.agent;
|
|
2044
2146
|
} else {
|
|
2045
2147
|
spinner.start("Loading agents...");
|
|
2046
2148
|
const installedAgents = await detectInstalledAgents();
|
|
@@ -2092,8 +2194,8 @@ async function handleRemoteSkill(source, url, options, spinner) {
|
|
|
2092
2194
|
}
|
|
2093
2195
|
installGlobally = scope;
|
|
2094
2196
|
}
|
|
2095
|
-
let installMode = "symlink";
|
|
2096
|
-
if (!options.yes) {
|
|
2197
|
+
let installMode = options.copy ? "copy" : "symlink";
|
|
2198
|
+
if (!options.copy && !options.yes) {
|
|
2097
2199
|
const modeChoice = await ve({
|
|
2098
2200
|
message: "Installation method",
|
|
2099
2201
|
options: [{
|
|
@@ -2119,7 +2221,7 @@ async function handleRemoteSkill(source, url, options, spinner) {
|
|
|
2119
2221
|
})));
|
|
2120
2222
|
const overwriteStatus = new Map(overwriteChecks.map(({ agent, installed }) => [agent, installed]));
|
|
2121
2223
|
const summaryLines = [];
|
|
2122
|
-
const shortCanonical = shortenPath$
|
|
2224
|
+
const shortCanonical = shortenPath$2(getCanonicalPath(remoteSkill.installName, { global: installGlobally }), cwd);
|
|
2123
2225
|
summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
|
|
2124
2226
|
summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
|
|
2125
2227
|
const overwriteAgents = targetAgents.filter((a) => overwriteStatus.get(a)).map((a) => agents[a].displayName);
|
|
@@ -2172,18 +2274,27 @@ async function handleRemoteSkill(source, url, options, spinner) {
|
|
|
2172
2274
|
skillFolderHash
|
|
2173
2275
|
});
|
|
2174
2276
|
} catch {}
|
|
2277
|
+
if (successful.length > 0 && !installGlobally) try {
|
|
2278
|
+
const firstResult = successful[0];
|
|
2279
|
+
const computedHash = await computeSkillFolderHash(firstResult.canonicalPath || firstResult.path);
|
|
2280
|
+
await addSkillToLocalLock(remoteSkill.installName, {
|
|
2281
|
+
source: remoteSkill.sourceIdentifier,
|
|
2282
|
+
sourceType: remoteSkill.providerId,
|
|
2283
|
+
computedHash
|
|
2284
|
+
}, cwd);
|
|
2285
|
+
} catch {}
|
|
2175
2286
|
if (successful.length > 0) {
|
|
2176
2287
|
const resultLines = [];
|
|
2177
2288
|
const firstResult = successful[0];
|
|
2178
2289
|
if (firstResult.mode === "copy") {
|
|
2179
2290
|
resultLines.push(`${import_picocolors.default.green("✓")} ${remoteSkill.installName} ${import_picocolors.default.dim("(copied)")}`);
|
|
2180
2291
|
for (const r of successful) {
|
|
2181
|
-
const shortPath = shortenPath$
|
|
2292
|
+
const shortPath = shortenPath$2(r.path, cwd);
|
|
2182
2293
|
resultLines.push(` ${import_picocolors.default.dim("→")} ${shortPath}`);
|
|
2183
2294
|
}
|
|
2184
2295
|
} else {
|
|
2185
2296
|
if (firstResult.canonicalPath) {
|
|
2186
|
-
const shortPath = shortenPath$
|
|
2297
|
+
const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
|
|
2187
2298
|
resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
|
|
2188
2299
|
} else resultLines.push(`${import_picocolors.default.green("✓")} ${remoteSkill.installName}`);
|
|
2189
2300
|
resultLines.push(...buildResultLines(successful, targetAgents));
|
|
@@ -2302,7 +2413,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2302
2413
|
targetAgents = selected;
|
|
2303
2414
|
}
|
|
2304
2415
|
else if (installedAgents.length === 1 || options.yes) {
|
|
2305
|
-
targetAgents = installedAgents;
|
|
2416
|
+
targetAgents = ensureUniversalAgents(installedAgents);
|
|
2306
2417
|
if (installedAgents.length === 1) {
|
|
2307
2418
|
const firstAgent = installedAgents[0];
|
|
2308
2419
|
M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
|
|
@@ -2337,8 +2448,8 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2337
2448
|
}
|
|
2338
2449
|
installGlobally = scope;
|
|
2339
2450
|
}
|
|
2340
|
-
let installMode = "symlink";
|
|
2341
|
-
if (!options.yes) {
|
|
2451
|
+
let installMode = options.copy ? "copy" : "symlink";
|
|
2452
|
+
if (!options.copy && !options.yes) {
|
|
2342
2453
|
const modeChoice = await ve({
|
|
2343
2454
|
message: "Installation method",
|
|
2344
2455
|
options: [{
|
|
@@ -2372,7 +2483,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2372
2483
|
}
|
|
2373
2484
|
for (const skill of selectedSkills) {
|
|
2374
2485
|
if (summaryLines.length > 0) summaryLines.push("");
|
|
2375
|
-
const shortCanonical = shortenPath$
|
|
2486
|
+
const shortCanonical = shortenPath$2(getCanonicalPath(skill.installName, { global: installGlobally }), cwd);
|
|
2376
2487
|
summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
|
|
2377
2488
|
summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
|
|
2378
2489
|
if (skill.files.size > 1) summaryLines.push(` ${import_picocolors.default.dim("files:")} ${skill.files.size}`);
|
|
@@ -2429,6 +2540,21 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2429
2540
|
});
|
|
2430
2541
|
} catch {}
|
|
2431
2542
|
}
|
|
2543
|
+
if (successful.length > 0 && !installGlobally) {
|
|
2544
|
+
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
2545
|
+
for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
|
|
2546
|
+
const matchingResult = successful.find((r) => r.skill === skill.installName);
|
|
2547
|
+
const installDir = matchingResult?.canonicalPath || matchingResult?.path;
|
|
2548
|
+
if (installDir) {
|
|
2549
|
+
const computedHash = await computeSkillFolderHash(installDir);
|
|
2550
|
+
await addSkillToLocalLock(skill.installName, {
|
|
2551
|
+
source: sourceIdentifier,
|
|
2552
|
+
sourceType: "well-known",
|
|
2553
|
+
computedHash
|
|
2554
|
+
}, cwd);
|
|
2555
|
+
}
|
|
2556
|
+
} catch {}
|
|
2557
|
+
}
|
|
2432
2558
|
if (successful.length > 0) {
|
|
2433
2559
|
const bySkill = /* @__PURE__ */ new Map();
|
|
2434
2560
|
for (const r of successful) {
|
|
@@ -2445,12 +2571,12 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
2445
2571
|
if (firstResult.mode === "copy") {
|
|
2446
2572
|
resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim("(copied)")}`);
|
|
2447
2573
|
for (const r of skillResults) {
|
|
2448
|
-
const shortPath = shortenPath$
|
|
2574
|
+
const shortPath = shortenPath$2(r.path, cwd);
|
|
2449
2575
|
resultLines.push(` ${import_picocolors.default.dim("→")} ${shortPath}`);
|
|
2450
2576
|
}
|
|
2451
2577
|
} else {
|
|
2452
2578
|
if (firstResult.canonicalPath) {
|
|
2453
|
-
const shortPath = shortenPath$
|
|
2579
|
+
const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
|
|
2454
2580
|
resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
|
|
2455
2581
|
} else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName}`);
|
|
2456
2582
|
resultLines.push(...buildResultLines(skillResults, targetAgents));
|
|
@@ -2536,7 +2662,7 @@ async function handleDirectUrlSkillLegacy(source, url, options, spinner) {
|
|
|
2536
2662
|
targetAgents = selected;
|
|
2537
2663
|
}
|
|
2538
2664
|
else if (installedAgents.length === 1 || options.yes) {
|
|
2539
|
-
targetAgents = installedAgents;
|
|
2665
|
+
targetAgents = ensureUniversalAgents(installedAgents);
|
|
2540
2666
|
if (installedAgents.length === 1) {
|
|
2541
2667
|
const firstAgent = installedAgents[0];
|
|
2542
2668
|
M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
|
|
@@ -2580,7 +2706,7 @@ async function handleDirectUrlSkillLegacy(source, url, options, spinner) {
|
|
|
2580
2706
|
const overwriteStatus = new Map(overwriteChecks.map(({ agent, installed }) => [agent, installed]));
|
|
2581
2707
|
const summaryLines = [];
|
|
2582
2708
|
targetAgents.map((a) => agents[a].displayName);
|
|
2583
|
-
const shortCanonical = shortenPath$
|
|
2709
|
+
const shortCanonical = shortenPath$2(getCanonicalPath(remoteSkill.installName, { global: installGlobally }), cwd);
|
|
2584
2710
|
summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
|
|
2585
2711
|
summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
|
|
2586
2712
|
const overwriteAgents = targetAgents.filter((a) => overwriteStatus.get(a)).map((a) => agents[a].displayName);
|
|
@@ -2632,7 +2758,7 @@ async function handleDirectUrlSkillLegacy(source, url, options, spinner) {
|
|
|
2632
2758
|
const resultLines = [];
|
|
2633
2759
|
const firstResult = successful[0];
|
|
2634
2760
|
if (firstResult.canonicalPath) {
|
|
2635
|
-
const shortPath = shortenPath$
|
|
2761
|
+
const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
|
|
2636
2762
|
resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
|
|
2637
2763
|
} else resultLines.push(`${import_picocolors.default.green("✓")} ${remoteSkill.installName}`);
|
|
2638
2764
|
resultLines.push(...buildResultLines(successful, targetAgents));
|
|
@@ -2818,7 +2944,7 @@ async function runAdd(args, options = {}) {
|
|
|
2818
2944
|
targetAgents = selected;
|
|
2819
2945
|
}
|
|
2820
2946
|
else if (installedAgents.length === 1 || options.yes) {
|
|
2821
|
-
targetAgents = installedAgents;
|
|
2947
|
+
targetAgents = ensureUniversalAgents(installedAgents);
|
|
2822
2948
|
if (installedAgents.length === 1) {
|
|
2823
2949
|
const firstAgent = installedAgents[0];
|
|
2824
2950
|
M.info(`Installing to: ${import_picocolors.default.cyan(agents[firstAgent].displayName)}`);
|
|
@@ -2855,8 +2981,8 @@ async function runAdd(args, options = {}) {
|
|
|
2855
2981
|
}
|
|
2856
2982
|
installGlobally = scope;
|
|
2857
2983
|
}
|
|
2858
|
-
let installMode = "symlink";
|
|
2859
|
-
if (!options.yes) {
|
|
2984
|
+
let installMode = options.copy ? "copy" : "symlink";
|
|
2985
|
+
if (!options.copy && !options.yes) {
|
|
2860
2986
|
const modeChoice = await ve({
|
|
2861
2987
|
message: "Installation method",
|
|
2862
2988
|
options: [{
|
|
@@ -2891,7 +3017,7 @@ async function runAdd(args, options = {}) {
|
|
|
2891
3017
|
}
|
|
2892
3018
|
for (const skill of selectedSkills) {
|
|
2893
3019
|
if (summaryLines.length > 0) summaryLines.push("");
|
|
2894
|
-
const shortCanonical = shortenPath$
|
|
3020
|
+
const shortCanonical = shortenPath$2(getCanonicalPath(skill.name, { global: installGlobally }), cwd);
|
|
2895
3021
|
summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
|
|
2896
3022
|
summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
|
|
2897
3023
|
const skillOverwrites = overwriteStatus.get(skill.name);
|
|
@@ -2985,6 +3111,20 @@ async function runAdd(args, options = {}) {
|
|
|
2985
3111
|
} catch {}
|
|
2986
3112
|
}
|
|
2987
3113
|
}
|
|
3114
|
+
if (successful.length > 0 && !installGlobally) {
|
|
3115
|
+
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
3116
|
+
for (const skill of selectedSkills) {
|
|
3117
|
+
const skillDisplayName = getSkillDisplayName(skill);
|
|
3118
|
+
if (successfulSkillNames.has(skillDisplayName)) try {
|
|
3119
|
+
const computedHash = await computeSkillFolderHash(skill.path);
|
|
3120
|
+
await addSkillToLocalLock(skill.name, {
|
|
3121
|
+
source: normalizedSource || parsed.url,
|
|
3122
|
+
sourceType: parsed.type,
|
|
3123
|
+
computedHash
|
|
3124
|
+
}, cwd);
|
|
3125
|
+
} catch {}
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
2988
3128
|
if (successful.length > 0) {
|
|
2989
3129
|
const bySkill = /* @__PURE__ */ new Map();
|
|
2990
3130
|
for (const r of successful) {
|
|
@@ -3001,12 +3141,12 @@ async function runAdd(args, options = {}) {
|
|
|
3001
3141
|
if (firstResult.mode === "copy") {
|
|
3002
3142
|
resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim("(copied)")}`);
|
|
3003
3143
|
for (const r of skillResults) {
|
|
3004
|
-
const shortPath = shortenPath$
|
|
3144
|
+
const shortPath = shortenPath$2(r.path, cwd);
|
|
3005
3145
|
resultLines.push(` ${import_picocolors.default.dim("→")} ${shortPath}`);
|
|
3006
3146
|
}
|
|
3007
3147
|
} else {
|
|
3008
3148
|
if (firstResult.canonicalPath) {
|
|
3009
|
-
const shortPath = shortenPath$
|
|
3149
|
+
const shortPath = shortenPath$2(firstResult.canonicalPath, cwd);
|
|
3010
3150
|
resultLines.push(`${import_picocolors.default.green("✓")} ${shortPath}`);
|
|
3011
3151
|
} else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName}`);
|
|
3012
3152
|
resultLines.push(...buildResultLines(skillResults, targetAgents));
|
|
@@ -3113,6 +3253,7 @@ function parseAddOptions(args) {
|
|
|
3113
3253
|
}
|
|
3114
3254
|
i--;
|
|
3115
3255
|
} else if (arg === "--full-depth") options.fullDepth = true;
|
|
3256
|
+
else if (arg === "--copy") options.copy = true;
|
|
3116
3257
|
else if (arg && !arg.startsWith("-")) source.push(arg);
|
|
3117
3258
|
}
|
|
3118
3259
|
return {
|
|
@@ -3342,6 +3483,340 @@ ${DIM$2} 2) npx skills add <owner/repo@skill>${RESET$2}`;
|
|
|
3342
3483
|
else console.log(`${DIM$2}Discover more skills at${RESET$2} ${TEXT$1}https://skills.sh${RESET$2}`);
|
|
3343
3484
|
console.log();
|
|
3344
3485
|
}
|
|
3486
|
+
const isCancelled = (value) => typeof value === "symbol";
|
|
3487
|
+
function shortenPath$1(fullPath, cwd) {
|
|
3488
|
+
const home = homedir();
|
|
3489
|
+
if (fullPath === home || fullPath.startsWith(home + sep)) return "~" + fullPath.slice(home.length);
|
|
3490
|
+
if (fullPath === cwd || fullPath.startsWith(cwd + sep)) return "." + fullPath.slice(cwd.length);
|
|
3491
|
+
return fullPath;
|
|
3492
|
+
}
|
|
3493
|
+
async function discoverNodeModuleSkills(cwd) {
|
|
3494
|
+
const nodeModulesDir = join(cwd, "node_modules");
|
|
3495
|
+
const skills = [];
|
|
3496
|
+
let topNames;
|
|
3497
|
+
try {
|
|
3498
|
+
topNames = await readdir(nodeModulesDir);
|
|
3499
|
+
} catch {
|
|
3500
|
+
return skills;
|
|
3501
|
+
}
|
|
3502
|
+
const processPackageDir = async (pkgDir, packageName) => {
|
|
3503
|
+
const rootSkill = await parseSkillMd(join(pkgDir, "SKILL.md"));
|
|
3504
|
+
if (rootSkill) {
|
|
3505
|
+
skills.push({
|
|
3506
|
+
...rootSkill,
|
|
3507
|
+
packageName
|
|
3508
|
+
});
|
|
3509
|
+
return;
|
|
3510
|
+
}
|
|
3511
|
+
const searchDirs = [
|
|
3512
|
+
pkgDir,
|
|
3513
|
+
join(pkgDir, "skills"),
|
|
3514
|
+
join(pkgDir, ".agents", "skills")
|
|
3515
|
+
];
|
|
3516
|
+
for (const searchDir of searchDirs) try {
|
|
3517
|
+
const entries = await readdir(searchDir);
|
|
3518
|
+
for (const name of entries) {
|
|
3519
|
+
const skillDir = join(searchDir, name);
|
|
3520
|
+
try {
|
|
3521
|
+
if (!(await stat(skillDir)).isDirectory()) continue;
|
|
3522
|
+
} catch {
|
|
3523
|
+
continue;
|
|
3524
|
+
}
|
|
3525
|
+
const skill = await parseSkillMd(join(skillDir, "SKILL.md"));
|
|
3526
|
+
if (skill) skills.push({
|
|
3527
|
+
...skill,
|
|
3528
|
+
packageName
|
|
3529
|
+
});
|
|
3530
|
+
}
|
|
3531
|
+
} catch {}
|
|
3532
|
+
};
|
|
3533
|
+
await Promise.all(topNames.map(async (name) => {
|
|
3534
|
+
if (name.startsWith(".")) return;
|
|
3535
|
+
const fullPath = join(nodeModulesDir, name);
|
|
3536
|
+
try {
|
|
3537
|
+
if (!(await stat(fullPath)).isDirectory()) return;
|
|
3538
|
+
} catch {
|
|
3539
|
+
return;
|
|
3540
|
+
}
|
|
3541
|
+
if (name.startsWith("@")) try {
|
|
3542
|
+
const scopeNames = await readdir(fullPath);
|
|
3543
|
+
await Promise.all(scopeNames.map(async (scopedName) => {
|
|
3544
|
+
const scopedPath = join(fullPath, scopedName);
|
|
3545
|
+
try {
|
|
3546
|
+
if (!(await stat(scopedPath)).isDirectory()) return;
|
|
3547
|
+
} catch {
|
|
3548
|
+
return;
|
|
3549
|
+
}
|
|
3550
|
+
await processPackageDir(scopedPath, `${name}/${scopedName}`);
|
|
3551
|
+
}));
|
|
3552
|
+
} catch {}
|
|
3553
|
+
else await processPackageDir(fullPath, name);
|
|
3554
|
+
}));
|
|
3555
|
+
return skills;
|
|
3556
|
+
}
|
|
3557
|
+
async function runSync(args, options = {}) {
|
|
3558
|
+
const cwd = process.cwd();
|
|
3559
|
+
console.log();
|
|
3560
|
+
Ie(import_picocolors.default.bgCyan(import_picocolors.default.black(" skills experimental_sync ")));
|
|
3561
|
+
const spinner = Y();
|
|
3562
|
+
spinner.start("Scanning node_modules for skills...");
|
|
3563
|
+
const discoveredSkills = await discoverNodeModuleSkills(cwd);
|
|
3564
|
+
if (discoveredSkills.length === 0) {
|
|
3565
|
+
spinner.stop(import_picocolors.default.yellow("No skills found"));
|
|
3566
|
+
Se(import_picocolors.default.dim("No SKILL.md files found in node_modules."));
|
|
3567
|
+
return;
|
|
3568
|
+
}
|
|
3569
|
+
spinner.stop(`Found ${import_picocolors.default.green(String(discoveredSkills.length))} skill${discoveredSkills.length > 1 ? "s" : ""} in node_modules`);
|
|
3570
|
+
for (const skill of discoveredSkills) {
|
|
3571
|
+
M.info(`${import_picocolors.default.cyan(skill.name)} ${import_picocolors.default.dim(`from ${skill.packageName}`)}`);
|
|
3572
|
+
if (skill.description) M.message(import_picocolors.default.dim(` ${skill.description}`));
|
|
3573
|
+
}
|
|
3574
|
+
const localLock = await readLocalLock(cwd);
|
|
3575
|
+
const toInstall = [];
|
|
3576
|
+
const upToDate = [];
|
|
3577
|
+
if (options.force) {
|
|
3578
|
+
toInstall.push(...discoveredSkills);
|
|
3579
|
+
M.info(import_picocolors.default.dim("Force mode: reinstalling all skills"));
|
|
3580
|
+
} else {
|
|
3581
|
+
for (const skill of discoveredSkills) {
|
|
3582
|
+
const existingEntry = localLock.skills[skill.name];
|
|
3583
|
+
if (existingEntry) {
|
|
3584
|
+
if (await computeSkillFolderHash(skill.path) === existingEntry.computedHash) {
|
|
3585
|
+
upToDate.push(skill.name);
|
|
3586
|
+
continue;
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
toInstall.push(skill);
|
|
3590
|
+
}
|
|
3591
|
+
if (upToDate.length > 0) M.info(import_picocolors.default.dim(`${upToDate.length} skill${upToDate.length !== 1 ? "s" : ""} already up to date`));
|
|
3592
|
+
if (toInstall.length === 0) {
|
|
3593
|
+
console.log();
|
|
3594
|
+
Se(import_picocolors.default.green("All skills are up to date."));
|
|
3595
|
+
return;
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
M.info(`${toInstall.length} skill${toInstall.length !== 1 ? "s" : ""} to install/update`);
|
|
3599
|
+
let targetAgents;
|
|
3600
|
+
const validAgents = Object.keys(agents);
|
|
3601
|
+
const universalAgents = getUniversalAgents();
|
|
3602
|
+
if (options.agent?.includes("*")) {
|
|
3603
|
+
targetAgents = validAgents;
|
|
3604
|
+
M.info(`Installing to all ${targetAgents.length} agents`);
|
|
3605
|
+
} else if (options.agent && options.agent.length > 0) {
|
|
3606
|
+
const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
|
|
3607
|
+
if (invalidAgents.length > 0) {
|
|
3608
|
+
M.error(`Invalid agents: ${invalidAgents.join(", ")}`);
|
|
3609
|
+
M.info(`Valid agents: ${validAgents.join(", ")}`);
|
|
3610
|
+
process.exit(1);
|
|
3611
|
+
}
|
|
3612
|
+
targetAgents = options.agent;
|
|
3613
|
+
} else {
|
|
3614
|
+
spinner.start("Loading agents...");
|
|
3615
|
+
const installedAgents = await detectInstalledAgents();
|
|
3616
|
+
const totalAgents = Object.keys(agents).length;
|
|
3617
|
+
spinner.stop(`${totalAgents} agents`);
|
|
3618
|
+
if (installedAgents.length === 0) if (options.yes) {
|
|
3619
|
+
targetAgents = universalAgents;
|
|
3620
|
+
M.info("Installing to universal agents");
|
|
3621
|
+
} else {
|
|
3622
|
+
const selected = await searchMultiselect({
|
|
3623
|
+
message: "Which agents do you want to install to?",
|
|
3624
|
+
items: getNonUniversalAgents().map((a) => ({
|
|
3625
|
+
value: a,
|
|
3626
|
+
label: agents[a].displayName,
|
|
3627
|
+
hint: agents[a].skillsDir
|
|
3628
|
+
})),
|
|
3629
|
+
initialSelected: [],
|
|
3630
|
+
lockedSection: {
|
|
3631
|
+
title: "Universal (.agents/skills)",
|
|
3632
|
+
items: universalAgents.map((a) => ({
|
|
3633
|
+
value: a,
|
|
3634
|
+
label: agents[a].displayName
|
|
3635
|
+
}))
|
|
3636
|
+
}
|
|
3637
|
+
});
|
|
3638
|
+
if (isCancelled(selected)) {
|
|
3639
|
+
xe("Sync cancelled");
|
|
3640
|
+
process.exit(0);
|
|
3641
|
+
}
|
|
3642
|
+
targetAgents = selected;
|
|
3643
|
+
}
|
|
3644
|
+
else if (installedAgents.length === 1 || options.yes) {
|
|
3645
|
+
targetAgents = [...installedAgents];
|
|
3646
|
+
for (const ua of universalAgents) if (!targetAgents.includes(ua)) targetAgents.push(ua);
|
|
3647
|
+
} else {
|
|
3648
|
+
const selected = await searchMultiselect({
|
|
3649
|
+
message: "Which agents do you want to install to?",
|
|
3650
|
+
items: getNonUniversalAgents().filter((a) => installedAgents.includes(a)).map((a) => ({
|
|
3651
|
+
value: a,
|
|
3652
|
+
label: agents[a].displayName,
|
|
3653
|
+
hint: agents[a].skillsDir
|
|
3654
|
+
})),
|
|
3655
|
+
initialSelected: installedAgents.filter((a) => !universalAgents.includes(a)),
|
|
3656
|
+
lockedSection: {
|
|
3657
|
+
title: "Universal (.agents/skills)",
|
|
3658
|
+
items: universalAgents.map((a) => ({
|
|
3659
|
+
value: a,
|
|
3660
|
+
label: agents[a].displayName
|
|
3661
|
+
}))
|
|
3662
|
+
}
|
|
3663
|
+
});
|
|
3664
|
+
if (isCancelled(selected)) {
|
|
3665
|
+
xe("Sync cancelled");
|
|
3666
|
+
process.exit(0);
|
|
3667
|
+
}
|
|
3668
|
+
targetAgents = selected;
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
const summaryLines = [];
|
|
3672
|
+
for (const skill of toInstall) {
|
|
3673
|
+
const shortCanonical = shortenPath$1(getCanonicalPath(skill.name, { global: false }), cwd);
|
|
3674
|
+
summaryLines.push(`${import_picocolors.default.cyan(skill.name)} ${import_picocolors.default.dim(`← ${skill.packageName}`)}`);
|
|
3675
|
+
summaryLines.push(` ${import_picocolors.default.dim(shortCanonical)}`);
|
|
3676
|
+
}
|
|
3677
|
+
console.log();
|
|
3678
|
+
Me(summaryLines.join("\n"), "Sync Summary");
|
|
3679
|
+
if (!options.yes) {
|
|
3680
|
+
const confirmed = await ye({ message: "Proceed with sync?" });
|
|
3681
|
+
if (pD(confirmed) || !confirmed) {
|
|
3682
|
+
xe("Sync cancelled");
|
|
3683
|
+
process.exit(0);
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
spinner.start("Syncing skills...");
|
|
3687
|
+
const results = [];
|
|
3688
|
+
for (const skill of toInstall) for (const agent of targetAgents) {
|
|
3689
|
+
const result = await installSkillForAgent(skill, agent, {
|
|
3690
|
+
global: false,
|
|
3691
|
+
cwd,
|
|
3692
|
+
mode: "symlink"
|
|
3693
|
+
});
|
|
3694
|
+
results.push({
|
|
3695
|
+
skill: skill.name,
|
|
3696
|
+
packageName: skill.packageName,
|
|
3697
|
+
agent: agents[agent].displayName,
|
|
3698
|
+
success: result.success,
|
|
3699
|
+
path: result.path,
|
|
3700
|
+
canonicalPath: result.canonicalPath,
|
|
3701
|
+
error: result.error
|
|
3702
|
+
});
|
|
3703
|
+
}
|
|
3704
|
+
spinner.stop("Sync complete");
|
|
3705
|
+
const successful = results.filter((r) => r.success);
|
|
3706
|
+
const failed = results.filter((r) => !r.success);
|
|
3707
|
+
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
3708
|
+
for (const skill of toInstall) if (successfulSkillNames.has(skill.name)) try {
|
|
3709
|
+
const computedHash = await computeSkillFolderHash(skill.path);
|
|
3710
|
+
await addSkillToLocalLock(skill.name, {
|
|
3711
|
+
source: skill.packageName,
|
|
3712
|
+
sourceType: "node_modules",
|
|
3713
|
+
computedHash
|
|
3714
|
+
}, cwd);
|
|
3715
|
+
} catch {}
|
|
3716
|
+
console.log();
|
|
3717
|
+
if (successful.length > 0) {
|
|
3718
|
+
const bySkill = /* @__PURE__ */ new Map();
|
|
3719
|
+
for (const r of successful) {
|
|
3720
|
+
const skillResults = bySkill.get(r.skill) || [];
|
|
3721
|
+
skillResults.push(r);
|
|
3722
|
+
bySkill.set(r.skill, skillResults);
|
|
3723
|
+
}
|
|
3724
|
+
const resultLines = [];
|
|
3725
|
+
for (const [skillName, skillResults] of bySkill) {
|
|
3726
|
+
const firstResult = skillResults[0];
|
|
3727
|
+
const pkg = toInstall.find((s) => s.name === skillName)?.packageName;
|
|
3728
|
+
if (firstResult.canonicalPath) {
|
|
3729
|
+
const shortPath = shortenPath$1(firstResult.canonicalPath, cwd);
|
|
3730
|
+
resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim(`← ${pkg}`)}`);
|
|
3731
|
+
resultLines.push(` ${import_picocolors.default.dim(shortPath)}`);
|
|
3732
|
+
} else resultLines.push(`${import_picocolors.default.green("✓")} ${skillName} ${import_picocolors.default.dim(`← ${pkg}`)}`);
|
|
3733
|
+
}
|
|
3734
|
+
const skillCount = bySkill.size;
|
|
3735
|
+
const title = import_picocolors.default.green(`Synced ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
|
|
3736
|
+
Me(resultLines.join("\n"), title);
|
|
3737
|
+
}
|
|
3738
|
+
if (failed.length > 0) {
|
|
3739
|
+
console.log();
|
|
3740
|
+
M.error(import_picocolors.default.red(`Failed to install ${failed.length}`));
|
|
3741
|
+
for (const r of failed) M.message(` ${import_picocolors.default.red("✗")} ${r.skill} → ${r.agent}: ${import_picocolors.default.dim(r.error)}`);
|
|
3742
|
+
}
|
|
3743
|
+
track({
|
|
3744
|
+
event: "experimental_sync",
|
|
3745
|
+
skillCount: String(toInstall.length),
|
|
3746
|
+
successCount: String(successfulSkillNames.size),
|
|
3747
|
+
agents: targetAgents.join(",")
|
|
3748
|
+
});
|
|
3749
|
+
console.log();
|
|
3750
|
+
Se(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Review skills before use; they run with full agent permissions."));
|
|
3751
|
+
}
|
|
3752
|
+
function parseSyncOptions(args) {
|
|
3753
|
+
const options = {};
|
|
3754
|
+
for (let i = 0; i < args.length; i++) {
|
|
3755
|
+
const arg = args[i];
|
|
3756
|
+
if (arg === "-y" || arg === "--yes") options.yes = true;
|
|
3757
|
+
else if (arg === "-f" || arg === "--force") options.force = true;
|
|
3758
|
+
else if (arg === "-a" || arg === "--agent") {
|
|
3759
|
+
options.agent = options.agent || [];
|
|
3760
|
+
i++;
|
|
3761
|
+
let nextArg = args[i];
|
|
3762
|
+
while (i < args.length && nextArg && !nextArg.startsWith("-")) {
|
|
3763
|
+
options.agent.push(nextArg);
|
|
3764
|
+
i++;
|
|
3765
|
+
nextArg = args[i];
|
|
3766
|
+
}
|
|
3767
|
+
i--;
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
return { options };
|
|
3771
|
+
}
|
|
3772
|
+
async function runInstallFromLock(args) {
|
|
3773
|
+
const lock = await readLocalLock(process.cwd());
|
|
3774
|
+
const skillEntries = Object.entries(lock.skills);
|
|
3775
|
+
if (skillEntries.length === 0) {
|
|
3776
|
+
M.warn("No project skills found in skills-lock.json");
|
|
3777
|
+
M.info(`Add project-level skills with ${import_picocolors.default.cyan("npx skills add <package>")} (without ${import_picocolors.default.cyan("-g")})`);
|
|
3778
|
+
return;
|
|
3779
|
+
}
|
|
3780
|
+
const universalAgentNames = getUniversalAgents();
|
|
3781
|
+
const nodeModuleSkills = [];
|
|
3782
|
+
const bySource = /* @__PURE__ */ new Map();
|
|
3783
|
+
for (const [skillName, entry] of skillEntries) {
|
|
3784
|
+
if (entry.sourceType === "node_modules") {
|
|
3785
|
+
nodeModuleSkills.push(skillName);
|
|
3786
|
+
continue;
|
|
3787
|
+
}
|
|
3788
|
+
const existing = bySource.get(entry.source);
|
|
3789
|
+
if (existing) existing.skills.push(skillName);
|
|
3790
|
+
else bySource.set(entry.source, {
|
|
3791
|
+
sourceType: entry.sourceType,
|
|
3792
|
+
skills: [skillName]
|
|
3793
|
+
});
|
|
3794
|
+
}
|
|
3795
|
+
const remoteCount = skillEntries.length - nodeModuleSkills.length;
|
|
3796
|
+
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/")}`);
|
|
3797
|
+
for (const [source, { skills }] of bySource) try {
|
|
3798
|
+
await runAdd([source], {
|
|
3799
|
+
skill: skills,
|
|
3800
|
+
agent: universalAgentNames,
|
|
3801
|
+
yes: true
|
|
3802
|
+
});
|
|
3803
|
+
} catch (error) {
|
|
3804
|
+
M.error(`Failed to install from ${import_picocolors.default.cyan(source)}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3805
|
+
}
|
|
3806
|
+
if (nodeModuleSkills.length > 0) {
|
|
3807
|
+
M.info(`${import_picocolors.default.cyan(String(nodeModuleSkills.length))} skill${nodeModuleSkills.length !== 1 ? "s" : ""} from node_modules`);
|
|
3808
|
+
try {
|
|
3809
|
+
const { options: syncOptions } = parseSyncOptions(args);
|
|
3810
|
+
await runSync(args, {
|
|
3811
|
+
...syncOptions,
|
|
3812
|
+
yes: true,
|
|
3813
|
+
agent: universalAgentNames
|
|
3814
|
+
});
|
|
3815
|
+
} catch (error) {
|
|
3816
|
+
M.error(`Failed to sync node_modules skills: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3817
|
+
}
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3345
3820
|
const RESET$1 = "\x1B[0m";
|
|
3346
3821
|
const BOLD$1 = "\x1B[1m";
|
|
3347
3822
|
const DIM$1 = "\x1B[38;5;102m";
|
|
@@ -3472,10 +3947,8 @@ async function removeCommand(skillNames, options) {
|
|
|
3472
3947
|
let targetAgents;
|
|
3473
3948
|
if (options.agent && options.agent.length > 0) targetAgents = options.agent;
|
|
3474
3949
|
else {
|
|
3475
|
-
|
|
3476
|
-
targetAgents
|
|
3477
|
-
if (targetAgents.length === 0) targetAgents = Object.keys(agents);
|
|
3478
|
-
spinner.stop(`Targeting ${targetAgents.length} installed agent(s)`);
|
|
3950
|
+
targetAgents = Object.keys(agents);
|
|
3951
|
+
spinner.stop(`Targeting ${targetAgents.length} potential agent(s)`);
|
|
3479
3952
|
}
|
|
3480
3953
|
if (!options.yes) {
|
|
3481
3954
|
console.log();
|
|
@@ -3491,25 +3964,42 @@ async function removeCommand(skillNames, options) {
|
|
|
3491
3964
|
spinner.start("Removing skills...");
|
|
3492
3965
|
const results = [];
|
|
3493
3966
|
for (const skillName of selectedSkills) try {
|
|
3967
|
+
const canonicalPath = getCanonicalPath(skillName, {
|
|
3968
|
+
global: isGlobal,
|
|
3969
|
+
cwd
|
|
3970
|
+
});
|
|
3494
3971
|
for (const agentKey of targetAgents) {
|
|
3495
3972
|
const agent = agents[agentKey];
|
|
3496
3973
|
const skillPath = getInstallPath(skillName, agentKey, {
|
|
3497
3974
|
global: isGlobal,
|
|
3498
3975
|
cwd
|
|
3499
3976
|
});
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3977
|
+
const pathsToCleanup = new Set([skillPath]);
|
|
3978
|
+
const sanitizedName = sanitizeName(skillName);
|
|
3979
|
+
if (isGlobal && agent.globalSkillsDir) pathsToCleanup.add(join(agent.globalSkillsDir, sanitizedName));
|
|
3980
|
+
else pathsToCleanup.add(join(cwd, agent.skillsDir, sanitizedName));
|
|
3981
|
+
for (const pathToCleanup of pathsToCleanup) {
|
|
3982
|
+
if (pathToCleanup === canonicalPath) continue;
|
|
3983
|
+
try {
|
|
3984
|
+
if (await lstat(pathToCleanup).catch(() => null)) await rm(pathToCleanup, {
|
|
3985
|
+
recursive: true,
|
|
3986
|
+
force: true
|
|
3987
|
+
});
|
|
3988
|
+
} catch (err) {
|
|
3989
|
+
M.warn(`Could not remove skill from ${agent.displayName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
3990
|
+
}
|
|
3507
3991
|
}
|
|
3508
3992
|
}
|
|
3509
|
-
await
|
|
3993
|
+
const remainingAgents = (await detectInstalledAgents()).filter((a) => !targetAgents.includes(a));
|
|
3994
|
+
let isStillUsed = false;
|
|
3995
|
+
for (const agentKey of remainingAgents) if (await lstat(getInstallPath(skillName, agentKey, {
|
|
3510
3996
|
global: isGlobal,
|
|
3511
3997
|
cwd
|
|
3512
|
-
})
|
|
3998
|
+
})).catch(() => null)) {
|
|
3999
|
+
isStillUsed = true;
|
|
4000
|
+
break;
|
|
4001
|
+
}
|
|
4002
|
+
if (!isStillUsed) await rm(canonicalPath, {
|
|
3513
4003
|
recursive: true,
|
|
3514
4004
|
force: true
|
|
3515
4005
|
});
|
|
@@ -3626,13 +4116,17 @@ function showBanner() {
|
|
|
3626
4116
|
console.log();
|
|
3627
4117
|
console.log(`${DIM}The open agent skills ecosystem${RESET}`);
|
|
3628
4118
|
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
|
|
4119
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills add ${DIM}<package>${RESET} ${DIM}Add a new skill${RESET}`);
|
|
4120
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills remove${RESET} ${DIM}Remove installed skills${RESET}`);
|
|
4121
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills list${RESET} ${DIM}List installed skills${RESET}`);
|
|
4122
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills find ${DIM}[query]${RESET} ${DIM}Search for skills${RESET}`);
|
|
4123
|
+
console.log();
|
|
4124
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills check${RESET} ${DIM}Check for updates${RESET}`);
|
|
4125
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills update${RESET} ${DIM}Update all skills${RESET}`);
|
|
4126
|
+
console.log();
|
|
4127
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills experimental_install${RESET} ${DIM}Restore from skills-lock.json${RESET}`);
|
|
4128
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills init ${DIM}[name]${RESET} ${DIM}Create a new skill${RESET}`);
|
|
4129
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx skills experimental_sync${RESET} ${DIM}Sync skills from node_modules${RESET}`);
|
|
3636
4130
|
console.log();
|
|
3637
4131
|
console.log(`${DIM}try:${RESET} npx skills add vercel-labs/agent-skills`);
|
|
3638
4132
|
console.log();
|
|
@@ -3643,16 +4137,22 @@ function showHelp() {
|
|
|
3643
4137
|
console.log(`
|
|
3644
4138
|
${BOLD}Usage:${RESET} skills <command> [options]
|
|
3645
4139
|
|
|
3646
|
-
${BOLD}
|
|
3647
|
-
add <package>
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
remove [skills]
|
|
3651
|
-
list, ls
|
|
3652
|
-
find [query]
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
4140
|
+
${BOLD}Manage Skills:${RESET}
|
|
4141
|
+
add <package> Add a skill package (alias: a)
|
|
4142
|
+
e.g. vercel-labs/agent-skills
|
|
4143
|
+
https://github.com/vercel-labs/agent-skills
|
|
4144
|
+
remove [skills] Remove installed skills
|
|
4145
|
+
list, ls List installed skills
|
|
4146
|
+
find [query] Search for skills interactively
|
|
4147
|
+
|
|
4148
|
+
${BOLD}Updates:${RESET}
|
|
4149
|
+
check Check for available skill updates
|
|
4150
|
+
update Update all skills to latest versions
|
|
4151
|
+
|
|
4152
|
+
${BOLD}Project:${RESET}
|
|
4153
|
+
experimental_install Restore skills from skills-lock.json
|
|
4154
|
+
init [name] Initialize a skill (creates <name>/SKILL.md or ./SKILL.md)
|
|
4155
|
+
experimental_sync Sync skills from node_modules into agent directories
|
|
3656
4156
|
|
|
3657
4157
|
${BOLD}Add Options:${RESET}
|
|
3658
4158
|
-g, --global Install skill globally (user-level) instead of project-level
|
|
@@ -3660,6 +4160,7 @@ ${BOLD}Add Options:${RESET}
|
|
|
3660
4160
|
-s, --skill <skills> Specify skill names to install (use '*' for all skills)
|
|
3661
4161
|
-l, --list List available skills in the repository without installing
|
|
3662
4162
|
-y, --yes Skip confirmation prompts
|
|
4163
|
+
--copy Copy files instead of symlinking to agent directories
|
|
3663
4164
|
--all Shorthand for --skill '*' --agent '*' -y
|
|
3664
4165
|
--full-depth Search all subdirectories even when a root SKILL.md exists
|
|
3665
4166
|
|
|
@@ -3670,6 +4171,10 @@ ${BOLD}Remove Options:${RESET}
|
|
|
3670
4171
|
-y, --yes Skip confirmation prompts
|
|
3671
4172
|
--all Shorthand for --skill '*' --agent '*' -y
|
|
3672
4173
|
|
|
4174
|
+
${BOLD}Experimental Sync Options:${RESET}
|
|
4175
|
+
-a, --agent <agents> Specify agents to install to (use '*' for all agents)
|
|
4176
|
+
-y, --yes Skip confirmation prompts
|
|
4177
|
+
|
|
3673
4178
|
${BOLD}List Options:${RESET}
|
|
3674
4179
|
-g, --global List global skills (default: project)
|
|
3675
4180
|
-a, --agent <agents> Filter by specific agents
|
|
@@ -3683,17 +4188,20 @@ ${BOLD}Examples:${RESET}
|
|
|
3683
4188
|
${DIM}$${RESET} skills add vercel-labs/agent-skills -g
|
|
3684
4189
|
${DIM}$${RESET} skills add vercel-labs/agent-skills --agent claude-code cursor
|
|
3685
4190
|
${DIM}$${RESET} skills add vercel-labs/agent-skills --skill pr-review commit
|
|
3686
|
-
${DIM}$${RESET} skills remove
|
|
3687
|
-
${DIM}$${RESET} skills remove web-design
|
|
4191
|
+
${DIM}$${RESET} skills remove ${DIM}# interactive remove${RESET}
|
|
4192
|
+
${DIM}$${RESET} skills remove web-design ${DIM}# remove by name${RESET}
|
|
3688
4193
|
${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
|
|
4194
|
+
${DIM}$${RESET} skills list ${DIM}# list project skills${RESET}
|
|
4195
|
+
${DIM}$${RESET} skills ls -g ${DIM}# list global skills${RESET}
|
|
4196
|
+
${DIM}$${RESET} skills ls -a claude-code ${DIM}# filter by agent${RESET}
|
|
4197
|
+
${DIM}$${RESET} skills find ${DIM}# interactive search${RESET}
|
|
4198
|
+
${DIM}$${RESET} skills find typescript ${DIM}# search by keyword${RESET}
|
|
3695
4199
|
${DIM}$${RESET} skills check
|
|
3696
4200
|
${DIM}$${RESET} skills update
|
|
4201
|
+
${DIM}$${RESET} skills experimental_install ${DIM}# restore from skills-lock.json${RESET}
|
|
4202
|
+
${DIM}$${RESET} skills init my-skill
|
|
4203
|
+
${DIM}$${RESET} skills experimental_sync ${DIM}# sync from node_modules${RESET}
|
|
4204
|
+
${DIM}$${RESET} skills experimental_sync -y ${DIM}# sync without prompts${RESET}
|
|
3697
4205
|
|
|
3698
4206
|
Discover more skills at ${TEXT}https://skills.sh/${RESET}
|
|
3699
4207
|
`);
|
|
@@ -3984,13 +4492,17 @@ async function main() {
|
|
|
3984
4492
|
console.log();
|
|
3985
4493
|
runInit(restArgs);
|
|
3986
4494
|
break;
|
|
4495
|
+
case "experimental_install":
|
|
4496
|
+
showLogo();
|
|
4497
|
+
await runInstallFromLock(restArgs);
|
|
4498
|
+
break;
|
|
3987
4499
|
case "i":
|
|
3988
4500
|
case "install":
|
|
3989
4501
|
case "a":
|
|
3990
4502
|
case "add": {
|
|
3991
4503
|
showLogo();
|
|
3992
|
-
const { source, options } = parseAddOptions(restArgs);
|
|
3993
|
-
await runAdd(
|
|
4504
|
+
const { source: addSource, options: addOpts } = parseAddOptions(restArgs);
|
|
4505
|
+
await runAdd(addSource, addOpts);
|
|
3994
4506
|
break;
|
|
3995
4507
|
}
|
|
3996
4508
|
case "remove":
|
|
@@ -4003,6 +4515,12 @@ async function main() {
|
|
|
4003
4515
|
const { skills, options: removeOptions } = parseRemoveOptions(restArgs);
|
|
4004
4516
|
await removeCommand(skills, removeOptions);
|
|
4005
4517
|
break;
|
|
4518
|
+
case "experimental_sync": {
|
|
4519
|
+
showLogo();
|
|
4520
|
+
const { options: syncOptions } = parseSyncOptions(restArgs);
|
|
4521
|
+
await runSync(restArgs, syncOptions);
|
|
4522
|
+
break;
|
|
4523
|
+
}
|
|
4006
4524
|
case "list":
|
|
4007
4525
|
case "ls":
|
|
4008
4526
|
await runList(restArgs);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skills",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "The open agent skills ecosystem",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"codex",
|
|
47
47
|
"command-code",
|
|
48
48
|
"continue",
|
|
49
|
+
"cortex",
|
|
49
50
|
"crush",
|
|
50
51
|
"cursor",
|
|
51
52
|
"droid",
|
|
@@ -74,7 +75,8 @@
|
|
|
74
75
|
"zencoder",
|
|
75
76
|
"neovate",
|
|
76
77
|
"pochi",
|
|
77
|
-
"adal"
|
|
78
|
+
"adal",
|
|
79
|
+
"universal"
|
|
78
80
|
],
|
|
79
81
|
"repository": {
|
|
80
82
|
"type": "git",
|