@dennisrongo/skills 0.1.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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +230 -0
  3. package/bin/claude-skills.js +189 -0
  4. package/lib/install.js +111 -0
  5. package/lib/list.js +63 -0
  6. package/lib/paths.js +39 -0
  7. package/lib/remove.js +52 -0
  8. package/lib/skills.js +121 -0
  9. package/package.json +48 -0
  10. package/skills/_template/SKILL.md +34 -0
  11. package/skills/conventional-commits/SKILL.md +136 -0
  12. package/skills/diagnose/SKILL.md +140 -0
  13. package/skills/dotnet-onion-api/SKILL.md +267 -0
  14. package/skills/dotnet-onion-api/references/anti-patterns.md +155 -0
  15. package/skills/dotnet-onion-api/references/solution-layout.md +113 -0
  16. package/skills/dotnet-onion-api/references/templates/appsettings.md +75 -0
  17. package/skills/dotnet-onion-api/references/templates/base-controller.cs.md +90 -0
  18. package/skills/dotnet-onion-api/references/templates/csproj-files.md +178 -0
  19. package/skills/dotnet-onion-api/references/templates/dbcontext.cs.md +149 -0
  20. package/skills/dotnet-onion-api/references/templates/exception-middleware.cs.md +101 -0
  21. package/skills/dotnet-onion-api/references/templates/feature-slice.md +349 -0
  22. package/skills/dotnet-onion-api/references/templates/program-cs.md +171 -0
  23. package/skills/dotnet-onion-api/references/templates/worker-program.cs.md +166 -0
  24. package/skills/grill-with-docs/SKILL.md +203 -0
  25. package/skills/handoff/SKILL.md +155 -0
  26. package/skills/improve-codebase-architecture/DEEPENING.md +37 -0
  27. package/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +44 -0
  28. package/skills/improve-codebase-architecture/LANGUAGE.md +53 -0
  29. package/skills/improve-codebase-architecture/SKILL.md +121 -0
  30. package/skills/nextjs-app-router/SKILL.md +328 -0
  31. package/skills/nextjs-app-router/references/anti-patterns.md +203 -0
  32. package/skills/nextjs-app-router/references/folder-layout.md +151 -0
  33. package/skills/nextjs-app-router/references/good-patterns.md +212 -0
  34. package/skills/nextjs-app-router/references/templates/api-base.md +62 -0
  35. package/skills/nextjs-app-router/references/templates/api-slice.md +75 -0
  36. package/skills/nextjs-app-router/references/templates/auth-slice.md +95 -0
  37. package/skills/nextjs-app-router/references/templates/ci-and-hooks.md +94 -0
  38. package/skills/nextjs-app-router/references/templates/components-json.md +41 -0
  39. package/skills/nextjs-app-router/references/templates/env-and-utils.md +110 -0
  40. package/skills/nextjs-app-router/references/templates/eslint-prettier.md +82 -0
  41. package/skills/nextjs-app-router/references/templates/feature-slice.md +184 -0
  42. package/skills/nextjs-app-router/references/templates/form-with-zod.md +192 -0
  43. package/skills/nextjs-app-router/references/templates/middleware.md +88 -0
  44. package/skills/nextjs-app-router/references/templates/next-config.md +42 -0
  45. package/skills/nextjs-app-router/references/templates/package.md +99 -0
  46. package/skills/nextjs-app-router/references/templates/providers-and-store.md +79 -0
  47. package/skills/nextjs-app-router/references/templates/root-layout.md +146 -0
  48. package/skills/nextjs-app-router/references/templates/route-group-layouts.md +90 -0
  49. package/skills/nextjs-app-router/references/templates/tailwind-config.md +89 -0
  50. package/skills/nextjs-app-router/references/templates/testing.md +137 -0
  51. package/skills/nextjs-app-router/references/templates/tsconfig.md +40 -0
  52. package/skills/plan-and-build/SKILL.md +214 -0
  53. package/skills/pr-review/SKILL.md +132 -0
  54. package/skills/write-a-skill/SKILL.md +191 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dennis Rongo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # claude-skills
