@biggora/claude-plugins 1.2.2 → 1.3.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.
Files changed (85) hide show
  1. package/README.md +5 -1
  2. package/package.json +1 -1
  3. package/registry/registry.json +15 -0
  4. package/specs/coding.md +11 -0
  5. package/src/commands/skills/add.js +115 -31
  6. package/src/commands/skills/list.js +25 -52
  7. package/src/commands/skills/remove.js +45 -27
  8. package/src/commands/skills/resolve.js +104 -0
  9. package/src/commands/skills/update.js +58 -74
  10. package/src/config.js +5 -0
  11. package/src/skills/nest-best-practices/SKILL.md +251 -0
  12. package/src/skills/nest-best-practices/references/best-practices-request-lifecycle.md +158 -0
  13. package/src/skills/nest-best-practices/references/cli-monorepo.md +106 -0
  14. package/src/skills/nest-best-practices/references/cli-overview.md +157 -0
  15. package/src/skills/nest-best-practices/references/core-controllers.md +165 -0
  16. package/src/skills/nest-best-practices/references/core-dependency-injection.md +179 -0
  17. package/src/skills/nest-best-practices/references/core-middleware.md +139 -0
  18. package/src/skills/nest-best-practices/references/core-modules.md +138 -0
  19. package/src/skills/nest-best-practices/references/core-providers.md +188 -0
  20. package/src/skills/nest-best-practices/references/faq-raw-body-hybrid.md +122 -0
  21. package/src/skills/nest-best-practices/references/fundamentals-circular-dependency.md +89 -0
  22. package/src/skills/nest-best-practices/references/fundamentals-custom-decorators.md +107 -0
  23. package/src/skills/nest-best-practices/references/fundamentals-dynamic-modules.md +125 -0
  24. package/src/skills/nest-best-practices/references/fundamentals-exception-filters.md +202 -0
  25. package/src/skills/nest-best-practices/references/fundamentals-execution-context.md +107 -0
  26. package/src/skills/nest-best-practices/references/fundamentals-guards.md +136 -0
  27. package/src/skills/nest-best-practices/references/fundamentals-interceptors.md +187 -0
  28. package/src/skills/nest-best-practices/references/fundamentals-lazy-loading.md +89 -0
  29. package/src/skills/nest-best-practices/references/fundamentals-lifecycle-events.md +87 -0
  30. package/src/skills/nest-best-practices/references/fundamentals-module-reference.md +107 -0
  31. package/src/skills/nest-best-practices/references/fundamentals-pipes.md +197 -0
  32. package/src/skills/nest-best-practices/references/fundamentals-provider-scopes.md +92 -0
  33. package/src/skills/nest-best-practices/references/fundamentals-testing.md +142 -0
  34. package/src/skills/nest-best-practices/references/graphql-overview.md +233 -0
  35. package/src/skills/nest-best-practices/references/graphql-resolvers-mutations.md +199 -0
  36. package/src/skills/nest-best-practices/references/graphql-scalars-unions-enums.md +180 -0
  37. package/src/skills/nest-best-practices/references/graphql-subscriptions.md +228 -0
  38. package/src/skills/nest-best-practices/references/microservices-grpc.md +175 -0
  39. package/src/skills/nest-best-practices/references/microservices-overview.md +221 -0
  40. package/src/skills/nest-best-practices/references/microservices-transports.md +119 -0
  41. package/src/skills/nest-best-practices/references/openapi-swagger.md +207 -0
  42. package/src/skills/nest-best-practices/references/recipes-authentication.md +97 -0
  43. package/src/skills/nest-best-practices/references/recipes-cqrs.md +176 -0
  44. package/src/skills/nest-best-practices/references/recipes-crud-generator.md +87 -0
  45. package/src/skills/nest-best-practices/references/recipes-documentation.md +93 -0
  46. package/src/skills/nest-best-practices/references/recipes-mongoose.md +153 -0
  47. package/src/skills/nest-best-practices/references/recipes-prisma.md +98 -0
  48. package/src/skills/nest-best-practices/references/recipes-terminus.md +148 -0
  49. package/src/skills/nest-best-practices/references/recipes-typeorm.md +122 -0
  50. package/src/skills/nest-best-practices/references/security-authorization.md +196 -0
  51. package/src/skills/nest-best-practices/references/security-cors-helmet-rate-limiting.md +204 -0
  52. package/src/skills/nest-best-practices/references/security-encryption-hashing.md +93 -0
  53. package/src/skills/nest-best-practices/references/techniques-caching.md +142 -0
  54. package/src/skills/nest-best-practices/references/techniques-compression-streaming-sse.md +194 -0
  55. package/src/skills/nest-best-practices/references/techniques-configuration.md +132 -0
  56. package/src/skills/nest-best-practices/references/techniques-database.md +153 -0
  57. package/src/skills/nest-best-practices/references/techniques-events.md +163 -0
  58. package/src/skills/nest-best-practices/references/techniques-fastify.md +137 -0
  59. package/src/skills/nest-best-practices/references/techniques-file-upload.md +140 -0
  60. package/src/skills/nest-best-practices/references/techniques-http-module.md +176 -0
  61. package/src/skills/nest-best-practices/references/techniques-logging.md +146 -0
  62. package/src/skills/nest-best-practices/references/techniques-mvc-serve-static.md +132 -0
  63. package/src/skills/nest-best-practices/references/techniques-queues.md +162 -0
  64. package/src/skills/nest-best-practices/references/techniques-serialization.md +158 -0
  65. package/src/skills/nest-best-practices/references/techniques-sessions-cookies.md +167 -0
  66. package/src/skills/nest-best-practices/references/techniques-task-scheduling.md +166 -0
  67. package/src/skills/nest-best-practices/references/techniques-validation.md +126 -0
  68. package/src/skills/nest-best-practices/references/techniques-versioning.md +153 -0
  69. package/src/skills/nest-best-practices/references/websockets-advanced.md +96 -0
  70. package/src/skills/nest-best-practices/references/websockets-gateways.md +215 -0
  71. package/src/skills/typescript-expert/SKILL.md +145 -0
  72. package/src/skills/typescript-expert/commands/typescript-fix.md +65 -0
  73. package/src/skills/typescript-expert/references/advanced-conditional-types.md +190 -0
  74. package/src/skills/typescript-expert/references/advanced-decorators.md +243 -0
  75. package/src/skills/typescript-expert/references/advanced-mapped-types.md +223 -0
  76. package/src/skills/typescript-expert/references/advanced-template-literals.md +209 -0
  77. package/src/skills/typescript-expert/references/advanced-type-guards.md +308 -0
  78. package/src/skills/typescript-expert/references/best-practices-patterns.md +313 -0
  79. package/src/skills/typescript-expert/references/best-practices-performance.md +185 -0
  80. package/src/skills/typescript-expert/references/best-practices-tsconfig.md +242 -0
  81. package/src/skills/typescript-expert/references/core-generics.md +246 -0
  82. package/src/skills/typescript-expert/references/core-interfaces-types.md +231 -0
  83. package/src/skills/typescript-expert/references/core-type-system.md +261 -0
  84. package/src/skills/typescript-expert/references/core-utility-types.md +235 -0
  85. package/src/skills/typescript-expert/references/features-ts5x.md +370 -0
