skpm-cli 1.4.7 → 1.4.8

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.
Files changed (3) hide show
  1. package/README.md +81 -57
  2. package/dist/cli.mjs +326 -55
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,37 +1,60 @@
1
- # skills
1
+ # skpm
2
2
 
3
- The CLI for the open agent skills ecosystem.
3
+ The Skill Package Manager for AI agents. Fork of [vercel-labs/skills](https://github.com/vercel-labs/skills) with **dependency graph resolution**.
4
4
 
5
5
  <!-- agent-list:start -->
6
- Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [40 more](#available-agents).
6
+ Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [40 more](#supported-agents).
7
7
  <!-- agent-list:end -->
8
8
 
9
+ ## What's different from `skills`?
10
+
11
+ - **`dependsOn`** — Skills declare dependencies in SKILL.md frontmatter. `skpm add` recursively installs the full tree.
12
+ - **`postInstall`** — Skills can declare setup commands (CLI installs, config). Printed with y/n prompt before execution.
13
+ - **`--trust-scripts`** — Auto-accept postInstall commands (for CI/CD).
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ pnpm i -g skpm-cli # global install (recommended)
19
+ skpm add owner/repo@skill -g
20
+ ```
21
+
22
+ Or use without installing:
23
+
24
+ ```bash
25
+ npx skpm-cli add owner/repo@skill -g
26
+ ```
27
+
9
28
  ## Install a Skill
10
29
 
11
30
  ```bash
12
- npx skills add vercel-labs/agent-skills
31
+ skpm add jonmumm/skills@swarm -g
32
+ # → automatically installs grill-me, mutation-testing, tdd
13
33
  ```
14
34
 
15
35
  ### Source Formats
16
36
 
17
37
  ```bash
18
38
  # GitHub shorthand (owner/repo)
19
- npx skills add vercel-labs/agent-skills
39
+ skpm add vercel-labs/agent-skills
40
+
41
+ # Install a specific skill with auto-resolved dependencies
42
+ skpm add jonmumm/skills@swarm
20
43
 
21
44
  # Full GitHub URL
22
- npx skills add https://github.com/vercel-labs/agent-skills
45
+ skpm add https://github.com/vercel-labs/agent-skills
23
46
 
24
47
  # Direct path to a skill in a repo
25
- npx skills add https://github.com/vercel-labs/agent-skills/tree/main/skills/web-design-guidelines
48
+ skpm add https://github.com/vercel-labs/agent-skills/tree/main/skills/web-design-guidelines
26
49
 
27
50
  # GitLab URL
28
- npx skills add https://gitlab.com/org/repo
51
+ skpm add https://gitlab.com/org/repo
29
52
 
30
53
  # Any git URL
31
- npx skills add git@github.com:vercel-labs/agent-skills.git
54
+ skpm add git@github.com:vercel-labs/agent-skills.git
32
55
 
33
56
  # Local path
34
- npx skills add ./my-local-skills
57
+ skpm add ./my-local-skills
35
58
  ```
36
59
 
37
60
  ### Options
@@ -49,29 +72,26 @@ npx skills add ./my-local-skills
49
72
  ### Examples
50
73
 
51
74
  ```bash
75
+ # Install a skill (dependencies auto-resolved)
76
+ skpm add jonmumm/skills@swarm -g
77
+
52
78
  # List skills in a repository
53
- npx skills add vercel-labs/agent-skills --list
79
+ skpm add vercel-labs/agent-skills --list
54
80
 
55
81
  # Install specific skills
56
- npx skills add vercel-labs/agent-skills --skill frontend-design --skill skill-creator
57
-
58
- # Install a skill with spaces in the name (must be quoted)
59
- npx skills add owner/repo --skill "Convex Best Practices"
82
+ skpm add vercel-labs/agent-skills --skill frontend-design --skill skill-creator
60
83
 
61
84
  # Install to specific agents
62
- npx skills add vercel-labs/agent-skills -a claude-code -a opencode
85
+ skpm add vercel-labs/agent-skills -a claude-code -a opencode
63
86
 
64
87
  # Non-interactive installation (CI/CD friendly)
65
- npx skills add vercel-labs/agent-skills --skill frontend-design -g -a claude-code -y
88
+ skpm add vercel-labs/agent-skills --skill frontend-design -g -a claude-code -y
66
89
 
67
90
  # Install all skills from a repo to all agents
68
- npx skills add vercel-labs/agent-skills --all
69
-
70
- # Install all skills to specific agents
71
- npx skills add vercel-labs/agent-skills --skill '*' -a claude-code
91
+ skpm add vercel-labs/agent-skills --all
72
92
 
73
- # Install specific skills to all agents
74
- npx skills add vercel-labs/agent-skills --agent '*' --skill frontend-design
93
+ # Auto-accept postInstall commands (CI/CD)
94
+ skpm add jonmumm/skills@swarm -g -y --trust-scripts
75
95
  ```
76
96
 
77
97
  ### Installation Scope
@@ -94,12 +114,12 @@ When installing interactively, you can choose:
94
114
 
95
115
  | Command | Description |
96
116
  | ---------------------------- | ---------------------------------------------- |
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 |
117
+ | `skpm list` | List installed skills (alias: `ls`) |
118
+ | `skpm find [query]` | Search for skills interactively or by keyword |
119
+ | `skpm remove [skills]` | Remove installed skills from agents |
120
+ | `skpm check` | Check for available skill updates |
121
+ | `skpm update` | Update all installed skills to latest versions |
122
+ | `skpm init [name]` | Create a new SKILL.md template |
103
123
 
104
124
  ### `skills list`
105
125
 
@@ -107,13 +127,13 @@ List all installed skills. Similar to `npm ls`.
107
127
 
108
128
  ```bash
109
129
  # List all installed skills (project and global)
110
- npx skills list
130
+ skpm list
111
131
 
112
132
  # List only global skills
113
- npx skills ls -g
133
+ skpm ls -g
114
134
 
115
135
  # Filter by specific agents
116
- npx skills ls -a claude-code -a cursor
136
+ skpm ls -a claude-code -a cursor
117
137
  ```
118
138
 
119
139
  ### `skills find`
@@ -122,30 +142,30 @@ Search for skills interactively or by keyword.
122
142
 
123
143
  ```bash
124
144
  # Interactive search (fzf-style)
125
- npx skills find
145
+ skpm find
126
146
 
127
147
  # Search by keyword
128
- npx skills find typescript
148
+ skpm find typescript
129
149
  ```
130
150
 
131
151
  ### `skills check` / `skills update`
132
152
 
133
153
  ```bash
134
154
  # Check if any installed skills have updates
135
- npx skills check
155
+ skpm check
136
156
 
137
157
  # Update all skills to latest versions
138
- npx skills update
158
+ skpm update
139
159
  ```
140
160
 
141
161
  ### `skills init`
142
162
 
143
163
  ```bash
144
164
  # Create SKILL.md in current directory
145
- npx skills init
165
+ skpm init
146
166
 
147
167
  # Create a new skill in a subdirectory
148
- npx skills init my-skill
168
+ skpm init my-skill
149
169
  ```
150
170
 
151
171
  ### `skills remove`
@@ -154,31 +174,31 @@ Remove installed skills from agents.
154
174
 
155
175
  ```bash
156
176
  # Remove interactively (select from installed skills)
157
- npx skills remove
177
+ skpm remove
158
178
 
159
179
  # Remove specific skill by name
160
- npx skills remove web-design-guidelines
180
+ skpm remove web-design-guidelines
161
181
 
162
182
  # Remove multiple skills
163
- npx skills remove frontend-design web-design-guidelines
183
+ skpm remove frontend-design web-design-guidelines
164
184
 
165
185
  # Remove from global scope
166
- npx skills remove --global web-design-guidelines
186
+ skpm remove --global web-design-guidelines
167
187
 
168
188
  # Remove from specific agents only
169
- npx skills remove --agent claude-code cursor my-skill
189
+ skpm remove --agent claude-code cursor my-skill
170
190
 
171
191
  # Remove all installed skills without confirmation
172
- npx skills remove --all
192
+ skpm remove --all
173
193
 
174
194
  # Remove all skills from a specific agent
175
- npx skills remove --skill '*' -a cursor
195
+ skpm remove --skill '*' -a cursor
176
196
 
177
197
  # Remove a specific skill from all agents
178
- npx skills remove my-skill --agent '*'
198
+ skpm remove my-skill --agent '*'
179
199
 
180
200
  # Use 'rm' alias
181
- npx skills rm my-skill
201
+ skpm rm my-skill
182
202
  ```
183
203
 
184
204
  | Option | Description |
@@ -200,7 +220,7 @@ Skills let agents perform specialized tasks like:
200
220
  - Creating PRs following your team's conventions
201
221
  - Integrating with external tools (Linear, Notion, etc.)
202
222
 
203
- Discover skills at **[skills.sh](https://skills.sh)**
223
+ Discover skills at **[skpm.sh](https://skpm.sh)**
204
224
 
205
225
  ## Supported Agents
206
226
 
@@ -295,16 +315,19 @@ Describe the scenarios where this skill should be used.
295
315
 
296
316
  ### Optional Fields
297
317
 
298
- - `metadata.internal`: Set to `true` to hide the skill from normal discovery. Internal skills are only visible and
299
- installable when `INSTALL_INTERNAL_SKILLS=1` is set. Useful for work-in-progress skills or skills meant only for
300
- internal tooling.
318
+ - `dependsOn`: Array of skill dependencies in `owner/repo@skill-name` format. Resolved recursively on install.
319
+ - `postInstall`: Array of shell commands to run after installation. Printed with y/n prompt (or auto-accepted with `--trust-scripts`).
320
+ - `metadata.internal`: Set to `true` to hide the skill from normal discovery.
301
321
 
302
322
  ```markdown
303
323
  ---
304
- name: my-internal-skill
305
- description: An internal skill not shown by default
306
- metadata:
307
- internal: true
324
+ name: my-skill
325
+ description: My skill that depends on others
326
+ dependsOn:
327
+ - jonmumm/skills@grill-me
328
+ - mattpocock/skills@tdd
329
+ postInstall:
330
+ - "which linear || pnpm i -g @linear/cli"
308
331
  ---
309
332
  ```
310
333
 
@@ -410,7 +433,7 @@ Ensure you have write access to the target directory.
410
433
 
411
434
  ```bash
412
435
  # Install internal skills
413
- INSTALL_INTERNAL_SKILLS=1 npx skills add vercel-labs/agent-skills --list
436
+ INSTALL_INTERNAL_SKILLS=1 skpm add vercel-labs/agent-skills --list
414
437
  ```
415
438
 
416
439
  ## Telemetry
@@ -422,7 +445,7 @@ Telemetry is automatically disabled in CI environments.
422
445
  ## Related Links
423
446
 
424
447
  - [Agent Skills Specification](https://agentskills.io)
425
- - [Skills Directory](https://skills.sh)
448
+ - [Skills Directory](https://skpm.sh)
426
449
  - [Amp Skills Documentation](https://ampcode.com/manual#agent-skills)
427
450
  - [Antigravity Skills Documentation](https://antigravity.google/docs/skills)
428
451
  - [Factory AI / Droid Skills Documentation](https://docs.factory.ai/cli/configuration/skills)
@@ -449,6 +472,7 @@ Telemetry is automatically disabled in CI environments.
449
472
  - [Replit Skills Documentation](https://docs.replit.com/replitai/skills)
450
473
  - [Roo Code Skills Documentation](https://docs.roocode.com/features/skills)
451
474
  - [Trae Skills Documentation](https://docs.trae.ai/ide/skills)
475
+ - [skpm CLI](https://github.com/skpm-sh/cli)
452
476
  - [Vercel Agent Skills Repository](https://github.com/vercel-labs/agent-skills)
453
477
 
454
478
  ## License
package/dist/cli.mjs CHANGED
@@ -1900,7 +1900,7 @@ function createEmptyLocalLock() {
1900
1900
  skills: {}
1901
1901
  };
1902
1902
  }
1903
- var version$1 = "1.4.7";
1903
+ var version$1 = "1.4.8";
1904
1904
  const isCancelled$1 = (value) => typeof value === "symbol";
1905
1905
  async function isSourcePrivate(source) {
1906
1906
  const ownerRepo = parseOwnerRepo(source);
@@ -2824,37 +2824,86 @@ async function runAdd(args, options = {}) {
2824
2824
  for (const r of failed) M.message(` ${import_picocolors.default.red("✗")} ${r.skill} → ${r.agent}: ${import_picocolors.default.dim(r.error)}`);
2825
2825
  }
2826
2826
  const allPostInstallCommands = [];
2827
+ const depFailures = [];
2828
+ const sharedResolved = /* @__PURE__ */ new Set();
2829
+ const depCloneCache = /* @__PURE__ */ new Map();
2830
+ const depTempDirs = /* @__PURE__ */ new Set();
2831
+ if (tempDir && normalizedSource) depCloneCache.set(parsed.url, tempDir);
2827
2832
  for (const skill of selectedSkills) {
2828
2833
  if (skill.postInstall.length > 0) allPostInstallCommands.push(...skill.postInstall);
2829
2834
  if (skill.dependsOn.length > 0) {
2830
2835
  M.info(import_picocolors.default.dim(`Resolving dependencies for ${skill.name}...`));
2831
- const depResult = await resolveDependencies(skill, async (source, skillName) => {
2832
- const depSource = skillName ? `${source}@${skillName}` : source;
2836
+ const depResult = await resolveDependencies(skill, async (depSourceStr, skillName) => {
2837
+ const depSource = skillName ? `${depSourceStr}@${skillName}` : depSourceStr;
2833
2838
  M.step(import_picocolors.default.dim(` Installing dependency: ${depSource}`));
2834
- const parsed = parseSource(source);
2835
- let depTempDir = null;
2839
+ const depParsed = parseSource(depSourceStr);
2840
+ let depDir;
2836
2841
  try {
2837
- if (parsed.type === "local") depTempDir = parsed.localPath;
2838
- else depTempDir = await cloneRepo(parsed.url, parsed.ref);
2839
- const depSkills = await discoverSkills(depTempDir, parsed.subpath, { includeInternal: true });
2842
+ if (depParsed.type === "local") depDir = depParsed.localPath;
2843
+ else {
2844
+ const cacheKey = depParsed.url;
2845
+ if (depCloneCache.has(cacheKey)) depDir = depCloneCache.get(cacheKey);
2846
+ else {
2847
+ depDir = await cloneRepo(depParsed.url, depParsed.ref);
2848
+ depCloneCache.set(cacheKey, depDir);
2849
+ depTempDirs.add(depDir);
2850
+ }
2851
+ }
2852
+ const depSkills = await discoverSkills(depDir, depParsed.subpath, { includeInternal: true });
2840
2853
  const targetSkills = skillName ? depSkills.filter((s) => s.name.toLowerCase() === skillName.toLowerCase()) : depSkills;
2841
2854
  if (targetSkills.length === 0) {
2842
2855
  M.warn(import_picocolors.default.yellow(` Dependency skill not found: ${depSource}`));
2843
2856
  return [];
2844
2857
  }
2845
- for (const depSkill of targetSkills) for (const agent of targetAgents) await installSkillForAgent(depSkill, agent, {
2846
- global: installGlobally,
2847
- mode: installMode
2848
- });
2858
+ for (const depSkill of targetSkills) {
2859
+ for (const agent of targetAgents) {
2860
+ const depInstallResult = await installSkillForAgent(depSkill, agent, {
2861
+ global: installGlobally,
2862
+ mode: installMode
2863
+ });
2864
+ if (!depInstallResult.success) depFailures.push({
2865
+ skill: depSkill.name,
2866
+ agent: agents[agent].displayName,
2867
+ error: depInstallResult.error
2868
+ });
2869
+ }
2870
+ if (installGlobally) try {
2871
+ const depNormalizedSource = getOwnerRepo(depParsed);
2872
+ let depSkillPath;
2873
+ if (depDir && depSkill.path.startsWith(depDir + sep)) depSkillPath = depSkill.path.slice(depDir.length + 1).split(sep).join("/") + "/SKILL.md";
2874
+ else if (depDir && depSkill.path === depDir) depSkillPath = "SKILL.md";
2875
+ let depFolderHash = "";
2876
+ if (depParsed.type === "github" && depSkillPath && depNormalizedSource) {
2877
+ const token = getGitHubToken();
2878
+ const hash = await fetchSkillFolderHash(depNormalizedSource, depSkillPath, token);
2879
+ if (hash) depFolderHash = hash;
2880
+ }
2881
+ const depLockSource = depParsed.url.startsWith("git@") ? depParsed.url : depNormalizedSource || depParsed.url;
2882
+ await addSkillToLock(depSkill.name, {
2883
+ source: depLockSource,
2884
+ sourceType: depParsed.type,
2885
+ sourceUrl: depParsed.url,
2886
+ skillPath: depSkillPath,
2887
+ skillFolderHash: depFolderHash,
2888
+ pluginName: depSkill.pluginName
2889
+ });
2890
+ } catch {}
2891
+ }
2849
2892
  return targetSkills;
2850
- } finally {
2851
- if (depTempDir && parsed.type !== "local") await cleanupTempDir(depTempDir).catch(() => {});
2893
+ } catch (err) {
2894
+ throw err;
2852
2895
  }
2853
- }, (warning) => M.warn(import_picocolors.default.yellow(warning)));
2896
+ }, (warning) => M.warn(import_picocolors.default.yellow(warning)), sharedResolved);
2854
2897
  allPostInstallCommands.push(...depResult.postInstallCommands);
2855
2898
  if (depResult.errors.length > 0) for (const err of depResult.errors) M.warn(import_picocolors.default.yellow(`Dependency ${err.ref}: ${err.error}`));
2856
2899
  }
2857
2900
  }
2901
+ for (const dir of depTempDirs) await cleanupTempDir(dir).catch(() => {});
2902
+ if (depFailures.length > 0) {
2903
+ console.log();
2904
+ M.error(import_picocolors.default.red(`Failed to install ${depFailures.length} dependency(ies)`));
2905
+ for (const f of depFailures) M.message(` ${import_picocolors.default.red("✗")} ${f.skill} → ${f.agent}${f.error ? `: ${import_picocolors.default.dim(f.error)}` : ""}`);
2906
+ }
2858
2907
  if (allPostInstallCommands.length > 0) {
2859
2908
  console.log();
2860
2909
  M.info("Post-install commands required:");
@@ -2967,10 +3016,10 @@ function parseAddOptions(args) {
2967
3016
  options
2968
3017
  };
2969
3018
  }
2970
- const RESET$2 = "\x1B[0m";
2971
- const BOLD$2 = "\x1B[1m";
2972
- const DIM$2 = "\x1B[38;5;102m";
2973
- const TEXT$1 = "\x1B[38;5;145m";
3019
+ const RESET$4 = "\x1B[0m";
3020
+ const BOLD$4 = "\x1B[1m";
3021
+ const DIM$4 = "\x1B[38;5;102m";
3022
+ const TEXT$3 = "\x1B[38;5;145m";
2974
3023
  const CYAN$1 = "\x1B[36m";
2975
3024
  const SEARCH_API_BASE = process.env.SKPM_API_URL || "https://skpm.sh";
2976
3025
  function formatInstalls(count) {
@@ -3014,28 +3063,28 @@ async function runSearchPrompt(initialQuery = "") {
3014
3063
  if (lastRenderedLines > 0) process.stdout.write(MOVE_UP(lastRenderedLines) + MOVE_TO_COL(1));
3015
3064
  process.stdout.write(CLEAR_DOWN);
3016
3065
  const lines = [];
3017
- const cursor = `${BOLD$2}_${RESET$2}`;
3018
- lines.push(`${TEXT$1}Search skills:${RESET$2} ${query}${cursor}`);
3066
+ const cursor = `${BOLD$4}_${RESET$4}`;
3067
+ lines.push(`${TEXT$3}Search skills:${RESET$4} ${query}${cursor}`);
3019
3068
  lines.push("");
3020
- if (!query || query.length < 2) lines.push(`${DIM$2}Start typing to search (min 2 chars)${RESET$2}`);
3021
- else if (results.length === 0 && loading) lines.push(`${DIM$2}Searching...${RESET$2}`);
3022
- else if (results.length === 0) lines.push(`${DIM$2}No skills found${RESET$2}`);
3069
+ if (!query || query.length < 2) lines.push(`${DIM$4}Start typing to search (min 2 chars)${RESET$4}`);
3070
+ else if (results.length === 0 && loading) lines.push(`${DIM$4}Searching...${RESET$4}`);
3071
+ else if (results.length === 0) lines.push(`${DIM$4}No skills found${RESET$4}`);
3023
3072
  else {
3024
3073
  const visible = results.slice(0, 8);
3025
3074
  for (let i = 0; i < visible.length; i++) {
3026
3075
  const skill = visible[i];
3027
3076
  const isSelected = i === selectedIndex;
3028
- const arrow = isSelected ? `${BOLD$2}>${RESET$2}` : " ";
3029
- const name = isSelected ? `${BOLD$2}${skill.name}${RESET$2}` : `${TEXT$1}${skill.name}${RESET$2}`;
3030
- const source = skill.source ? ` ${DIM$2}${skill.source}${RESET$2}` : "";
3077
+ const arrow = isSelected ? `${BOLD$4}>${RESET$4}` : " ";
3078
+ const name = isSelected ? `${BOLD$4}${skill.name}${RESET$4}` : `${TEXT$3}${skill.name}${RESET$4}`;
3079
+ const source = skill.source ? ` ${DIM$4}${skill.source}${RESET$4}` : "";
3031
3080
  const installs = formatInstalls(skill.installs);
3032
- const installsBadge = installs ? ` ${CYAN$1}${installs}${RESET$2}` : "";
3033
- const loadingIndicator = loading && i === 0 ? ` ${DIM$2}...${RESET$2}` : "";
3081
+ const installsBadge = installs ? ` ${CYAN$1}${installs}${RESET$4}` : "";
3082
+ const loadingIndicator = loading && i === 0 ? ` ${DIM$4}...${RESET$4}` : "";
3034
3083
  lines.push(` ${arrow} ${name}${source}${installsBadge}${loadingIndicator}`);
3035
3084
  }
3036
3085
  }
3037
3086
  lines.push("");
3038
- lines.push(`${DIM$2}up/down navigate | enter select | esc cancel${RESET$2}`);
3087
+ lines.push(`${DIM$4}up/down navigate | enter select | esc cancel${RESET$4}`);
3039
3088
  for (const line of lines) process.stdout.write(line + "\n");
3040
3089
  lastRenderedLines = lines.length;
3041
3090
  }
@@ -3131,9 +3180,9 @@ async function isRepoPublic(owner, repo) {
3131
3180
  async function runFind(args) {
3132
3181
  const query = args.join(" ");
3133
3182
  const isNonInteractive = !process.stdin.isTTY;
3134
- const agentTip = `${DIM$2}Tip: if running in a coding agent, follow these steps:${RESET$2}
3135
- ${DIM$2} 1) npx skpm-cli find [query]${RESET$2}
3136
- ${DIM$2} 2) npx skpm-cli add <owner/repo@skill>${RESET$2}`;
3183
+ const agentTip = `${DIM$4}Tip: if running in a coding agent, follow these steps:${RESET$4}
3184
+ ${DIM$4} 1) npx skpm-cli find [query]${RESET$4}
3185
+ ${DIM$4} 2) npx skpm-cli add <owner/repo@skill>${RESET$4}`;
3137
3186
  if (query) {
3138
3187
  const results = await searchSkillsAPI(query);
3139
3188
  track({
@@ -3142,16 +3191,16 @@ ${DIM$2} 2) npx skpm-cli add <owner/repo@skill>${RESET$2}`;
3142
3191
  resultCount: String(results.length)
