skpm-cli 1.4.6 → 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 +335 -51
  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
@@ -423,8 +423,8 @@ var GitCloneError = class extends Error {
423
423
  };
424
424
  async function cloneRepo(url, ref) {
425
425
  const tempDir = await mkdtemp(join(tmpdir(), "skpm-"));
426
- process.env.GIT_TERMINAL_PROMPT = "0";
427
426
  const git = esm_default({ timeout: { block: CLONE_TIMEOUT_MS } });
427
+ git.env("GIT_TERMINAL_PROMPT", "0");
428
428
  const cloneOptions = ref ? [
429
429
  "--depth",
430
430
  "1",
@@ -1900,7 +1900,7 @@ function createEmptyLocalLock() {
1900
1900
  skills: {}
1901
1901
  };
1902
1902
  }
1903
- var version$1 = "1.4.6";
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,24 +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
- await runAdd([depSource], {
2835
- ...options,
2836
- yes: true,
2837
- skill: skillName ? [skillName] : void 0
2838
- });
2839
- return [];
2840
- }, (warning) => M.warn(import_picocolors.default.yellow(warning)));
2839
+ const depParsed = parseSource(depSourceStr);
2840
+ let depDir;
2841
+ try {
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 });
2853
+ const targetSkills = skillName ? depSkills.filter((s) => s.name.toLowerCase() === skillName.toLowerCase()) : depSkills;
2854
+ if (targetSkills.length === 0) {
2855
+ M.warn(import_picocolors.default.yellow(` Dependency skill not found: ${depSource}`));
2856
+ return [];
2857
+ }
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
+ }
2892
+ return targetSkills;
2893
+ } catch (err) {
2894
+ throw err;
2895
+ }
2896
+ }, (warning) => M.warn(import_picocolors.default.yellow(warning)), sharedResolved);
2841
2897
  allPostInstallCommands.push(...depResult.postInstallCommands);
2842
2898
  if (depResult.errors.length > 0) for (const err of depResult.errors) M.warn(import_picocolors.default.yellow(`Dependency ${err.ref}: ${err.error}`));
2843
2899
  }
2844
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
+ }
2845
2907
  if (allPostInstallCommands.length > 0) {
2846
2908
  console.log();
2847
2909
  M.info("Post-install commands required:");
@@ -2954,10 +3016,10 @@ function parseAddOptions(args) {
2954
3016
  options
2955
3017
  };
2956
3018
  }
2957
- const RESET$2 = "\x1B[0m";
2958
- const BOLD$2 = "\x1B[1m";
2959
- const DIM$2 = "\x1B[38;5;102m";
2960
- 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";
2961
3023
  const CYAN$1 = "\x1B[36m";
2962
3024
  const SEARCH_API_BASE = process.env.SKPM_API_URL || "https://skpm.sh";
2963
3025
  function formatInstalls(count) {
@@ -3001,28 +3063,28 @@ async function runSearchPrompt(initialQuery = "") {
3001
3063
  if (lastRenderedLines > 0) process.stdout.write(MOVE_UP(lastRenderedLines) + MOVE_TO_COL(1));
3002
3064
  process.stdout.write(CLEAR_DOWN);
3003
3065
  const lines = [];
3004
- const cursor = `${BOLD$2}_${RESET$2}`;
3005
- 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}`);
3006
3068
  lines.push("");
3007
- if (!query || query.length < 2) lines.push(`${DIM$2}Start typing to search (min 2 chars)${RESET$2}`);
3008
- else if (results.length === 0 && loading) lines.push(`${DIM$2}Searching...${RESET$2}`);
3009
- 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}`);
3010
3072
  else {
3011
3073
  const visible = results.slice(0, 8);
3012
3074
  for (let i = 0; i < visible.length; i++) {
3013
3075
  const skill = visible[i];
3014
3076
  const isSelected = i === selectedIndex;
3015
- const arrow = isSelected ? `${BOLD$2}>${RESET$2}` : " ";
3016
- const name = isSelected ? `${BOLD$2}${skill.name}${RESET$2}` : `${TEXT$1}${skill.name}${RESET$2}`;
3017
- 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}` : "";
3018
3080
  const installs = formatInstalls(skill.installs);
3019
- const installsBadge = installs ? ` ${CYAN$1}${installs}${RESET$2}` : "";
3020
- 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}` : "";
3021
3083
  lines.push(` ${arrow} ${name}${source}${installsBadge}${loadingIndicator}`);
3022
3084
  }