package/README.md CHANGED
@@ -67,6 +67,7 @@ claude-plugins skills remove commafeed-api
67
67
  | `vite-best-practices` | frontend | Vite 8 build tool best practices — configuration, Rolldown/Oxc migration, plugin API, SSR, library mode, and virtual modules |
68
68
  | `google-merchant-api` | workflow | Work with Google Merchant Center APIs — Merchant API (v1) and Content API for Shopping (v2.1) for products, inventory, promotions, and reports |
69
69
  | `lv-aggregators-api` | workflow | Generate and manage XML product feeds for Latvian price comparison platforms — Salidzini.lv and KurPirkt.lv integration |
70
+ | `typescript-expert` | code-quality | TypeScript type system, generics, utility types, advanced patterns, tsconfig, and TS 5.x features — includes `/typescript-fix` slash command |
70
71
  | `youtube-thumbnail` | other | Generates professional YouTube thumbnails in 11 strategic styles with auto-detection of AI image backends and Pillow compositing |
71
72
 
72
73
  #### Install Examples
@@ -90,6 +91,7 @@ npx skills add https://github.com/biggora/claude-plugins-registry --skill tailwi
90
91
  npx skills add https://github.com/biggora/claude-plugins-registry --skill vite-best-practices
91
92
  npx skills add https://github.com/biggora/claude-plugins-registry --skill google-merchant-api