3143
3192
  });
3144
3193
  if (results.length === 0) {
3145
- console.log(`${DIM$2}No skills found for "${query}"${RESET$2}`);
3194
+ console.log(`${DIM$4}No skills found for "${query}"${RESET$4}`);
3146
3195
  return;
3147
3196
  }
3148
- console.log(`${DIM$2}Install with${RESET$2} npx skpm-cli add <owner/repo@skill>`);
3197
+ console.log(`${DIM$4}Install with${RESET$4} npx skpm-cli add <owner/repo@skill>`);
3149
3198
  console.log();
3150
3199
  for (const skill of results.slice(0, 6)) {
3151
3200
  const pkg = skill.source || skill.slug;
3152
3201
  const installs = formatInstalls(skill.installs);
3153
- console.log(`${TEXT$1}${pkg}@${skill.name}${RESET$2}${installs ? ` ${CYAN$1}${installs}${RESET$2}` : ""}`);
3154
- console.log(`${DIM$2}└ https://skpm.sh/${skill.slug}${RESET$2}`);
3202
+ console.log(`${TEXT$3}${pkg}@${skill.name}${RESET$4}${installs ? ` ${CYAN$1}${installs}${RESET$4}` : ""}`);
3203
+ console.log(`${DIM$4}└ https://skpm.sh/${skill.slug}${RESET$4}`);
3155
3204
  console.log();
3156
3205
  }