3023
3085
  }
3024
3086
  lines.push("");
3025
- 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}`);
3026
3088
  for (const line of lines) process.stdout.write(line + "\n");
3027
3089
  lastRenderedLines = lines.length;
3028
3090
  }
@@ -3118,9 +3180,9 @@ async function isRepoPublic(owner, repo) {
3118
3180
  async function runFind(args) {
3119
3181
  const query = args.join(" ");
3120
3182
  const isNonInteractive = !process.stdin.isTTY;
3121
- const agentTip = `${DIM$2}Tip: if running in a coding agent, follow these steps:${RESET$2}
3122
- ${DIM$2} 1) npx skpm-cli find [query]${RESET$2}
3123
- ${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}`;
3124
3186
  if (query) {
3125
3187
  const results = await searchSkillsAPI(query);
3126
3188
  track({
@@ -3129,16 +3191,16 @@ ${DIM$2} 2) npx skpm-cli add <owner/repo@skill>${RESET$2}`;
3129
3191
  resultCount: String(results.length)
3130
3192
  });
3131
3193
  if (results.length === 0) {
3132
- console.log(`${DIM$2}No skills found for "${query}"${RESET$2}`);
3194
+ console.log(`${DIM$4}No skills found for "${query}"${RESET$4}`);
3133
3195
  return;
3134
3196
  }
3135
- 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>`);
3136
3198
  console.log();
3137
3199
  for (const skill of results.slice(0, 6)) {
3138
3200
  const pkg = skill.source || skill.slug;
3139
3201
  const installs = formatInstalls(skill.installs);
3140
- console.log(`${TEXT$1}${pkg}@${skill.name}${RESET$2}${installs ? ` ${CYAN$1}${installs}${RESET$2}` : ""}`);
3141
- 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}`);
3142
3204
  console.log();
3143
3205
  }
3144
3206
  return;
@@ -3155,14 +3217,14 @@ ${DIM$2} 2) npx skpm-cli add <owner/repo@skill>${RESET$2}`;
3155
3217
  interactive: "1"
3156
3218
  });
3157
3219
  if (!selected) {
3158
- console.log(`${DIM$2}Search cancelled${RESET$2}`);
3220
+ console.log(`${DIM$4}Search cancelled${RESET$4}`);
3159
3221
  console.log();
3160
3222
  return;
3161
3223
  }
3162
3224
  const pkg = selected.source || selected.slug;
3163
3225
  const skillName = selected.name;
3164
3226
  console.log();
3165
- 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}...`);
3166
3228
  console.log();
3167
3229
  const { source, options } = parseAddOptions([
3168
3230
  pkg,
@@ -3172,8 +3234,8 @@ ${DIM$2} 2) npx skpm-cli add <owner/repo@skill>${RESET$2}`;
3172
3234
  await runAdd(source, options);
3173
3235
  console.log();
3174
3236
  const info = getOwnerRepoFromString(pkg);
3175
- 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}`);
3176
- 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}`);
3177
3239
  console.log();
3178
3240
  }
3179
3241
  const isCancelled = (value) => typeof value === "symbol";
@@ -3510,9 +3572,9 @@ async function runInstallFromLock(args) {
3510
3572
  }
3511
3573
  }
3512
3574
  }
3513
- const RESET$1 = "\x1B[0m";
3514
- const BOLD$1 = "\x1B[1m";
3515
- 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";
3516
3578
  const CYAN = "\x1B[36m";
3517
3579
  const YELLOW = "\x1B[33m";
3518
3580
  function shortenPath(fullPath, cwd) {
@@ -3548,8 +3610,8 @@ async function runList(args) {
3548
3610
  const validAgents = Object.keys(agents);
3549
3611
  const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
3550
3612
  if (invalidAgents.length > 0) {
3551
- console.log(`${YELLOW}Invalid agents: ${invalidAgents.join(", ")}${RESET$1}`);
3552
- 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}`);
3553
3615
  process.exit(1);
