@biggora/claude-plugins 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -1
- package/registry/registry.json +15 -0
- package/specs/coding.md +6 -0
- package/src/commands/skills/add.js +63 -7
- package/src/commands/skills/list.js +23 -52
- package/src/commands/skills/remove.js +26 -27
- package/src/commands/skills/resolve.js +155 -0
- package/src/commands/skills/update.js +58 -74
- package/src/skills/nest-best-practices/SKILL.md +251 -0
- package/src/skills/nest-best-practices/references/best-practices-request-lifecycle.md +158 -0
- package/src/skills/nest-best-practices/references/cli-monorepo.md +106 -0
- package/src/skills/nest-best-practices/references/cli-overview.md +157 -0
- package/src/skills/nest-best-practices/references/core-controllers.md +165 -0
- package/src/skills/nest-best-practices/references/core-dependency-injection.md +179 -0
- package/src/skills/nest-best-practices/references/core-middleware.md +139 -0
- package/src/skills/nest-best-practices/references/core-modules.md +138 -0
- package/src/skills/nest-best-practices/references/core-providers.md +188 -0
- package/src/skills/nest-best-practices/references/faq-raw-body-hybrid.md +122 -0
- package/src/skills/nest-best-practices/references/fundamentals-circular-dependency.md +89 -0
- package/src/skills/nest-best-practices/references/fundamentals-custom-decorators.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-dynamic-modules.md +125 -0
- package/src/skills/nest-best-practices/references/fundamentals-exception-filters.md +202 -0
- package/src/skills/nest-best-practices/references/fundamentals-execution-context.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-guards.md +136 -0
- package/src/skills/nest-best-practices/references/fundamentals-interceptors.md +187 -0
- package/src/skills/nest-best-practices/references/fundamentals-lazy-loading.md +89 -0
- package/src/skills/nest-best-practices/references/fundamentals-lifecycle-events.md +87 -0
- package/src/skills/nest-best-practices/references/fundamentals-module-reference.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-pipes.md +197 -0
- package/src/skills/nest-best-practices/references/fundamentals-provider-scopes.md +92 -0
- package/src/skills/nest-best-practices/references/fundamentals-testing.md +142 -0
- package/src/skills/nest-best-practices/references/graphql-overview.md +233 -0
- package/src/skills/nest-best-practices/references/graphql-resolvers-mutations.md +199 -0
- package/src/skills/nest-best-practices/references/graphql-scalars-unions-enums.md +180 -0
- package/src/skills/nest-best-practices/references/graphql-subscriptions.md +228 -0
- package/src/skills/nest-best-practices/references/microservices-grpc.md +175 -0
- package/src/skills/nest-best-practices/references/microservices-overview.md +221 -0
- package/src/skills/nest-best-practices/references/microservices-transports.md +119 -0
- package/src/skills/nest-best-practices/references/openapi-swagger.md +207 -0
- package/src/skills/nest-best-practices/references/recipes-authentication.md +97 -0
- package/src/skills/nest-best-practices/references/recipes-cqrs.md +176 -0
- package/src/skills/nest-best-practices/references/recipes-crud-generator.md +87 -0
- package/src/skills/nest-best-practices/references/recipes-documentation.md +93 -0
- package/src/skills/nest-best-practices/references/recipes-mongoose.md +153 -0
- package/src/skills/nest-best-practices/references/recipes-prisma.md +98 -0
- package/src/skills/nest-best-practices/references/recipes-terminus.md +148 -0
- package/src/skills/nest-best-practices/references/recipes-typeorm.md +122 -0
- package/src/skills/nest-best-practices/references/security-authorization.md +196 -0
- package/src/skills/nest-best-practices/references/security-cors-helmet-rate-limiting.md +204 -0
- package/src/skills/nest-best-practices/references/security-encryption-hashing.md +93 -0
- package/src/skills/nest-best-practices/references/techniques-caching.md +142 -0
- package/src/skills/nest-best-practices/references/techniques-compression-streaming-sse.md +194 -0
- package/src/skills/nest-best-practices/references/techniques-configuration.md +132 -0
- package/src/skills/nest-best-practices/references/techniques-database.md +153 -0
- package/src/skills/nest-best-practices/references/techniques-events.md +163 -0
- package/src/skills/nest-best-practices/references/techniques-fastify.md +137 -0
- package/src/skills/nest-best-practices/references/techniques-file-upload.md +140 -0
- package/src/skills/nest-best-practices/references/techniques-http-module.md +176 -0
- package/src/skills/nest-best-practices/references/techniques-logging.md +146 -0
- package/src/skills/nest-best-practices/references/techniques-mvc-serve-static.md +132 -0
- package/src/skills/nest-best-practices/references/techniques-queues.md +162 -0
- package/src/skills/nest-best-practices/references/techniques-serialization.md +158 -0
- package/src/skills/nest-best-practices/references/techniques-sessions-cookies.md +167 -0
- package/src/skills/nest-best-practices/references/techniques-task-scheduling.md +166 -0
- package/src/skills/nest-best-practices/references/techniques-validation.md +126 -0
- package/src/skills/nest-best-practices/references/techniques-versioning.md +153 -0
- package/src/skills/nest-best-practices/references/websockets-advanced.md +96 -0
- package/src/skills/nest-best-practices/references/websockets-gateways.md +215 -0
- package/src/skills/typescript-expert/SKILL.md +145 -0
- package/src/skills/typescript-expert/commands/typescript-fix.md +65 -0
- package/src/skills/typescript-expert/references/advanced-conditional-types.md +190 -0
- package/src/skills/typescript-expert/references/advanced-decorators.md +243 -0
- package/src/skills/typescript-expert/references/advanced-mapped-types.md +223 -0
- package/src/skills/typescript-expert/references/advanced-template-literals.md +209 -0
- package/src/skills/typescript-expert/references/advanced-type-guards.md +308 -0
- package/src/skills/typescript-expert/references/best-practices-patterns.md +313 -0
- package/src/skills/typescript-expert/references/best-practices-performance.md +185 -0
- package/src/skills/typescript-expert/references/best-practices-tsconfig.md +242 -0
- package/src/skills/typescript-expert/references/core-generics.md +246 -0
- package/src/skills/typescript-expert/references/core-interfaces-types.md +231 -0
- package/src/skills/typescript-expert/references/core-type-system.md +261 -0
- package/src/skills/typescript-expert/references/core-utility-types.md +235 -0
- 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
|
package/package.json
CHANGED
package/registry/registry.json
CHANGED
|
@@ -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,10 @@ 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.
|
|
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."
|
|
24
30
|
|
|
@@ -2,9 +2,10 @@ import { execFileSync } from 'node:child_process';
|
|
|
2
2
|
import { existsSync, readFileSync, readdirSync, cpSync, rmSync, mkdirSync, writeFileSync, statSync } from 'node:fs';
|
|
3
3
|
import { join, basename } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
|
-
import { getSkillsDir } from '../../config.js';
|
|
5
|
+
import { getSkillsDir, getPluginsDir } from '../../config.js';
|
|
6
6
|
import { fetchRegistry, findPlugin } from '../../registry.js';
|
|
7
7
|
import { log, spinner } from '../../utils.js';
|
|
8
|
+
import { hasPluginComponents, 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---/);
|
|
@@ -142,18 +143,30 @@ export async function add(source, options = {}) {
|
|
|
142
143
|
const frontmatter = parseFrontmatter(skillMd);
|
|
143
144
|
const skillName = frontmatter.name || options.skill || basename(targetDir === tmpDir ? repoUrl.replace(/\.git$/, '').split('/').pop() : targetDir);
|
|
144
145
|
|
|
145
|
-
|
|
146
|
-
const
|
|
146
|
+
// Detect plugin components (commands, hooks, agents) alongside SKILL.md
|
|
147
|
+
const components = detectComponents(targetDir);
|
|
148
|
+
const installAsPlugin = hasPluginComponents(targetDir);
|
|
147
149
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
const destDir = installAsPlugin ? getPluginsDir() : getSkillsDir();
|
|
151
|
+
const dest = join(destDir, skillName);
|
|
152
|
+
|
|
153
|
+
// Check both locations for existing installation
|
|
154
|
+
const existing = findInstalledSkill(skillName);
|
|
155
|
+
if (existing) {
|
|
156
|
+
log.warn(`Skill "${skillName}" is already installed at ${existing.dir}`);
|
|
150
157
|
log.dim(`Run "claude-plugins skills update ${skillName}" to update it`);
|
|
151
158
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
152
159
|
return;
|
|
153
160
|
}
|
|
154
161
|
|
|
155
|
-
|
|
156
|
-
|
|
162
|
+
if (existsSync(dest)) {
|
|
163
|
+
log.warn(`"${skillName}" already exists at ${dest}`);
|
|
164
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const label = installAsPlugin ? 'skill + components' : 'skill';
|
|
169
|
+
const spin2 = spinner(`Installing ${label} "${skillName}"...`);
|
|
157
170
|
spin2.start();
|
|
158
171
|
|
|
159
172
|
try {
|
|
@@ -165,6 +178,25 @@ export async function add(source, options = {}) {
|
|
|
165
178
|
rmSync(gitInDest, { recursive: true, force: true });
|
|
166
179
|
}
|
|
167
180
|
|
|
181
|
+
// Generate .claude-plugin/plugin.json if installing as plugin and none exists
|
|
182
|
+
if (installAsPlugin && !existsSync(join(dest, '.claude-plugin', 'plugin.json'))) {
|
|
183
|
+
const pluginDir = join(dest, '.claude-plugin');
|
|
184
|
+
mkdirSync(pluginDir, { recursive: true });
|
|
185
|
+
|
|
186
|
+
const pluginJson = {
|
|
187
|
+
name: skillName,
|
|
188
|
+
version: '1.0.0',
|
|
189
|
+
description: frontmatter.description || `Skill: ${skillName}`,
|
|
190
|
+
commands: listCommandFiles(dest),
|
|
191
|
+
_generatedBy: 'claude-plugins skills add',
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
writeFileSync(
|
|
195
|
+
join(pluginDir, 'plugin.json'),
|
|
196
|
+
JSON.stringify(pluginJson, null, 2)
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
168
200
|
// Write origin metadata
|
|
169
201
|
writeFileSync(
|
|
170
202
|
join(dest, '.origin.json'),
|
|
@@ -172,6 +204,8 @@ export async function add(source, options = {}) {
|
|
|
172
204
|
{
|
|
173
205
|
repository: repoUrl,
|
|
174
206
|
skill: options.skill || null,
|
|
207
|
+
installedAs: installAsPlugin ? 'plugin-skill' : 'skill',
|
|
208
|
+
components,
|
|
175
209
|
installedAt: new Date().toISOString(),
|
|
176
210
|
},
|
|
177
211
|
null,
|
|
@@ -184,6 +218,13 @@ export async function add(source, options = {}) {
|
|
|
184
218
|
if (frontmatter.description) {
|
|
185
219
|
log.dim(` ${frontmatter.description}`);
|
|
186
220
|
}
|
|
221
|
+
|
|
222
|
+
if (installAsPlugin) {
|
|
223
|
+
const extras = components.filter((c) => c !== 'skill');
|
|
224
|
+
log.info(`Detected: ${components.join(', ')}`);
|
|
225
|
+
log.dim(`Installed as plugin so Claude Code discovers ${extras.join(', ')}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
187
228
|
log.dim('\nRestart Claude Code to load the skill.');
|
|
188
229
|
} catch (err) {
|
|
189
230
|
spin2.fail(`Failed to install skill "${skillName}"`);
|
|
@@ -192,3 +233,18 @@ export async function add(source, options = {}) {
|
|
|
192
233
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
193
234
|
}
|
|
194
235
|
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* List command file names (without extension) from a commands/ directory.
|
|
239
|
+
*/
|
|
240
|
+
function listCommandFiles(dir) {
|
|
241
|
+
const cmdsDir = join(dir, 'commands');
|
|
242
|
+
if (!existsSync(cmdsDir)) return [];
|
|
243
|
+
try {
|
|
244
|
+
return readdirSync(cmdsDir)
|
|
245
|
+
.filter((f) => f.endsWith('.md'))
|
|
246
|
+
.map((f) => `/${f.replace(/\.md$/, '')}`);
|
|
247
|
+
} catch {
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
}
|
|
@@ -1,52 +1,23 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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, location }) => {
|
|
17
|
+
const type = location === 'plugins' ? chalk.cyan('plugin') : chalk.dim('skill');
|
|
18
|
+
return [name, truncate(meta.description, 45), type, truncate(meta.repository, 35)];
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
formatTable(rows, ['Name', 'Description', 'Type', 'Repository']);
|
|
22
|
+
console.log();
|
|
23
|
+
}
|
|
@@ -1,27 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
log.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
1
|
+
import { rmSync } from 'node:fs';
|
|
2
|
+
import { log, spinner } from '../../utils.js';
|
|
3
|
+
import { findInstalledSkill } from './resolve.js';
|
|
4
|
+
|
|
5
|
+
export async function remove(name) {
|
|
6
|
+
const found = findInstalledSkill(name);
|
|
7
|
+
|
|
8
|
+
if (!found) {
|
|
9
|
+
log.error(`Skill "${name}" is not installed`);
|
|
10
|
+
log.dim('Run "claude-plugins skills list" to see installed skills');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const spin = spinner(`Removing skill "${name}"...`);
|
|
15
|
+
spin.start();
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
rmSync(found.dir, { recursive: true, force: true });
|
|
19
|
+
spin.succeed(`Removed skill "${name}" from ${found.location}`);
|
|
20
|
+
log.dim('Restart Claude Code to apply changes.');
|
|
21
|
+
} catch (err) {
|
|
22
|
+
spin.fail(`Failed to remove skill "${name}"`);
|
|
23
|
+
log.error(err.message);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { getSkillsDir, getPluginsDir } from '../../config.js';
|
|
4
|
+
import { parseFrontmatter } from './add.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Plugin component directories that Claude Code auto-discovers.
|
|
8
|
+
* When a skill repo contains any of these, it must be installed
|
|
9
|
+
* as a plugin so Claude Code can find them.
|
|
10
|
+
*/
|
|
11
|
+
export const PLUGIN_COMPONENT_DIRS = ['commands', 'hooks', 'agents'];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check if a directory contains plugin components (commands, hooks, agents)
|
|
15
|
+
* beyond just SKILL.md + references.
|
|
16
|
+
*/
|
|
17
|
+
export function hasPluginComponents(dir) {
|
|
18
|
+
return PLUGIN_COMPONENT_DIRS.some((name) => {
|
|
19
|
+
const p = join(dir, name);
|
|
20
|
+
return existsSync(p);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Detect which plugin components exist in a directory.
|
|
26
|
+
* Returns an array of found component names.
|
|
27
|
+
*/
|
|
28
|
+
export function detectComponents(dir) {
|
|
29
|
+
const found = [];
|
|
30
|
+
if (existsSync(join(dir, 'SKILL.md'))) found.push('skill');
|
|
31
|
+
for (const name of PLUGIN_COMPONENT_DIRS) {
|
|
32
|
+
if (existsSync(join(dir, name))) found.push(name);
|
|
33
|
+
}
|
|
34
|
+
return found;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Read .origin.json from a skill/plugin directory.
|
|
39
|
+
*/
|
|
40
|
+
export function readOrigin(dir) {
|
|
41
|
+
const originPath = join(dir, '.origin.json');
|
|
42
|
+
if (!existsSync(originPath)) return null;
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(readFileSync(originPath, 'utf-8'));
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Read skill metadata from a directory (SKILL.md frontmatter + .origin.json).
|
|
52
|
+
*/
|
|
53
|
+
export function readSkillMeta(dir, dirName) {
|
|
54
|
+
const skillMdPath = join(dir, 'SKILL.md');
|
|
55
|
+
const meta = { name: dirName, description: '', repository: '-' };
|
|
56
|
+
|
|
57
|
+
if (existsSync(skillMdPath)) {
|
|
58
|
+
try {
|
|
59
|
+
const fm = parseFrontmatter(readFileSync(skillMdPath, 'utf-8'));
|
|
60
|
+
meta.name = fm.name || dirName;
|
|
61
|
+
meta.description = fm.description || '';
|
|
62
|
+
} catch {
|
|
63
|
+
// ignore
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const origin = readOrigin(dir);
|
|
68
|
+
if (origin) {
|
|
69
|
+
meta.repository = origin.repository || '-';
|
|
70
|
+
meta.installedAs = origin.installedAs || 'skill';
|
|
71
|
+
meta.skill = origin.skill || null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return meta;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Find a skill by name across both ~/.claude/skills/ and ~/.claude/plugins/.
|
|
79
|
+
* Returns { dir, location } or null.
|
|
80
|
+
*/
|
|
81
|
+
export function findInstalledSkill(name) {
|
|
82
|
+
const skillsDir = getSkillsDir();
|
|
83
|
+
const pluginsDir = getPluginsDir();
|
|
84
|
+
|
|
85
|
+
// Check ~/.claude/skills/ first
|
|
86
|
+
const skillPath = join(skillsDir, name);
|
|
87
|
+
if (existsSync(skillPath) && existsSync(join(skillPath, 'SKILL.md'))) {
|
|
88
|
+
return { dir: skillPath, location: 'skills' };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check ~/.claude/plugins/ for skills installed as plugins
|
|
92
|
+
const pluginPath = join(pluginsDir, name);
|
|
93
|
+
if (existsSync(pluginPath)) {
|
|
94
|
+
const origin = readOrigin(pluginPath);
|
|
95
|
+
if (origin && origin.installedAs === 'plugin-skill') {
|
|
96
|
+
return { dir: pluginPath, location: 'plugins' };
|
|
97
|
+
}
|
|
98
|
+
// Also check if it has a SKILL.md (might be installed via skills add)
|
|
99
|
+
if (existsSync(join(pluginPath, 'SKILL.md'))) {
|
|
100
|
+
return { dir: pluginPath, location: 'plugins' };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* List all installed skills from both directories.
|
|
109
|
+
* Returns array of { name, dir, location, meta }.
|
|
110
|
+
*/
|
|
111
|
+
export function listAllSkills() {
|
|
112
|
+
const skillsDir = getSkillsDir();
|
|
113
|
+
const pluginsDir = getPluginsDir();
|
|
114
|
+
const results = [];
|
|
115
|
+
|
|
116
|
+
// Skills from ~/.claude/skills/
|
|
117
|
+
try {
|
|
118
|
+
const entries = readdirSync(skillsDir, { withFileTypes: true });
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
if (!entry.isDirectory()) continue;
|
|
121
|
+
const dir = join(skillsDir, entry.name);
|
|
122
|
+
if (!existsSync(join(dir, 'SKILL.md'))) continue;
|
|
123
|
+
results.push({
|
|
124
|
+
name: entry.name,
|
|
125
|
+
dir,
|
|
126
|
+
location: 'skills',
|
|
127
|
+
meta: readSkillMeta(dir, entry.name),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
// directory might not exist yet
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Skills from ~/.claude/plugins/ (installed as plugin-skill)
|
|
135
|
+
try {
|
|
136
|
+
const entries = readdirSync(pluginsDir, { withFileTypes: true });
|
|
137
|
+
for (const entry of entries) {
|
|
138
|
+
if (!entry.isDirectory()) continue;
|
|
139
|
+
const dir = join(pluginsDir, entry.name);
|
|
140
|
+
const origin = readOrigin(dir);
|
|
141
|
+
if (origin && origin.installedAs === 'plugin-skill') {
|
|
142
|
+
results.push({
|
|
143
|
+
name: entry.name,
|
|
144
|
+
dir,
|
|
145
|
+
location: 'plugins',
|
|
146
|
+
meta: readSkillMeta(dir, entry.name),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
// directory might not exist yet
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return results;
|
|
155
|
+
}
|
|
@@ -1,74 +1,58 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
if (!entries.length) {
|
|
60
|
-
log.info('No skills installed');
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
log.info(`Updating ${entries.length} skill${entries.length === 1 ? '' : 's'}...\n`);
|
|
65
|
-
|
|
66
|
-
let updated = 0;
|
|
67
|
-
for (const entry of entries) {
|
|
68
|
-
if (await updateOne(entry.name)) updated++;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
console.log();
|
|
72
|
-
log.success(`${updated}/${entries.length} skills updated`);
|
|
73
|
-
log.dim('Restart Claude Code to apply changes.');
|
|
74
|
-
}
|
|
1
|
+
import { log, spinner } from '../../utils.js';
|
|
2
|
+
import { findInstalledSkill, listAllSkills, readOrigin } from './resolve.js';
|
|
3
|
+
import { add } from './add.js';
|
|
4
|
+
import { remove } from './remove.js';
|
|
5
|
+
|
|
6
|
+
async function updateOne(name) {
|
|
7
|
+
const found = findInstalledSkill(name);
|
|
8
|
+
|
|
9
|
+
if (!found) {
|
|
10
|
+
log.error(`Skill "${name}" is not installed`);
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const origin = readOrigin(found.dir);
|
|
15
|
+
if (!origin || !origin.repository) {
|
|
16
|
+
log.warn(`Skill "${name}" has no origin metadata, skipping`);
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const spin = spinner(`Updating skill "${name}"...`);
|
|
21
|
+
spin.start();
|
|
22
|
+
spin.stop();
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
// Remove and re-add
|
|
26
|
+
await remove(name);
|
|
27
|
+
await add(origin.repository, { skill: origin.skill || undefined });
|
|
28
|
+
return true;
|
|
29
|
+
} catch (err) {
|
|
30
|
+
log.error(`Failed to update skill "${name}": ${err.message}`);
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function update(name) {
|
|
36
|
+
if (name) {
|
|
37
|
+
await updateOne(name);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const skills = listAllSkills();
|
|
42
|
+
|
|
43
|
+
if (!skills.length) {
|
|
44
|
+
log.info('No skills installed');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
log.info(`Updating ${skills.length} skill${skills.length === 1 ? '' : 's'}...\n`);
|
|
49
|
+
|
|
50
|
+
let updated = 0;
|
|
51
|
+
for (const skill of skills) {
|
|
52
|
+
if (await updateOne(skill.name)) updated++;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log();
|
|
56
|
+
log.success(`${updated}/${skills.length} skills updated`);
|
|
57
|
+
log.dim('Restart Claude Code to apply changes.');
|
|
58
|
+
}
|