3157
3206
  return;
@@ -3168,14 +3217,14 @@ ${DIM$2} 2) npx skpm-cli add <owner/repo@skill>${RESET$2}`;
3168
3217
  interactive: "1"
3169
3218
  });
3170
3219
  if (!selected) {
3171
- console.log(`${DIM$2}Search cancelled${RESET$2}`);
3220
+ console.log(`${DIM$4}Search cancelled${RESET$4}`);
3172
3221
  console.log();
3173
3222
  return;
3174
3223
  }
3175
3224
  const pkg = selected.source || selected.slug;
3176
3225
  const skillName = selected.name;
3177
3226
  console.log();
3178
- console.log(`${TEXT$1}Installing ${BOLD$2}${skillName}${RESET$2} from ${DIM$2}${pkg}${RESET$2}...`);
3227
+ console.log(`${TEXT$3}Installing ${BOLD$4}${skillName}${RESET$4} from ${DIM$4}${pkg}${RESET$4}...`);
3179
3228
  console.log();
3180
3229
  const { source, options } = parseAddOptions([
3181
3230
  pkg,
@@ -3185,8 +3234,8 @@ ${DIM$2} 2) npx skpm-cli add <owner/repo@skill>${RESET$2}`;
3185
3234
  await runAdd(source, options);