92
93
  npx skills add https://github.com/biggora/claude-plugins-registry --skill lv-aggregators-api
94
+ npx skills add https://github.com/biggora/claude-plugins-registry --skill typescript-expert
93
95
  ```
94
96
 
95
97
  ## Plugins
@@ -224,6 +226,7 @@ Skills are simpler than plugins — just a SKILL.md file with optional supportin
224
226
  ```
225
227
  my-skill/
226
228
  SKILL.md # Required: skill instructions with YAML frontmatter
229
+ commands/ # Slash commands (optional, copied to ~/.claude/commands/ on install)
227
230
  references/ # Reference docs (optional)
228
231
  scripts/ # Helper scripts (optional)
229
232
  ```
@@ -260,8 +263,9 @@ The plugin registry is a GitHub-hosted JSON file at [biggora/claude-plugins-regi
260
263
 
261
264
  - **Plugins** are installed via `git clone` to `~/.claude/plugins/<name>`
262
265
  - **Skills** are installed via `git clone` + copy to `~/.claude/skills/<name>`
266
+ - **Slash commands** bundled with skills are automatically copied to `~/.claude/commands/`
263
267
  - Updates use `git pull --ff-only` to safely fast-forward
264
- - Claude Code automatically discovers plugins and skills in `~/.claude/`
268
+ - Claude Code automatically discovers plugins in `~/.claude/plugins/`, skills in `~/.claude/skills/`, and commands in `~/.claude/commands/`
265
269
  - Restart Claude Code after installing or removing plugins/skills
266
270
 
267
271
  ## Requirements
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biggora/claude-plugins",
3
- "version": "1.2.2",
3
+ "version": "1.3.1",
4
4
  "description": "CLI marketplace for discovering, installing, and managing Claude Code plugins",
5
5
  "keywords": [
6
6
  "claude",
@@ -314,6 +314,21 @@
314
314
  "commands": [],
315
315
  "category": "workflow",
316
316
  "type": "skill"
317
+ },
318
+ {
319
+ "name": "typescript-expert",
320
+ "version": "1.0.0",
321
+ "description": "TypeScript language expertise — type system, generics, utility types, advanced type patterns, tsconfig configuration, and TS 5.x features with /typescript-fix slash command for auto-resolving type errors",
322
+ "author": {
323
+ "name": "biggora",
324
+ "url": "https://github.com/biggora"
325
+ },
326
+ "repository": "https://github.com/biggora/claude-plugins-registry",
327
+ "keywords": ["typescript", "types", "generics", "tsconfig", "type-checking", "code-quality"],
328
+ "license": "MIT",
329
+ "commands": ["/typescript-fix"],
330
+ "category": "code-quality",
331
+ "type": "skill"
317
332
  }
318
333
  ]
319
334
  }
package/specs/coding.md CHANGED
@@ -13,6 +13,7 @@ salidzini + kurpirkt
13
13
  https://www.salidzini.lv/shops_info.php
14
14
  https://www.kurpirkt.lv/veikaliem.php
15
15
 
16
+ nest--best-practices
16
17
 
17
18
  add vite-best-practices, tailwindcss-best-practices skills to registry/registry.json and README.md
18
19
 
@@ -20,5 +21,15 @@ create skill google-merchant-api in src/skills/google-merchant-api. The skill sh
20
21
 
21
22
  create skill lv-aggregators-api in src/skills/lv-aggregators-api. The skill should include knowledge of how to work with Aggregators APIs, how to integrate them into projects, formats and data types, etc. Aggregators Docs: https://www.salidzini.lv/shops_info.php, https://www.kurpirkt.lv/veikaliem.php
22
23
 
24
+ create skill typescript-expert src/skills/typescript-expert. The skill should include knowledge of TypeScript language features, best practices, type system, advanced types, generics, utility types, and how to use TypeScript effectively in projects.
23
25
 
26
+ create skill eslint-expert src/skills/eslint-expert. The skill should include knowledge of ESLint configuration, rules, plugins, and how to use ESLint effectively in projects for code quality and consistency.
24
27
 
28
+ create slash command typescript-fix for the TypeScript expert (typescript-expert) skill. with some content like this:
29
+ "You are a TypeScript expert with deep knowledge of complex typing patterns, modern JavaScript (ES6+), ESLint configuration, and enterprise-grade development practices. You specialize in building reliable, maintainable, and performant TypeScript solutions for complex business problems, with a solid understanding of JavaScript fundamentals and code quality tools. Read package.json to identify the command to run the linter. Use the `typescript-expert` skill to run `npm run typecheck`, `pnpm run typecheck`, or `tsc --noEmit` in the project directory and resolve TypeScript errors."
30
+
31
+ the slash command is copied to the same skill subfolder ~/.claude/skills/typescript-expert/commands/typescript-fix.md, but must be copied to ~/.claude/commands/typescript-fix.md
32
+
33
+
34
+ try this command npx skills add ./ --skill typescript-expert and check C:\Users\biggora\.claude\commands. Looks
35
+ like it does not work
@@ -1,10 +1,11 @@
1
1
  import { execFileSync } from 'node:child_process';
2
- import { existsSync, readFileSync, readdirSync, cpSync, rmSync, mkdirSync, writeFileSync, statSync } from 'node:fs';
3
- import { join, basename } from 'node:path';
2
+ import { existsSync, readFileSync, readdirSync, cpSync, rmSync, mkdirSync, writeFileSync, statSync, copyFileSync } from 'node:fs';
3
+ import { join, basename, resolve } from 'node:path';
4
4
  import { tmpdir } from 'node:os';
5
- import { getSkillsDir } from '../../config.js';
5
+ import { getSkillsDir, getCommandsDir } from '../../config.js';
6
6
  import { fetchRegistry, findPlugin } from '../../registry.js';
7
7
  import { log, spinner } from '../../utils.js';
8
+ import { detectComponents, findInstalledSkill } from './resolve.js';
8
9
 
9
10
  export function parseFrontmatter(content) {
10
11
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
@@ -48,6 +49,15 @@ function isUrl(str) {
48
49
  return str.startsWith('http://') || str.startsWith('https://') || str.startsWith('git@');
49
50
  }
50
51
 
52
+ function isLocalPath(str) {
53
+ if (str.startsWith('./') || str.startsWith('../') || str.startsWith('/')) return true;
54
+ // Windows absolute paths like C:\ or E:\
55
+ if (/^[a-zA-Z]:[/\\]/.test(str)) return true;
56
+ // Explicit . for current directory
57
+ if (str === '.') return true;
58
+ return false;
59
+ }
60
+
51
61
  function makeTempDir() {
52
62
  const dir = join(tmpdir(), `claude-skill-${Date.now()}`);
53
63
  mkdirSync(dir, { recursive: true });
@@ -56,9 +66,19 @@ function makeTempDir() {
56
66
 
57
67
  export async function add(source, options = {}) {
58
68
  let repoUrl = source;
69
+ let sourceDir = null;
59
70
 
60
- // If not a URL, look up in registry
61
- if (!isUrl(source)) {
71
+ if (isLocalPath(source)) {
72
+ // Local directory — use directly, no git clone
73
+ sourceDir = resolve(source);
74
+ if (!existsSync(sourceDir)) {
75
+ log.error(`Local path "${sourceDir}" does not exist`);
76
+ process.exit(1);
77
+ }
78
+ repoUrl = sourceDir;
79
+ log.info(`Using local path: ${sourceDir}`);
80
+ } else if (!isUrl(source)) {
81
+ // Not a URL and not a local path — look up in registry
62
82
  const registry = await fetchRegistry();
63
83
  const entry = findPlugin(registry, source);
64
84
  if (!entry) {
@@ -70,29 +90,36 @@ export async function add(source, options = {}) {
70
90
  log.info(`Resolved "${source}" to ${repoUrl}`);
71
91
  }
72
92
 
73
- const tmpDir = makeTempDir();
74
- const spin = spinner(`Cloning ${repoUrl}...`);
75
- spin.start();
93
+ let tmpDir = null;
76
94
 
77
- try {
78
- const gitUrl = repoUrl.endsWith('.git') ? repoUrl : `${repoUrl}.git`;
79
- execFileSync('git', ['clone', '--depth', '1', gitUrl, tmpDir], {
80
- stdio: 'pipe',
81
- });
82
- spin.succeed('Repository cloned');
83
- } catch (err) {
84
- spin.fail('Failed to clone repository');
85
- log.error(err.message);
86
- rmSync(tmpDir, { recursive: true, force: true });
87
- process.exit(1);
95
+ // Clone if remote URL, otherwise use local path directly
96
+ if (!sourceDir) {
97
+ tmpDir = makeTempDir();
98
+ const spin = spinner(`Cloning ${repoUrl}...`);
99
+ spin.start();
100
+
101
+ try {
102
+ const gitUrl = repoUrl.endsWith('.git') ? repoUrl : `${repoUrl}.git`;
103
+ execFileSync('git', ['clone', '--depth', '1', gitUrl, tmpDir], {
104
+ stdio: 'pipe',
105
+ });
106
+ spin.succeed('Repository cloned');
107
+ } catch (err) {
108
+ spin.fail('Failed to clone repository');
109
+ log.error(err.message);
110
+ rmSync(tmpDir, { recursive: true, force: true });
111
+ process.exit(1);
112
+ }
88
113
  }
89
114
 
115
+ const searchRoot = sourceDir || tmpDir;
116
+
90
117
  // Find all skill directories
91
- const skillDirs = findSkillDirs(tmpDir);
118
+ const skillDirs = findSkillDirs(searchRoot);
92
119
 
93
120
  if (!skillDirs.length) {
94
121
  log.error('No SKILL.md found in repository');
95
- rmSync(tmpDir, { recursive: true, force: true });
122
+ if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });
96
123
  process.exit(1);
97
124
  }
98
125
 
@@ -117,22 +144,22 @@ export async function add(source, options = {}) {
117
144
  const fm = parseFrontmatter(readFileSync(join(d, 'SKILL.md'), 'utf-8'));
118
145
  log.dim(` - ${fm.name || basename(d)}`);
119
146
  }
120
- rmSync(tmpDir, { recursive: true, force: true });
147
+ if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });
121
148
  process.exit(1);
122
149
  }
123
150
  } else if (skillDirs.length === 1) {
124
151
  targetDir = skillDirs[0];
125
152
  } else {
126
153
  // Check if root has SKILL.md
127
- if (existsSync(join(tmpDir, 'SKILL.md'))) {
128
- targetDir = tmpDir;
154
+ if (existsSync(join(searchRoot, 'SKILL.md'))) {
155
+ targetDir = searchRoot;
129
156
  } else {
130
157
  log.warn('Multiple skills found. Use --skill <name> to select one:');
131
158
  for (const d of skillDirs) {
132
159
  const fm = parseFrontmatter(readFileSync(join(d, 'SKILL.md'), 'utf-8'));
133
160
  log.dim(` - ${fm.name || basename(d)}`);
134
161
  }
135
- rmSync(tmpDir, { recursive: true, force: true });
162
+ if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });
136
163
  process.exit(1);
137
164
  }
138
165
  }
@@ -140,19 +167,29 @@ export async function add(source, options = {}) {
140
167
  // Parse skill metadata
141
168
  const skillMd = readFileSync(join(targetDir, 'SKILL.md'), 'utf-8');
142
169
  const frontmatter = parseFrontmatter(skillMd);
143
- const skillName = frontmatter.name || options.skill || basename(targetDir === tmpDir ? repoUrl.replace(/\.git$/, '').split('/').pop() : targetDir);
170
+ const skillName = frontmatter.name || options.skill || basename(targetDir === searchRoot ? repoUrl.replace(/\.git$/, '').split('/').pop() : targetDir);
171
+
172
+ // Detect components alongside SKILL.md
173
+ const components = detectComponents(targetDir);
144
174
 
145
175
  const skillsDir = getSkillsDir();
146
176
  const dest = join(skillsDir, skillName);
147
177
 
148
- if (existsSync(dest)) {
149
- log.warn(`Skill "${skillName}" is already installed at ${dest}`);
178
+ // Check both locations for existing installation
179
+ const existing = findInstalledSkill(skillName);
180
+ if (existing) {
181
+ log.warn(`Skill "${skillName}" is already installed at ${existing.dir}`);
150
182
  log.dim(`Run "claude-plugins skills update ${skillName}" to update it`);
151
- rmSync(tmpDir, { recursive: true, force: true });
183
+ if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });
184
+ return;
185
+ }
186
+
187
+ if (existsSync(dest)) {
188
+ log.warn(`"${skillName}" already exists at ${dest}`);
189
+ if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });
152
190
  return;
153
191
  }
154
192
 
155
- // Copy skill files
156
193
  const spin2 = spinner(`Installing skill "${skillName}"...`);
157
194
  spin2.start();
158
195
 
@@ -165,6 +202,9 @@ export async function add(source, options = {}) {
165
202
  rmSync(gitInDest, { recursive: true, force: true });
166
203
  }
167
204
 
205
+ // Copy slash commands to ~/.claude/commands/ so Claude Code discovers them
206
+ const installedCommands = installCommands(dest, skillName);
207
+
168
208
  // Write origin metadata
169
209
  writeFileSync(
170
210
  join(dest, '.origin.json'),
@@ -172,6 +212,8 @@ export async function add(source, options = {}) {
172
212
  {
173
213
  repository: repoUrl,
174
214
  skill: options.skill || null,
215
+ components,
216
+ installedCommands,
175
217
  installedAt: new Date().toISOString(),
176
218
  },
177
219
  null,
@@ -184,11 +226,53 @@ export async function add(source, options = {}) {
184
226
  if (frontmatter.description) {
185
227
  log.dim(` ${frontmatter.description}`);
186
228
  }
229
+
230
+ if (installedCommands.length) {
231
+ log.info(`Commands: ${installedCommands.map((c) => `/${c}`).join(', ')}`);
232
+ log.dim(` Copied to ${getCommandsDir()}`);
233
+ }
234
+
235
+ if (components.length > 1) {
236
+ log.info(`Detected: ${components.join(', ')}`);
237
+ }
238
+
187
239
  log.dim('\nRestart Claude Code to load the skill.');
188
240
  } catch (err) {
189
241
  spin2.fail(`Failed to install skill "${skillName}"`);
190
242
  log.error(err.message);
191
243
  } finally {
192
- rmSync(tmpDir, { recursive: true, force: true });
244
+ if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Copy command .md files from skill's commands/ dir to ~/.claude/commands/.
250
+ * Returns array of installed command names (without extension).
251
+ */
252
+ function installCommands(skillDir, skillName) {
253
+ const cmdsDir = join(skillDir, 'commands');
254
+ if (!existsSync(cmdsDir)) return [];
255
+
256
+ const commandsDir = getCommandsDir();
257
+ const installed = [];
258
+
259
+ try {
260
+ const files = readdirSync(cmdsDir).filter((f) => f.endsWith('.md'));
261
+ for (const file of files) {
262
+ const src = join(cmdsDir, file);
263
+ const destFile = join(commandsDir, file);
264
+
265
+ if (existsSync(destFile)) {
266
+ log.warn(`Command "${file}" already exists in ${commandsDir}, skipping`);
267
+ continue;
268
+ }
269
+
270
+ copyFileSync(src, destFile);
271
+ installed.push(file.replace(/\.md$/, ''));
272
+ }
273
+ } catch {
274
+ // ignore errors reading commands dir
193
275
  }
276
+
277
+ return installed;
194
278
  }
@@ -1,52 +1,25 @@
1
- import { readdirSync, existsSync, readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import chalk from 'chalk';
4
- import { getSkillsDir } from '../../config.js';
5
- import { log, formatTable, truncate } from '../../utils.js';
6
- import { parseFrontmatter } from './add.js';
7
-
8
- export async function list() {
9
- const skillsDir = getSkillsDir();
10
- const entries = readdirSync(skillsDir, { withFileTypes: true }).filter(
11
- (e) => e.isDirectory()
12
- );
13
-
14
- if (!entries.length) {
15
- log.info('No skills installed');
16
- log.dim('Run "claude-plugins skills add <source>" to install a skill');
17
- return;
18
- }
19
-
20
- console.log(chalk.bold(`\n ${entries.length} skill${entries.length === 1 ? '' : 's'} installed\n`));
21
-
22
- const rows = entries.map((entry) => {
23
- const dir = join(skillsDir, entry.name);
24
- const skillMdPath = join(dir, 'SKILL.md');
25
- const originPath = join(dir, '.origin.json');
26
- let description = '';
27
- let repo = '-';
28
-
29
- if (existsSync(skillMdPath)) {
30
- try {
31
- const fm = parseFrontmatter(readFileSync(skillMdPath, 'utf-8'));
32
- description = fm.description || '';
33
- } catch {
34
- // ignore
35
- }
36
- }
37
-
38
- if (existsSync(originPath)) {
39
- try {
40
- const origin = JSON.parse(readFileSync(originPath, 'utf-8'));
41
- repo = origin.repository || '-';
42
- } catch {
43
- // ignore
44
- }
45
- }
46
-
47
- return [entry.name, truncate(description, 50), truncate(repo, 40)];
48
- });
49
-
50
- formatTable(rows, ['Name', 'Description', 'Repository']);
51
- console.log();
52
- }
1
+ import chalk from 'chalk';
2
+ import { log, formatTable, truncate } from '../../utils.js';
3
+ import { listAllSkills } from './resolve.js';
4
+
5
+ export async function list() {
6
+ const skills = listAllSkills();
7
+
8
+ if (!skills.length) {
9
+ log.info('No skills installed');
10
+ log.dim('Run "claude-plugins skills add <source>" to install a skill');
11
+ return;
12
+ }
13
+
14
+ console.log(chalk.bold(`\n ${skills.length} skill${skills.length === 1 ? '' : 's'} installed\n`));
15
+
16
+ const rows = skills.map(({ name, meta }) => {
17
+ const cmds = meta.commands.length
18
+ ? meta.commands.map((c) => `/${c}`).join(', ')
19
+ : '';
20
+ return [name, truncate(meta.description, 45), cmds, truncate(meta.repository, 35)];
21
+ });
22
+
23
+ formatTable(rows, ['Name', 'Description', 'Commands', 'Repository']);
24
+ console.log();
25
+ }
@@ -1,27 +1,45 @@
1
- import { existsSync, rmSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { getSkillsDir } from '../../config.js';
4
- import { log, spinner } from '../../utils.js';
5
-
6
- export async function remove(name) {
7
- const dest = join(getSkillsDir(), name);
8
-
9
- if (!existsSync(dest)) {
10
- log.error(`Skill "${name}" is not installed`);
11
- log.dim('Run "claude-plugins skills list" to see installed skills');
12
- process.exit(1);
13
- }
14
-
15
- const spin = spinner(`Removing skill "${name}"...`);
16
- spin.start();
17
-
18
- try {
19
- rmSync(dest, { recursive: true, force: true });
20
- spin.succeed(`Removed skill "${name}"`);
21
- log.dim('Restart Claude Code to apply changes.');
22
- } catch (err) {
23
- spin.fail(`Failed to remove skill "${name}"`);
24
- log.error(err.message);
25
- process.exit(1);
26
- }
27
- }
1
+ import { existsSync, rmSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { getCommandsDir } from '../../config.js';
4
+ import { log, spinner } from '../../utils.js';
5
+ import { findInstalledSkill, readOrigin } from './resolve.js';
6
+
7
+ export async function remove(name) {
8
+ const found = findInstalledSkill(name);
9
+
10
+ if (!found) {
11
+ log.error(`Skill "${name}" is not installed`);
12
+ log.dim('Run "claude-plugins skills list" to see installed skills');
13
+ process.exit(1);
14
+ }
15
+
16
+ const spin = spinner(`Removing skill "${name}"...`);
17
+ spin.start();
18
+
19
+ try {
20
+ // Clean up commands that were copied to ~/.claude/commands/
21
+ const origin = readOrigin(found.dir);
22
+ if (origin && origin.installedCommands?.length) {
23
+ const commandsDir = getCommandsDir();
24
+ for (const cmd of origin.installedCommands) {
25
+ const cmdFile = join(commandsDir, `${cmd}.md`);
26
+ if (existsSync(cmdFile)) {
27
+ rmSync(cmdFile);
28
+ }
29
+ }
30
+ }
31
+
32
+ rmSync(found.dir, { recursive: true, force: true });
33
+ spin.succeed(`Removed skill "${name}"`);
34
+
35
+ if (origin?.installedCommands?.length) {
36
+ log.dim(` Also removed commands: ${origin.installedCommands.map((c) => `/${c}`).join(', ')}`);
37
+ }
38
+
39
+ log.dim('Restart Claude Code to apply changes.');
40
+ } catch (err) {
41
+ spin.fail(`Failed to remove skill "${name}"`);
42
+ log.error(err.message);
43
+ process.exit(1);
44
+ }
45
+ }
@@ -0,0 +1,104 @@
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { getSkillsDir } from '../../config.js';
4
+ import { parseFrontmatter } from './add.js';
5
+
6
+ /**
7
+ * Component directories that can accompany a SKILL.md.
8
+ */
9
+ export const COMPONENT_DIRS = ['commands', 'hooks', 'agents'];
10
+
11
+ /**
12
+ * Detect which components exist in a directory.
13
+ * Returns an array of found component names.
14
+ */
15
+ export function detectComponents(dir) {
16
+ const found = [];
17
+ if (existsSync(join(dir, 'SKILL.md'))) found.push('skill');
18
+ for (const name of COMPONENT_DIRS) {
19
+ if (existsSync(join(dir, name))) found.push(name);
20
+ }
21
+ return found;
22
+ }
23
+
24
+ /**
25
+ * Read .origin.json from a skill directory.
26
+ */
27
+ export function readOrigin(dir) {
28
+ const originPath = join(dir, '.origin.json');
29
+ if (!existsSync(originPath)) return null;
30
+ try {
31
+ return JSON.parse(readFileSync(originPath, 'utf-8'));
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Read skill metadata from a directory (SKILL.md frontmatter + .origin.json).
39
+ */
40
+ export function readSkillMeta(dir, dirName) {
41
+ const skillMdPath = join(dir, 'SKILL.md');
42
+ const meta = { name: dirName, description: '', repository: '-', commands: [] };
43
+
44
+ if (existsSync(skillMdPath)) {
45
+ try {
46
+ const fm = parseFrontmatter(readFileSync(skillMdPath, 'utf-8'));
47
+ meta.name = fm.name || dirName;
48
+ meta.description = fm.description || '';
49
+ } catch {
50
+ // ignore
51
+ }
52
+ }
53
+
54
+ const origin = readOrigin(dir);
55
+ if (origin) {
56
+ meta.repository = origin.repository || '-';
57
+ meta.skill = origin.skill || null;
58
+ meta.commands = origin.installedCommands || [];
59
+ }
60
+
61
+ return meta;
62
+ }
63
+
64
+ /**
65
+ * Find a skill by name in ~/.claude/skills/.
66
+ * Returns { dir, location } or null.
67
+ */
68
+ export function findInstalledSkill(name) {
69
+ const skillsDir = getSkillsDir();
70
+ const skillPath = join(skillsDir, name);
71
+
72
+ if (existsSync(skillPath) && existsSync(join(skillPath, 'SKILL.md'))) {
73
+ return { dir: skillPath, location: 'skills' };
74
+ }
75
+
76
+ return null;
77
+ }
78
+
79
+ /**
80
+ * List all installed skills from ~/.claude/skills/.
81
+ * Returns array of { name, dir, meta }.
82
+ */
83
+ export function listAllSkills() {
84
+ const skillsDir = getSkillsDir();
85
+ const results = [];
86
+
87
+ try {
88
+ const entries = readdirSync(skillsDir, { withFileTypes: true });
89
+ for (const entry of entries) {
90
+ if (!entry.isDirectory()) continue;
91
+ const dir = join(skillsDir, entry.name);
92
+ if (!existsSync(join(dir, 'SKILL.md'))) continue;
93
+ results.push({
94
+ name: entry.name,
95
+ dir,
96
+ meta: readSkillMeta(dir, entry.name),
97
+ });
98
+ }
99
+ } catch {
100
+ // directory might not exist yet
101
+ }
102
+
103
+ return results;
104
+ }