2
+
3
+ A curated, fine-tunable library of [Claude Code](https://docs.claude.com/en/docs/claude-code/overview) skills. Install globally or per-project with one command.
4
+
5
+ > Skills are reusable bundles of instructions that Claude consults when relevant. This repo is a personal/team library of them — grow it over time, tune the ones that misfire, and install the set you want on any machine.
6
+
7
+ ## Quick start
8
+
9
+ ### From npm (recommended)
10
+
11
+ The package is published as [`@dennisrongo/skills`](https://www.npmjs.com/package/@dennisrongo/skills). The installed binary is `claude-skills`.
12
+
13
+ ```bash
14
+ # See what's in the library
15
+ npx @dennisrongo/skills list
16
+
17
+ # Interactive picker, global (~/.claude/skills)
18
+ npx @dennisrongo/skills install
19
+
20
+ # Interactive picker, project-scoped (./.claude/skills)
21
+ npx @dennisrongo/skills install -p
22
+
23
+ # Install specific skills globally (~/.claude/skills)
24
+ npx @dennisrongo/skills install conventional-commits pr-review
25
+
26
+ # Install specific skills into the current project
27
+ npx @dennisrongo/skills install conventional-commits pr-review -p
28
+
29
+ # Install everything into the current project (./.claude/skills)
30
+ npx @dennisrongo/skills install --all --project
31
+ ```
32
+
33
+ Or install once and call it directly:
34
+
35
+ ```bash
36
+ npm install -g @dennisrongo/skills
37
+ claude-skills list
38
+ ```
39
+
40
+ ### From GitHub (no npm publish needed)
41
+
42
+ ```bash
43
+ npx github:dennisrongo/claude-skills list
44
+ npx github:dennisrongo/claude-skills install
45
+ ```
46
+
47
+ > The interactive picker, `--all`, and named-skill installs all accept `-p` / `--project` to target `./.claude/skills` instead of the global `~/.claude/skills`. Run it from the project root.
48
+
49
+ ### Shorter alias (recommended)
50
+
51
+ `npx github:dennisrongo/claude-skills` is a mouthful. Add a shell alias:
52
+
53
+ ```bash
54
+ # bash / zsh — add to ~/.bashrc or ~/.zshrc
55
+ alias claude-skills="npx --yes github:dennisrongo/claude-skills"
56
+
57
+ # fish — add to ~/.config/fish/config.fish
58
+ alias claude-skills "npx --yes github:dennisrongo/claude-skills"
59
+ ```
60
+
61
+ Then:
62
+
63
+ ```bash
64
+ claude-skills list
65
+ claude-skills install --all
66
+ ```
67
+
68
+ ### Pinning a version
69
+
70
+ `npx github:...` resolves to the latest commit on `main`. To pin to a specific commit, branch, or tag:
71
+
72
+ ```bash
73
+ npx github:dennisrongo/claude-skills#v0.1.0 install # tag
74
+ npx github:dennisrongo/claude-skills#abc1234 install # commit SHA
75
+ npx github:dennisrongo/claude-skills#some-branch install # branch
76
+ ```
77
+
78
+ ### Local clone (for contributors)
79
+
80
+ ```bash
81
+ git clone https://github.com/dennisrongo/claude-skills.git
82
+ cd claude-skills
83
+ npm install
84
+ node bin/claude-skills.js list
85
+ ```
86
+
87
+ ## Available skills
88
+
89
+ | Skill | What it does |
90
+ |---|---|
91
+ | [`conventional-commits`](./skills/conventional-commits/SKILL.md) | Write git commit messages that follow the [Conventional Commits](https://www.conventionalcommits.org/) spec (`feat`, `fix`, `chore`, `docs`, …), auto-prefixed with the ticket number from the current branch (e.g. `feature/12345-...` → `#12345`) and a project tag (`API` / `CLIENT` / `CONSOLE` / `DB`) when detectable from the diff. Both prefixes are omitted when they don't apply. Triggers on commit-message requests. |
92
+ | [`diagnose`](./skills/diagnose/SKILL.md) | Disciplined diagnosis loop for hard bugs and performance regressions — reproduce → minimise → hypothesise → instrument → fix → regression-test. Forces a fast, deterministic feedback loop before any guessing, generates 3–5 ranked falsifiable hypotheses, uses tagged `[DEBUG-...]` instrumentation that's trivially cleaned up, and ends with a post-mortem on what would have prevented the bug. Triggers on "diagnose this" / "debug this" / bug reports / flaky tests / perf regressions. |
93
+ | [`dotnet-onion-api`](./skills/dotnet-onion-api/SKILL.md) | Scaffold a new .NET solution (Web API + Worker microservices) using ONION architecture and EF Core, codifying battle-tested layered patterns and explicitly removing common legacy pitfalls (sproc-centric repos with reflection, EF6 on netstandard2.1, polling console workers, mutable base-service state, missing `CancellationToken`). Three modes — full solution scaffold, add-a-feature slice, add-a-worker microservice. Resolves TFM and NuGet versions at scaffold time (not hard-coded). |
94
+ | [`grill-with-docs`](./skills/grill-with-docs/SKILL.md) | Interview-driven design review. Stress-tests a plan, RFC, or feature idea against the project's existing domain model and documented decisions — one question at a time with `AskUserQuestion`, sharpening fuzzy terminology and surfacing contradictions with the codebase. Updates `CONTEXT.md` (glossary) inline as terms resolve and writes ADRs to `docs/adr/` only when the decision is hard to reverse, non-obvious, and had real trade-offs. Writes no production code — composes with [`plan-and-build`](./skills/plan-and-build/SKILL.md) for the implementation handoff. Triggers on "grill me", "stress-test this plan", "challenge this design", "/grill-with-docs", or a pasted RFC. |
95
+ | [`handoff`](./skills/handoff/SKILL.md) | Capture a session hand-off before context runs out — writes a dated `.claude/handoffs/*.md` (objective, progress, decisions, files, open issues, ready-to-paste next-session prompt) plus a lightweight memory pointer so a fresh Claude session can resume cleanly. |
96
+ | [`improve-codebase-architecture`](./skills/improve-codebase-architecture/SKILL.md) | Surface architectural friction and propose **deepening opportunities** — refactors that collapse clusters of shallow modules into one deep module with a real seam. Walks the codebase with an Explore sub-agent, applies the **deletion test** to suspected pass-throughs, presents numbered candidates (files / problem / solution / benefits) using `CONTEXT.md` for the domain and a strict architecture glossary (module / interface / seam / depth / leverage / locality) for the structure, then drops into a grilling loop with optional parallel sub-agent interface design ("Design It Twice"). Updates `CONTEXT.md` inline as new concepts get named and offers an ADR only when a rejection is load-bearing. Composes with [`grill-with-docs`](./skills/grill-with-docs/SKILL.md) for glossary + ADR discipline and [`plan-and-build`](./skills/plan-and-build/SKILL.md) for the implementation handoff. Writes no production code. Triggers on "improve architecture", "architecture review", "find refactoring / deepening opportunities", "find shallow modules", "make this more testable", or `/improve-codebase-architecture`. |
97
+ | [`nextjs-app-router`](./skills/nextjs-app-router/SKILL.md) | Scaffold a new Next.js (App Router) frontend with TypeScript, Redux Toolkit + RTK Query, Tailwind + shadcn/ui (Radix), and React Hook Form + Zod. Codifies route-group auth boundaries, a single injected RTK Query API, schema-driven forms with introspected defaults, server-side `middleware.ts`, and Vitest + Playwright + CI defaults — while forbidding common pitfalls (`'use client'` on root pages, `router.push` in `useEffect`, `serializableCheck: false`, `@ts-ignore`, mixed `moment`/`date-fns`, `styled-components` alongside Tailwind, case-sensitive folder dupes, `dangerouslySetInnerHTML` without sanitization, tokens outside `httpOnly` cookies). Three modes — full project scaffold, add-a-feature slice, add-an-API-slice. Resolves package versions at scaffold time (not hard-coded). |
98
+ | [`plan-and-build`](./skills/plan-and-build/SKILL.md) | Plan-first feature builder. Grills the user about the feature (à la `grill-with-docs`) until the design is unambiguous, detects the project's stack and conventions, presents a plan, and gates on `ExitPlanMode` approval before writing any code. Builds TDD-first with NUnit when a .NET API changes — appending to the matching test class if one already exists rather than forking a parallel one — reuses existing patterns, keeps comments minimal, and generates EF Core / migration files **without ever** running `dotnet ef database update` or any DDL/SQL against the user's database. Triggers on "build/add/implement a feature", "/plan-and-build", or a pasted feature spec. |
99
+ | [`pr-review`](./skills/pr-review/SKILL.md) | Conduct a structured PR / diff review prioritized correctness → design → tests → security → performance → readability, with categorized feedback (`blocking` / `suggestion` / `question` / `nit` / `praise`). |
100
+ | [`write-a-skill`](./skills/write-a-skill/SKILL.md) | Author a new Claude Code skill — interview-driven scaffolding that produces a properly-structured `SKILL.md` (trigger-rich YAML description, "When to use", workflow, examples, anti-patterns), drops it in the right location (library `skills/`, project `./.claude/skills/`, or global `~/.claude/skills/`), updates the README skills table when extending this library, and runs a review checklist focused on the failure mode that matters most — under-triggering descriptions. Triggers on "create/write/add a skill", "/write-a-skill", or a pasted SKILL.md URL with "one like this". |
101
+
102
+ Run `claude-skills list` to see this list with install status, or browse [`skills/`](./skills) directly.
103
+
104
+ ## How Claude Code finds these skills
105
+
106
+ Claude Code looks for `SKILL.md` files in:
107
+
108
+ - `~/.claude/skills/<skill>/SKILL.md` — available in every session (global)
109
+ - `<project>/.claude/skills/<skill>/SKILL.md` — available only inside that project
110
+
111
+ This CLI just copies skill folders to one of those locations. Nothing magic.
112
+
113
+ ## Commands
114
+
115
+ | Command | What it does |
116
+ |---|---|
117
+ | `claude-skills list` | List skills available in the library, marking which are installed |
118
+ | `claude-skills installed` | List skills currently installed |
119
+ | `claude-skills install` | Interactive multi-select picker |
120
+ | `claude-skills install <name>...` | Install one or more skills by name |
121
+ | `claude-skills install --all` | Install every skill in the library |
122
+ | `claude-skills remove <name>...` | Remove installed skill(s) |
123
+ | `claude-skills remove --all` | Remove every installed skill |
124
+
125
+ ## Flags
126
+
127
+ - `-g, --global` — target `~/.claude/skills` (default)
128
+ - `-p, --project` — target `./.claude/skills`
129
+ - `-f, --force` — overwrite if already installed (interactive install always overwrites selected items)
130
+ - `-h, --help` / `-v, --version`
131
+
132
+ ## Adding your own skills
133
+
134
+ The library lives in [`skills/`](./skills). Each skill is a directory containing a `SKILL.md` with YAML frontmatter.
135
+
136
+ ```
137
+ skills/
138
+ ├── _template/ # not installed (leading underscore skips it)
139
+ │ └── SKILL.md
140
+ ├── conventional-commits/
141
+ │ └── SKILL.md
142
+ └── my-new-skill/
143
+ ├── SKILL.md
144
+ ├── references/ # optional supporting files
145
+ └── scripts/ # optional executable helpers
146
+ ```
147
+
148
+ ### Minimal `SKILL.md`
149
+
150
+ ```markdown
151
+ ---
152
+ name: my-skill-name
153
+ description: One sentence describing what it does AND when to trigger it. Be specific about phrases the user might use.
154
+ ---
155
+
156
+ # My Skill Name
157
+
158
+ Instructions for Claude...
159
+ ```
160
+
161
+ The `description` is the most important field — it's what Claude reads to decide whether to consult the skill. Be explicit about trigger conditions; under-triggering is the more common failure mode.
162
+
163
+ To add a new skill:
164
+
165
+ 1. Clone the repo: `git clone https://github.com/dennisrongo/claude-skills.git`
166
+ 2. Copy `skills/_template/` to `skills/<your-skill-name>/`
167
+ 3. Edit `SKILL.md`
168
+ 4. Commit and push to `main`
169
+ 5. On any machine: `npx github:dennisrongo/claude-skills install <your-skill-name>` — picks up the new skill immediately, no publishing step needed
170
+
171
+ > Tip: `npx` caches the package per version spec. If you push an update to `main` and the next `npx github:dennisrongo/claude-skills ...` call doesn't seem to pick it up, run `npx --yes ...` to force a refresh, or clear the cache with `npx clear-npx-cache`.
172
+
173
+ ## Fine-tuning skills you've installed
174
+
175
+ Two patterns:
176
+
177
+ **Tune in-place, then upstream:**
178
+ Edit the file at `~/.claude/skills/<name>/SKILL.md` directly while you're iterating with Claude. Once it feels right, copy the edits back into this repo's `skills/<name>/SKILL.md` and commit.
179
+
180
+ **Tune in the repo, reinstall:**
181
+ Edit `skills/<name>/SKILL.md` in your clone, then run `npx github:dennisrongo/claude-skills install <name> --force` to push it to your install location.
182
+
183
+ **Pull upstream updates:**
184
+ When a skill in this library has been updated on `main` and you want the new version locally, force-reinstall with a cache-bust:
185
+
186
+ ```bash
187
+ # Single skill
188
+ npx --yes github:dennisrongo/claude-skills install <name> --force
189
+
190
+ # All installed skills
191
+ npx --yes github:dennisrongo/claude-skills install --all --force
192
+ ```
193
+
194
+ - `--yes` bypasses the `npx` cache so it re-fetches the latest commit on `main` instead of reusing an old one.
195
+ - `--force` overwrites the existing install (without it, the CLI skips skills that already exist).
196
+ - Add `-p` / `--project` if the skill lives in `./.claude/skills` instead of the global `~/.claude/skills`.
197
+
198
+ ## Releasing
199
+
200
+ Releases are published to npm automatically by [`.github/workflows/publish.yml`](./.github/workflows/publish.yml) when a GitHub Release is published. The package ships with [npm provenance](https://docs.npmjs.com/generating-provenance-statements) — npm verifies it was built by this repo's Actions workflow.
201
+
202
+ One-time setup:
203
+
204
+ 1. Create an automation-scoped `NPM_TOKEN` at https://www.npmjs.com/settings/<user>/tokens (use a "Granular Access Token" or "Automation" token).
205
+ 2. Add it to the repo as a secret: **Settings → Secrets and variables → Actions → New repository secret**, name `NPM_TOKEN`.
206
+ 3. If you ever rename the package, confirm the new name is free: `npm view <name>`. The current scoped name `@dennisrongo/skills` lives under your npm user/org — `npm publish --access public` will create it on first publish.
207
+
208
+ Cutting a release:
209
+
210
+ ```bash
211
+ # Bump version (creates a commit + tag)
212
+ npm version patch # or minor / major
213
+
214
+ # Push the commit and tag
215
+ git push --follow-tags
216
+
217
+ # Create a GitHub Release pointing at the new tag — the workflow takes over from there.
218
+ gh release create "v$(node -p "require('./package.json').version")" --generate-notes
219
+ ```
220
+
221
+ The workflow checks `package.json` version matches the release tag, runs tests, then publishes with `--provenance`. After publishing both invocation forms work:
222
+
223
+ ```bash
224
+ npx @dennisrongo/skills install # via npm
225
+ npx github:dennisrongo/claude-skills install # still works, latest main
226
+ ```
227
+
228
+ ## License
229
+
230
+ [MIT](./LICENSE) © Dennis Rongo
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { parseArgs } = require('node:util');
5
+ const kleur = require('kleur');
6
+
7
+ const { install } = require('../lib/install');
8
+ const { remove, removeAll } = require('../lib/remove');
9
+ const { listLibrary, listInstalled } = require('../lib/list');
10
+ const { listLibrarySkills, listInstalledSkills } = require('../lib/skills');
11
+ const { getInstallDir } = require('../lib/paths');
12
+
13
+ const VERSION = require('../package.json').version;
14
+
15
+ function printHelp() {
16
+ console.log(`
17
+ ${kleur.bold('claude-skills')} ${kleur.dim('v' + VERSION)}
18
+ A curated library of Claude Code skills.
19
+
20
+ ${kleur.bold('USAGE')}
21
+ npx claude-skills <command> [options]
22
+
23
+ ${kleur.bold('COMMANDS')}
24
+ list List skills available in the library
25
+ installed List skills installed locally
26
+ install Interactive picker — choose what to install
27
+ install <name>... Install one or more skills by name
28
+ install --all Install every skill in the library
29
+ remove <name>... Remove installed skill(s)
30
+ remove --all Remove all installed skills
31
+ help Show this help
32
+
33
+ ${kleur.bold('OPTIONS')}
34
+ -g, --global Install/remove at ~/.claude/skills ${kleur.dim('(default)')}
35
+ -p, --project Install/remove at ./.claude/skills
36
+ -f, --force Overwrite if already installed
37
+ -h, --help Show help
38
+ -v, --version Show version
39
+
40
+ ${kleur.bold('EXAMPLES')}
41
+ npx claude-skills list
42
+ npx claude-skills install ${kleur.dim('# interactive picker')}
43
+ npx claude-skills install pdf docx ${kleur.dim('# specific skills')}
44
+ npx claude-skills install --all -p ${kleur.dim('# all skills, in project')}
45
+ npx claude-skills remove pdf
46
+ `);
47
+ }
48
+
49
+ const OPTIONS = {
50
+ global: { type: 'boolean', short: 'g' },
51
+ project: { type: 'boolean', short: 'p' },
52
+ force: { type: 'boolean', short: 'f' },
53
+ all: { type: 'boolean', short: 'a' },
54
+ help: { type: 'boolean', short: 'h' },
55
+ version: { type: 'boolean', short: 'v' },
56
+ };
57
+
58
+ function resolveScope(values) {
59
+ if (values.project && values.global) {
60
+ console.error(kleur.red('Cannot use --global and --project together.'));
61
+ process.exit(1);
62
+ }
63
+ if (values.project) return 'project';
64
+ return 'global';
65
+ }
66
+
67
+ async function interactiveInstall(scope, force) {
68
+ // Lazy-require so plain `list` doesn't pay the import cost.
69
+ const { checkbox } = require('@inquirer/prompts');
70
+
71
+ const installDir = getInstallDir(scope);
72
+ const library = listLibrarySkills();
73
+ const installed = new Set(listInstalledSkills(installDir).map((s) => s.slug));
74
+
75
+ if (library.length === 0) {
76
+ console.log(kleur.yellow('No skills available in the library.'));
77
+ return;
78
+ }
79
+
80
+ const choices = library.map((s) => ({
81
+ name: `${s.name} ${kleur.dim('— ' + s.description.replace(/\s+/g, ' ').slice(0, 70))}`,
82
+ value: s.slug,
83
+ checked: installed.has(s.slug),
84
+ }));
85
+
86
+ let selected;
87
+ try {
88
+ selected = await checkbox({
89
+ message: `Select skills to install (${scope})`,
90
+ choices,
91
+ pageSize: 15,
92
+ });
93
+ } catch (err) {
94
+ if (err && err.name === 'ExitPromptError') {
95
+ console.log(kleur.dim('Cancelled.'));
96
+ return;
97
+ }
98
+ throw err;
99
+ }
100
+
101
+ if (selected.length === 0) {
102
+ console.log(kleur.dim('Nothing selected.'));
103
+ return;
104
+ }
105
+
106
+ install(selected, { scope, force: true /* user explicitly picked these */ });
107
+ }
108
+
109
+ async function main() {
110
+ // Split out the subcommand from option parsing so we can hand the rest to parseArgs.
111
+ const argv = process.argv.slice(2);
112
+ if (argv.length === 0) {
113
+ printHelp();
114
+ return;
115
+ }
116
+
117
+ const command = argv[0];
118
+ const rest = argv.slice(1);
119
+
120
+ let parsed;
121
+ try {
122
+ parsed = parseArgs({
123
+ args: rest,
124
+ options: OPTIONS,
125
+ allowPositionals: true,
126
+ strict: true,
127
+ });
128
+ } catch (err) {
129
+ console.error(kleur.red(err.message));
130
+ process.exit(1);
131
+ }
132
+
133
+ const { values, positionals } = parsed;
134
+
135
+ if (values.help || command === 'help' || command === '--help' || command === '-h') {
136
+ printHelp();
137
+ return;
138
+ }
139
+ if (values.version || command === '--version' || command === '-v') {
140
+ console.log(VERSION);
141
+ return;
142
+ }
143
+
144
+ const scope = resolveScope(values);
145
+
146
+ switch (command) {
147
+ case 'list':
148
+ case 'ls':
149
+ listLibrary({ scope });
150
+ break;
151
+
152
+ case 'installed':
153
+ listInstalled({ scope });
154
+ break;
155
+
156
+ case 'install':
157
+ case 'add': {
158
+ if (values.all) {
159
+ install([], { scope, force: values.force, all: true });
160
+ } else if (positionals.length === 0) {
161
+ await interactiveInstall(scope, values.force);
162
+ } else {
163
+ install(positionals, { scope, force: values.force });
164
+ }
165
+ break;
166
+ }
167
+
168
+ case 'remove':
169
+ case 'rm':
170
+ case 'uninstall': {
171
+ if (values.all) {
172
+ removeAll({ scope });
173
+ } else {
174
+ remove(positionals, { scope });
175
+ }
176
+ break;
177
+ }
178
+
179
+ default:
180
+ console.error(kleur.red(`Unknown command: ${command}`));
181
+ console.error(kleur.dim(`Run "npx claude-skills help" to see available commands.`));
182
+ process.exit(1);
183
+ }
184
+ }
185
+
186
+ main().catch((err) => {
187
+ console.error(kleur.red('Error:'), err.message);
188
+ process.exit(1);
189
+ });
package/lib/install.js ADDED
@@ -0,0 +1,111 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const kleur = require('kleur');
6
+ const { getInstallDir, ensureDir } = require('./paths');
7
+ const { findLibrarySkill, listLibrarySkills } = require('./skills');
8
+
9
+ function copyDirRecursive(src, dest) {
10
+ ensureDir(dest);
11
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
12
+ const srcPath = path.join(src, entry.name);
13
+ const destPath = path.join(dest, entry.name);
14
+ if (entry.isDirectory()) {
15
+ copyDirRecursive(srcPath, destPath);
16
+ } else if (entry.isSymbolicLink()) {
17
+ const link = fs.readlinkSync(srcPath);
18
+ fs.symlinkSync(link, destPath);
19
+ } else {
20
+ fs.copyFileSync(srcPath, destPath);
21
+ }
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Install one skill into the target install dir.
27
+ * @param {object} skill - { name, slug, dir }
28
+ * @param {string} installDir
29
+ * @param {object} opts - { force }
30
+ * @returns {'installed'|'skipped'|'updated'}
31
+ */
32
+ function installOne(skill, installDir, opts = {}) {
33
+ ensureDir(installDir);
34
+ const target = path.join(installDir, skill.slug);
35
+ const exists = fs.existsSync(target);
36
+
37
+ if (exists && !opts.force) {
38
+ return 'skipped';
39
+ }
40
+
41
+ if (exists) {
42
+ fs.rmSync(target, { recursive: true, force: true });
43
+ copyDirRecursive(skill.dir, target);
44
+ return 'updated';
45
+ }
46
+
47
+ copyDirRecursive(skill.dir, target);
48
+ return 'installed';
49
+ }
50
+
51
+ /**
52
+ * Install multiple skills by identifier.
53
+ * @param {string[]} identifiers
54
+ * @param {object} options - { scope, force, all }
55
+ */
56
+ function install(identifiers, options) {
57
+ const { scope = 'global', force = false, all = false } = options;
58
+ const installDir = getInstallDir(scope);
59
+
60
+ let toInstall;
61
+ if (all) {
62
+ toInstall = listLibrarySkills();
63
+ } else {
64
+ toInstall = [];
65
+ for (const id of identifiers) {
66
+ const skill = findLibrarySkill(id);
67
+ if (!skill) {
68
+ console.error(kleur.red(`✗ Skill not found in library: ${id}`));
69
+ continue;
70
+ }
71
+ toInstall.push(skill);
72
+ }
73
+ }
74
+
75
+ if (toInstall.length === 0) {
76
+ console.error(kleur.yellow('No skills to install.'));
77
+ return { installed: 0, updated: 0, skipped: 0 };
78
+ }
79
+
80
+ console.log(
81
+ kleur.bold(
82
+ `Installing ${toInstall.length} skill${toInstall.length === 1 ? '' : 's'} to ${kleur.cyan(installDir)}`
83
+ )
84
+ );
85
+ console.log();
86
+
87
+ const counts = { installed: 0, updated: 0, skipped: 0 };
88
+
89
+ for (const skill of toInstall) {
90
+ const result = installOne(skill, installDir, { force });
91
+ counts[result]++;
92
+ const label = {
93
+ installed: kleur.green('✓ installed'),
94
+ updated: kleur.cyan('↻ updated '),
95
+ skipped: kleur.yellow('⊘ skipped '),
96
+ }[result];
97
+ const suffix = result === 'skipped' ? kleur.dim(' (use --force to overwrite)') : '';
98
+ console.log(` ${label} ${skill.name}${suffix}`);
99
+ }
100
+
101
+ console.log();
102
+ const summary = [];
103
+ if (counts.installed) summary.push(kleur.green(`${counts.installed} installed`));
104
+ if (counts.updated) summary.push(kleur.cyan(`${counts.updated} updated`));
105
+ if (counts.skipped) summary.push(kleur.yellow(`${counts.skipped} skipped`));
106
+ console.log(summary.join(', ') || 'No changes.');
107
+
108
+ return counts;
109
+ }
110
+
111
+ module.exports = { install, installOne, copyDirRecursive };
package/lib/list.js ADDED
@@ -0,0 +1,63 @@
1
+ 'use strict';
2
+
3
+ const kleur = require('kleur');
4
+ const { getInstallDir } = require('./paths');
5
+ const { listLibrarySkills, listInstalledSkills } = require('./skills');
6
+
7
+ function truncate(s, max) {
8
+ if (!s) return '';
9
+ return s.length <= max ? s : s.slice(0, max - 1) + '…';
10
+ }
11
+
12
+ function printSkillTable(skills, { installedSet } = {}) {
13
+ if (skills.length === 0) {
14
+ console.log(kleur.dim(' (none)'));
15
+ return;
16
+ }
17
+ const nameWidth = Math.min(
18
+ 28,
19
+ Math.max(...skills.map((s) => s.name.length), 4)
20
+ );
21
+ for (const s of skills) {
22
+ const marker = installedSet
23
+ ? installedSet.has(s.slug)
24
+ ? kleur.green('●')
25
+ : kleur.dim('○')
26
+ : ' ';
27
+ const name = s.name.padEnd(nameWidth);
28
+ const desc = kleur.dim(truncate(s.description.replace(/\s+/g, ' '), 80));
29
+ console.log(` ${marker} ${kleur.bold(name)} ${desc}`);
30
+ }
31
+ }
32
+
33
+ function listLibrary(options = {}) {
34
+ const skills = listLibrarySkills();
35
+ const { scope = 'global' } = options;
36
+ const installed = listInstalledSkills(getInstallDir(scope));
37
+ const installedSet = new Set(installed.map((s) => s.slug));
38
+
39
+ console.log();
40
+ console.log(kleur.bold(`Available skills`) + kleur.dim(` (${scope} scope)`));
41
+ console.log();
42
+ printSkillTable(skills, { installedSet });
43
+ console.log();
44
+ console.log(
45
+ kleur.dim(
46
+ ` ${kleur.green('●')} installed ${kleur.dim('○')} available`
47
+ )
48
+ );
49
+ console.log();
50
+ }
51
+
52
+ function listInstalled(options = {}) {
53
+ const { scope = 'global' } = options;
54
+ const installDir = getInstallDir(scope);
55
+ const skills = listInstalledSkills(installDir);
56
+ console.log();
57
+ console.log(kleur.bold(`Installed skills`) + kleur.dim(` (${installDir})`));
58
+ console.log();
59
+ printSkillTable(skills);
60
+ console.log();
61
+ }
62
+
63
+ module.exports = { listLibrary, listInstalled };
package/lib/paths.js ADDED
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const os = require('node:os');
5
+ const fs = require('node:fs');
6
+
7
+ const GLOBAL_DIR = path.join(os.homedir(), '.claude', 'skills');
8
+ const PROJECT_DIR = path.join(process.cwd(), '.claude', 'skills');
9
+
10
+ /**
11
+ * Return the install target directory based on a scope flag.
12
+ * @param {'global'|'project'} scope
13
+ */
14
+ function getInstallDir(scope) {
15
+ if (scope === 'project') return PROJECT_DIR;
16
+ if (scope === 'global') return GLOBAL_DIR;
17
+ throw new Error(`Unknown scope: ${scope}`);
18
+ }
19
+
20
+ /**
21
+ * Path to the bundled skills library (inside this package).
22
+ * Resolves relative to the package root so it works whether run via npx,
23
+ * a global install, or a local checkout.
24
+ */
25
+ function getLibraryDir() {
26
+ return path.resolve(__dirname, '..', 'skills');
27
+ }
28
+
29
+ function ensureDir(dir) {
30
+ fs.mkdirSync(dir, { recursive: true });
31
+ }
32
+
33
+ module.exports = {
34
+ GLOBAL_DIR,
35
+ PROJECT_DIR,
36
+ getInstallDir,
37
+ getLibraryDir,
38
+ ensureDir,
39
+ };