3186
3235
  console.log();
3187
3236
  const info = getOwnerRepoFromString(pkg);
3188
- if (info && await isRepoPublic(info.owner, info.repo)) console.log(`${DIM$2}View the skill at${RESET$2} ${TEXT$1}https://skpm.sh/${selected.slug}${RESET$2}`);
3189
- else console.log(`${DIM$2}Discover more skills at${RESET$2} ${TEXT$1}https://skpm.sh${RESET$2}`);
3237
+ if (info && await isRepoPublic(info.owner, info.repo)) console.log(`${DIM$4}View the skill at${RESET$4} ${TEXT$3}https://skpm.sh/${selected.slug}${RESET$4}`);
3238
+ else console.log(`${DIM$4}Discover more skills at${RESET$4} ${TEXT$3}https://skpm.sh${RESET$4}`);
3190
3239
  console.log();
3191
3240
  }
3192
3241
  const isCancelled = (value) => typeof value === "symbol";
@@ -3523,9 +3572,9 @@ async function runInstallFromLock(args) {
3523
3572
  }
3524
3573
  }
3525
3574
  }
3526
- const RESET$1 = "\x1B[0m";
3527
- const BOLD$1 = "\x1B[1m";
3528
- const DIM$1 = "\x1B[38;5;102m";
3575
+ const RESET$3 = "\x1B[0m";
3576
+ const BOLD$3 = "\x1B[1m";
3577
+ const DIM$3 = "\x1B[38;5;102m";
3529
3578
  const CYAN = "\x1B[36m";