3554
3616
  }
3555
3617
  agentFilter = options.agent;
@@ -3576,20 +3638,20 @@ async function runList(args) {
3576
3638
  console.log("[]");
3577
3639
  return;
3578
3640
  }
3579
- console.log(`${DIM$1}No ${scopeLabel.toLowerCase()} skills found.${RESET$1}`);
3580
- if (scope) console.log(`${DIM$1}Try listing project skills without -g${RESET$1}`);
3581
- 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}`);
3582
3644
  return;
3583
3645
  }
3584
3646
  function printSkill(skill, indent = false) {
3585
3647
  const prefix = indent ? " " : "";
3586
3648
  const shortPath = shortenPath(skill.canonicalPath, cwd);
3587
3649
  const agentNames = skill.agents.map((a) => agents[a].displayName);
3588
- const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW}not linked${RESET$1}`;
3589
- console.log(`${prefix}${CYAN}${skill.name}${RESET$1} ${DIM$1}${shortPath}${RESET$1}`);
3590
- 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}`);
3591
3653
  }
3592
- console.log(`${BOLD$1}${scopeLabel} Skills${RESET$1}`);
3654
+ console.log(`${BOLD$3}${scopeLabel} Skills${RESET$3}`);
3593
3655
  console.log();
3594
3656
  const groupedSkills = {};
3595
3657
  const ungroupedSkills = [];
@@ -3605,13 +3667,13 @@ async function runList(args) {
3605
3667
  const sortedGroups = Object.keys(groupedSkills).sort();
3606
3668
  for (const group of sortedGroups) {
3607
3669
  const title = group.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
3608
- console.log(`${BOLD$1}${title}${RESET$1}`);
3670
+ console.log(`${BOLD$3}${title}${RESET$3}`);
3609
3671
  const skills = groupedSkills[group];
3610
3672
  if (skills) for (const skill of skills) printSkill(skill, true);
3611
3673
  console.log();
3612
3674
  }
3613
3675
  if (ungroupedSkills.length > 0) {
3614
- console.log(`${BOLD$1}General${RESET$1}`);
3676
+ console.log(`${BOLD$3}General${RESET$3}`);
3615
3677
  for (const skill of ungroupedSkills) printSkill(skill, true);
3616
3678
  console.log();
3617
3679
  }
@@ -3810,6 +3872,218 @@ function parseRemoveOptions(args) {
3810
3872
  options
3811
3873
  };
3812
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
+ }
3813
4087
  const __dirname = dirname(fileURLToPath(import.meta.url));
3814
4088
  function getVersion() {
3815
4089
  try {
@@ -3885,6 +4159,10 @@ ${BOLD}Updates:${RESET}
3885
4159
  check Check for available skill updates
3886
4160
  update Update all skills to latest versions
3887
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
+
3888
4166
  ${BOLD}Project:${RESET}
3889
4167
  experimental_install Restore skills from skills-lock.json
3890
4168
  init [name] Initialize a skill (creates <name>/SKILL.md or ./SKILL.md)
@@ -4305,6 +4583,12 @@ async function main() {
4305
4583
  await runSync(restArgs, syncOptions);
4306
4584
  break;
4307
4585
  }
4586
+ case "deps":
4587
+ await runDeps(restArgs);
4588
+ break;
4589
+ case "run-scripts":
4590
+ await runRunScripts(restArgs);
4591
+ break;
4308
4592
  case "list":
4309
4593
  case "ls":
4310
4594
  await runList(restArgs);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skpm-cli",
3
- "version": "1.4.6",
3
+ "version": "1.4.8",
4
4
  "description": "The Skill Package Manager for AI Agents",
5
5
  "type": "module",
6
6
  "bin": {