3530
3579
  const YELLOW = "\x1B[33m";
3531
3580
  function shortenPath(fullPath, cwd) {
@@ -3561,8 +3610,8 @@ async function runList(args) {
3561
3610
  const validAgents = Object.keys(agents);
3562
3611
  const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
3563
3612
  if (invalidAgents.length > 0) {
3564
- console.log(`${YELLOW}Invalid agents: ${invalidAgents.join(", ")}${RESET$1}`);
3565
- console.log(`${DIM$1}Valid agents: ${validAgents.join(", ")}${RESET$1}`);
3613
+ console.log(`${YELLOW}Invalid agents: ${invalidAgents.join(", ")}${RESET$3}`);
3614
+ console.log(`${DIM$3}Valid agents: ${validAgents.join(", ")}${RESET$3}`);
3566
3615
  process.exit(1);
3567
3616
  }
3568
3617
  agentFilter = options.agent;
@@ -3589,20 +3638,20 @@ async function runList(args) {
3589
3638
  console.log("[]");
3590
3639
  return;
3591
3640
  }
3592
- console.log(`${DIM$1}No ${scopeLabel.toLowerCase()} skills found.${RESET$1}`);
3593
- if (scope) console.log(`${DIM$1}Try listing project skills without -g${RESET$1}`);
3594
- else console.log(`${DIM$1}Try listing global skills with -g${RESET$1}`);
3641
+ console.log(`${DIM$3}No ${scopeLabel.toLowerCase()} skills found.${RESET$3}`);
3642
+ if (scope) console.log(`${DIM$3}Try listing project skills without -g${RESET$3}`);
3643
+ else console.log(`${DIM$3}Try listing global skills with -g${RESET$3}`);
3595
3644
  return;
3596
3645
  }
3597
3646
  function printSkill(skill, indent = false) {
3598
3647
  const prefix = indent ? " " : "";
3599
3648
  const shortPath = shortenPath(skill.canonicalPath, cwd);
3600
3649
  const agentNames = skill.agents.map((a) => agents[a].displayName);
3601
- const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW}not linked${RESET$1}`;
3602
- console.log(`${prefix}${CYAN}${skill.name}${RESET$1} ${DIM$1}${shortPath}${RESET$1}`);
3603
- console.log(`${prefix} ${DIM$1}Agents:${RESET$1} ${agentInfo}`);
3650
+ const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW}not linked${RESET$3}`;
3651
+ console.log(`${prefix}${CYAN}${skill.name}${RESET$3} ${DIM$3}${shortPath}${RESET$3}`);
3652
+ console.log(`${prefix} ${DIM$3}Agents:${RESET$3} ${agentInfo}`);
3604
3653
  }
3605
- console.log(`${BOLD$1}${scopeLabel} Skills${RESET$1}`);
3654
+ console.log(`${BOLD$3}${scopeLabel} Skills${RESET$3}`);
3606
3655
  console.log();
3607
3656
  const groupedSkills = {};
3608
3657
  const ungroupedSkills = [];
@@ -3618,13 +3667,13 @@ async function runList(args) {
3618
3667
  const sortedGroups = Object.keys(groupedSkills).sort();
3619
3668
  for (const group of sortedGroups) {
3620
3669
  const title = group.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
3621
- console.log(`${BOLD$1}${title}${RESET$1}`);
3670
+ console.log(`${BOLD$3}${title}${RESET$3}`);
3622
3671
  const skills = groupedSkills[group];
3623
3672
  if (skills) for (const skill of skills) printSkill(skill, true);
3624
3673
  console.log();
3625
3674
  }
3626
3675
  if (ungroupedSkills.length > 0) {
3627
- console.log(`${BOLD$1}General${RESET$1}`);
3676
+ console.log(`${BOLD$3}General${RESET$3}`);
3628
3677
  for (const skill of ungroupedSkills) printSkill(skill, true);
3629
3678
  console.log();
3630
3679
  }
@@ -3823,6 +3872,218 @@ function parseRemoveOptions(args) {
3823
3872
  options
3824
3873
  };
3825
3874
  }
3875
+ const RESET$2 = "\x1B[0m";
3876
+ const BOLD$2 = "\x1B[1m";
3877
+ const DIM$2 = "\x1B[38;5;102m";
3878
+ const TEXT$2 = "\x1B[38;5;145m";
3879
+ async function buildDependencyTree(skill, resolving, cloneCache, tempDirs) {
3880
+ const nodes = [];
3881
+ for (const depRef of skill.dependsOn) {
3882
+ if (resolving.has(depRef)) {
3883
+ nodes.push({
3884
+ name: `${depRef} (circular)`,
3885
+ ref: depRef,
3886
+ children: []
3887
+ });
3888
+ continue;
3889
+ }
3890
+ const parsed = parseDependencyRef(depRef);
3891
+ if (!parsed) {
3892
+ nodes.push({
3893
+ name: `${depRef} (invalid ref)`,
3894
+ ref: depRef,
3895
+ children: []
3896
+ });
3897
+ continue;
3898
+ }
3899
+ resolving.add(depRef);
3900
+ try {
3901
+ const parsedSource = parseSource(parsed.source);
3902
+ let depDir;
3903
+ const cacheKey = parsedSource.url;
3904
+ if (cloneCache.has(cacheKey)) depDir = cloneCache.get(cacheKey);
3905
+ else if (parsedSource.type === "local") depDir = parsedSource.localPath;
3906
+ else {
3907
+ depDir = await cloneRepo(parsedSource.url, parsedSource.ref);
3908
+ tempDirs.add(depDir);
3909
+ cloneCache.set(cacheKey, depDir);
3910
+ }
3911
+ const depSkills = await discoverSkills(depDir, parsedSource.subpath, { includeInternal: true });
3912
+ const targetSkills = parsed.skillName ? depSkills.filter((s) => s.name.toLowerCase() === parsed.skillName.toLowerCase()) : depSkills;
3913
+ for (const depSkill of targetSkills) {
3914
+ const children = await buildDependencyTree(depSkill, resolving, cloneCache, tempDirs);
3915
+ nodes.push({
3916
+ name: depRef,
3917
+ ref: depRef,
3918
+ children
3919
+ });
3920
+ }
3921
+ if (targetSkills.length === 0) nodes.push({
3922
+ name: `${depRef} (not found)`,
3923
+ ref: depRef,
3924
+ children: []
3925
+ });
3926
+ } catch (err) {
3927
+ const msg = err instanceof Error ? err.message : String(err);
3928
+ nodes.push({
3929
+ name: `${depRef} (error: ${msg})`,
3930
+ ref: depRef,
3931
+ children: []
3932
+ });
3933
+ }
3934
+ }
3935
+ return nodes;
3936
+ }
3937
+ function printTree(nodes, prefix = "") {
3938
+ for (let i = 0; i < nodes.length; i++) {
3939
+ const node = nodes[i];
3940
+ const isLast = i === nodes.length - 1;
3941
+ const connector = isLast ? "└── " : "├── ";
3942
+ const childPrefix = isLast ? " " : "│ ";
3943
+ console.log(`${prefix}${connector}${TEXT$2}${node.name}${RESET$2}`);
3944
+ if (node.children.length > 0) printTree(node.children, prefix + childPrefix);
3945
+ }
3946
+ }
3947
+ async function runDeps(args) {
3948
+ if (args.includes("--help") || args.includes("-h")) {
3949
+ console.log(`
3950
+ ${BOLD$2}Usage:${RESET$2} skpm deps <package>
3951
+
3952
+ ${BOLD$2}Description:${RESET$2}
3953
+ Show the dependency tree for a skill without installing it.
3954
+
3955
+ ${BOLD$2}Arguments:${RESET$2}
3956
+ package Skill reference (e.g., owner/repo@skill-name)
3957
+
3958
+ ${BOLD$2}Examples:${RESET$2}
3959
+ ${DIM$2}$${RESET$2} skpm deps jonmumm/skills@swarm
3960
+ ${DIM$2}$${RESET$2} skpm deps vercel-labs/agent-skills@pr-review
3961
+ `);
3962
+ return;
3963
+ }
3964
+ const source = args[0];
3965
+ if (!source) {
3966
+ console.log(`${RESET$2}Missing required argument: package`);
3967
+ console.log(`${DIM$2}Usage: skpm deps <package>${RESET$2}`);
3968
+ process.exit(1);
3969
+ }
3970
+ const tempDirs = /* @__PURE__ */ new Set();
3971
+ const cloneCache = /* @__PURE__ */ new Map();
3972
+ try {
3973
+ const parsed = parseSource(source);
3974
+ let skillsDir;
3975
+ if (parsed.type === "local") skillsDir = parsed.localPath;
3976
+ else {
3977
+ console.log(`${DIM$2}Cloning repository...${RESET$2}`);
3978
+ skillsDir = await cloneRepo(parsed.url, parsed.ref);
3979
+ tempDirs.add(skillsDir);
3980
+ cloneCache.set(parsed.url, skillsDir);
3981
+ }
3982
+ const skills = await discoverSkills(skillsDir, parsed.subpath, { includeInternal: true });
3983
+ const targetSkills = parsed.skillFilter ? skills.filter((s) => s.name.toLowerCase() === parsed.skillFilter.toLowerCase()) : skills;
3984
+ if (targetSkills.length === 0) {
3985
+ console.log(`No skills found matching: ${source}`);
3986
+ process.exit(1);
3987
+ }
3988
+ for (const skill of targetSkills) {
3989
+ console.log(`${BOLD$2}${skill.name}${RESET$2}`);
3990
+ if (skill.dependsOn.length === 0) {
3991
+ console.log(`${DIM$2} (no dependencies)${RESET$2}`);
3992
+ continue;
3993
+ }
3994
+ printTree(await buildDependencyTree(skill, /* @__PURE__ */ new Set(), cloneCache, tempDirs));
3995
+ }
3996
+ } finally {
3997
+ for (const dir of tempDirs) await cleanupTempDir(dir).catch(() => {});
3998
+ }
3999
+ }
4000
+ const RESET$1 = "\x1B[0m";
4001
+ const BOLD$1 = "\x1B[1m";
4002
+ const DIM$1 = "\x1B[38;5;102m";
4003
+ const TEXT$1 = "\x1B[38;5;145m";
4004
+ function parseRunScriptsOptions(args) {
4005
+ const options = {};
4006
+ let skillName;
4007
+ for (const arg of args) if (arg === "--trust-scripts") options.trustScripts = true;
4008
+ else if (!arg.startsWith("-")) skillName = arg;
4009
+ return {
4010
+ skillName,
4011
+ options
4012
+ };
4013
+ }
4014
+ async function runRunScripts(args) {
4015
+ if (args.includes("--help") || args.includes("-h")) {
4016
+ console.log(`
4017
+ ${BOLD$1}Usage:${RESET$1} skpm run-scripts <skill-name> [options]
4018
+
4019
+ ${BOLD$1}Description:${RESET$1}
4020
+ Execute postInstall commands for an already-installed skill.
4021
+
4022
+ ${BOLD$1}Arguments:${RESET$1}
4023
+ skill-name Name of the installed skill
4024
+
4025
+ ${BOLD$1}Options:${RESET$1}
4026
+ --trust-scripts Auto-accept without prompting
4027
+
4028
+ ${BOLD$1}Examples:${RESET$1}
4029
+ ${DIM$1}$${RESET$1} skpm run-scripts swarm
4030
+ ${DIM$1}$${RESET$1} skpm run-scripts swarm --trust-scripts
4031
+ `);
4032
+ return;
4033
+ }
4034
+ const { skillName, options } = parseRunScriptsOptions(args);
4035
+ if (!skillName) {
4036
+ console.log(`Missing required argument: skill-name`);
4037
+ console.log(`${DIM$1}Usage: skpm run-scripts <skill-name>${RESET$1}`);
4038
+ process.exit(1);
4039
+ }
4040
+ const skillMdPath = join(join(homedir(), ".agents", "skills", skillName), "SKILL.md");
4041
+ if (!existsSync(skillMdPath)) {
4042
+ console.log(`Skill not found: ${skillName}`);
4043
+ console.log(`${DIM$1}Expected at: ${skillMdPath}${RESET$1}`);
4044
+ process.exit(1);
4045
+ }
4046
+ const skill = await parseSkillMd(skillMdPath, { includeInternal: true });
4047
+ if (!skill) {
4048
+ console.log(`Failed to parse SKILL.md for: ${skillName}`);
4049
+ process.exit(1);
4050
+ }
4051
+ if (skill.postInstall.length === 0) {
4052
+ console.log(`${TEXT$1}No postInstall commands defined for ${skillName}${RESET$1}`);
4053
+ return;
4054
+ }
4055
+ console.log(`${TEXT$1}Post-install commands for ${BOLD$1}${skillName}${RESET$1}${TEXT$1}:${RESET$1}`);
4056
+ console.log(formatPostInstallCommands(skill.postInstall));
4057
+ console.log();
4058
+ let shouldRun = options.trustScripts === true;
4059
+ if (!shouldRun) {
4060
+ const rl = readline.createInterface({
4061
+ input: process.stdin,
4062
+ output: process.stdout
4063
+ });
4064
+ const answer = await new Promise((resolve) => {
4065
+ rl.question("Run these commands? [y/N] ", (ans) => {
4066
+ rl.close();
4067
+ resolve(ans);
4068
+ });
4069
+ });
4070
+ shouldRun = answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
4071
+ }
4072
+ if (shouldRun) {
4073
+ const result = await executePostInstallCommands(skill.postInstall, (cmd) => {
4074
+ console.log(`${DIM$1}$ ${cmd}${RESET$1}`);
4075
+ execSync(cmd, { stdio: "inherit" });
4076
+ });
4077
+ if (result.failed.length > 0) {
4078
+ for (const f of result.failed) {
4079
+ console.log(`${RESET$1}Command failed: ${f.command}`);
4080
+ console.log(`${DIM$1}${f.error}${RESET$1}`);
4081
+ }
4082
+ process.exit(1);
4083
+ }
4084
+ console.log(`${TEXT$1}All commands completed successfully${RESET$1}`);
4085
+ } else console.log(`${DIM$1}Skipped.${RESET$1}`);
4086
+ }
3826
4087
  const __dirname = dirname(fileURLToPath(import.meta.url));
3827
4088
  function getVersion() {
3828
4089
  try {
@@ -3898,6 +4159,10 @@ ${BOLD}Updates:${RESET}
3898
4159
  check Check for available skill updates
3899
4160
  update Update all skills to latest versions
3900
4161
 
4162
+ ${BOLD}Inspect:${RESET}
4163
+ deps <package> Show dependency tree for a skill
4164
+ run-scripts <name> Execute postInstall for an installed skill
4165
+
3901
4166
  ${BOLD}Project:${RESET}
3902
4167
  experimental_install Restore skills from skills-lock.json
3903
4168
  init [name] Initialize a skill (creates <name>/SKILL.md or ./SKILL.md)
@@ -4318,6 +4583,12 @@ async function main() {
4318
4583
  await runSync(restArgs, syncOptions);
4319
4584
  break;
4320
4585
  }
4586
+ case "deps":
4587
+ await runDeps(restArgs);
4588
+ break;
4589
+ case "run-scripts":
4590
+ await runRunScripts(restArgs);
4591
+ break;
4321
4592
  case "list":
4322
4593
  case "ls":
4323
4594
  await runList(restArgs);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skpm-cli",
3
- "version": "1.4.7",
3
+ "version": "1.4.8",
4
4
  "description": "The Skill Package Manager for AI Agents",
5
5
  "type": "module",
6
6
  "bin": {