skill-tags 1.3.0 → 1.5.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 +74 -38
- package/bin/categories.js +87 -76
- package/bin/postinstall.js +58 -24
- package/bin/skill-tags.js +63 -1
- package/package.json +1 -1
- package/sync.sh +42 -27
package/README.md
CHANGED
|
@@ -24,36 +24,28 @@ If you're interested in contributing to Cursor Kits, please let me know!
|
|
|
24
24
|
- [How Categorization Works](#how-categorization-works)
|
|
25
25
|
- [Uninstall](#uninstall)
|
|
26
26
|
- [Requirements](#requirements)
|
|
27
|
+
- [Changelog](#changelog)
|
|
27
28
|
- [License](#license)
|
|
28
29
|
|
|
29
30
|
---
|
|
30
31
|
|
|
31
32
|
## Quick Start
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
2. **Setup:** `skill-tags --setup` → choose **Global** → `source ~/.zshrc`
|
|
37
|
-
3. **Sync:** `skill-tags` (generates the command file)
|
|
38
|
-
4. **Use:** Reference `@skill-tags.md` in any Cursor chat
|
|
34
|
+
1. **Install:** `npm install skill-tags -g` (or `--save-dev` for project-level)
|
|
35
|
+
2. **Follow the prompts** — choose **Global** or **Project**, then **Auto** or **Manual** sync
|
|
36
|
+
3. **Use:** Reference `@skill-tags.md` in any Cursor chat
|
|
39
37
|
|
|
40
38
|
```bash
|
|
41
39
|
npm install skill-tags -g
|
|
42
|
-
|
|
40
|
+
# Setup wizard runs automatically — choose Global + Auto (recommended)
|
|
43
41
|
source ~/.zshrc
|
|
44
|
-
skill-tags
|
|
45
42
|
```
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
1. **Install:** `npm install skill-tags --save-dev`
|
|
50
|
-
2. **Setup:** `npx skill-tags --setup` → choose **Project**
|
|
51
|
-
3. **Use:** Reference `@project-skill-tags.md` in Cursor chat
|
|
44
|
+
For project-level installs:
|
|
52
45
|
|
|
53
46
|
```bash
|
|
54
47
|
npm install skill-tags --save-dev
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
# Setup wizard runs automatically — choose Project + Auto
|
|
57
49
|
npm run skills add owner/repo/skill-name # adds skill + auto-syncs
|
|
58
50
|
```
|
|
59
51
|
|
|
@@ -79,20 +71,25 @@ For more focused context windows, you can group your skills into category-specif
|
|
|
79
71
|
skill-tags --categories
|
|
80
72
|
```
|
|
81
73
|
|
|
82
|
-
This opens an interactive wizard where you can
|
|
83
|
-
|
|
84
|
-
Suggested skills are pre-selected (`[*]`). Use arrow keys and space to toggle any skill before confirming.
|
|
74
|
+
This opens an interactive wizard where you can add and remove categories. On save, skill assignment is powered by keyword matching against the generated `skill-tags.md` index — all text is lowercased with strict full-word matching and automatic suffix expansion (plurals, gerunds, etc.) to avoid false positives.
|
|
85
75
|
|
|
86
76
|
Once configured, every `skill-tags` sync automatically regenerates the category files from the saved config at `~/.cursor/skill-tags-categories.conf`.
|
|
87
77
|
|
|
78
|
+
For project-level categories (using only skills in `.agents/skills/`):
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
skill-tags --categories --local
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Then reference focused category indexes instead of the full list:
|
|
85
|
+
|
|
88
86
|
```bash
|
|
89
|
-
# Inject a focused category index instead of the full list:
|
|
90
87
|
@skills-frontend.md
|
|
91
88
|
@skills-testing.md
|
|
92
89
|
@skills-ai-agents.md
|
|
93
90
|
```
|
|
94
91
|
|
|
95
|
-
Predefined categories: `frontend`, `backend`, `database`, `testing`, `design`, `accessibility`, `performance`, `ai-agents`, `devops`, `marketing`, `mobile`, `documentation`.
|
|
92
|
+
Predefined categories: `frontend`, `backend`, `database`, `testing`, `design`, `accessibility`, `performance`, `ai-agents`, `devops`, `marketing`, `mobile`, `documentation`.
|
|
96
93
|
|
|
97
94
|
---
|
|
98
95
|
|
|
@@ -109,10 +106,8 @@ Install and configure the `skill-tags` package (npm: https://www.npmjs.com/packa
|
|
|
109
106
|
Use a terminal that runs outside the sandbox with full permissions to avoid permission errors during install. In Cursor, this means using a non-sandboxed terminal session if available (required_permissions: ["all"] if running via agent shell tools).
|
|
110
107
|
|
|
111
108
|
Steps to perform:
|
|
112
|
-
1. Install the package: `npm install skill-tags -g`
|
|
113
|
-
2.
|
|
114
|
-
3. Source my shell rc file or instruct me to do so manually
|
|
115
|
-
4. Run `skill-tags` to perform an initial sync of all installed skills
|
|
109
|
+
1. Install the package: `npm install skill-tags -g` — the setup wizard runs automatically during install; when prompted, choose Global and Auto (recommended)
|
|
110
|
+
2. Source my shell rc file or instruct me to do so manually
|
|
116
111
|
|
|
117
112
|
When complete, output a summary that includes:
|
|
118
113
|
- Where the command file was generated (~/.cursor/commands/skill-tags.md)
|
|
@@ -132,16 +127,14 @@ When complete, output a summary that includes:
|
|
|
132
127
|
### Global install (npm)
|
|
133
128
|
|
|
134
129
|
```bash
|
|
135
|
-
npm install skill-tags -g
|
|
136
|
-
skill-tags --setup # choose: Global
|
|
130
|
+
npm install skill-tags -g # setup wizard runs automatically
|
|
137
131
|
source ~/.zshrc
|
|
138
132
|
```
|
|
139
133
|
|
|
140
134
|
### Project install (npm)
|
|
141
135
|
|
|
142
136
|
```bash
|
|
143
|
-
npm install skill-tags --save-dev
|
|
144
|
-
npx skill-tags --setup # choose: Project
|
|
137
|
+
npm install skill-tags --save-dev # setup wizard runs automatically
|
|
145
138
|
```
|
|
146
139
|
|
|
147
140
|
Adds `"skills": "st-skills"` to `package.json`. Use `npm run skills add <pkg>` to add project skills — auto-syncs `.cursor/commands/project-skill-tags.md` on every change.
|
|
@@ -153,7 +146,7 @@ npx skill-tags
|
|
|
153
146
|
```
|
|
154
147
|
|
|
155
148
|
### Install via curl
|
|
156
|
-
|
|
149
|
+
|
|
157
150
|
```bash
|
|
158
151
|
curl -fsSL https://raw.githubusercontent.com/steve-piece/skill-tags/main/install.sh | bash
|
|
159
152
|
```
|
|
@@ -181,11 +174,11 @@ skills add vercel-labs/agent-skills/vercel-react-best-practices
|
|
|
181
174
|
|
|
182
175
|
## Project-Level Install
|
|
183
176
|
|
|
184
|
-
Install skill-tags as a dev dependency to manage project-specific skills without touching your global shell config. The
|
|
177
|
+
Install skill-tags as a dev dependency to manage project-specific skills without touching your global shell config. The setup wizard runs automatically during install and adds a `"skills"` npm script backed by the bundled `st-skills` binary.
|
|
185
178
|
|
|
186
179
|
```bash
|
|
187
180
|
npm install skill-tags --save-dev
|
|
188
|
-
|
|
181
|
+
# choose: Project + Auto when prompted
|
|
189
182
|
```
|
|
190
183
|
|
|
191
184
|
After setup, `package.json` will include:
|
|
@@ -215,13 +208,15 @@ The `st-skills` binary (from this package) wraps `npx skills`, then automaticall
|
|
|
215
208
|
## CLI Reference
|
|
216
209
|
|
|
217
210
|
```bash
|
|
218
|
-
skill-tags
|
|
219
|
-
skill-tags --categories
|
|
220
|
-
skill-tags --
|
|
221
|
-
skill-tags --
|
|
222
|
-
skill-tags --
|
|
223
|
-
skill-tags --
|
|
224
|
-
skill-tags --
|
|
211
|
+
skill-tags # sync all skills, generate/update the command file
|
|
212
|
+
skill-tags --categories # open interactive category wizard
|
|
213
|
+
skill-tags --categories --local # category wizard for project-level skills only
|
|
214
|
+
skill-tags --setup # re-run setup wizard (runs automatically on first install)
|
|
215
|
+
skill-tags --global # skip local skills (.agents/skills in CWD); scan global sources only
|
|
216
|
+
skill-tags --local # scan only .agents/skills in CWD; write to .cursor/commands/project-skill-tags.md
|
|
217
|
+
skill-tags --latest # update skill-tags to the latest version
|
|
218
|
+
skill-tags --version # print version
|
|
219
|
+
skill-tags --help # show usage
|
|
225
220
|
```
|
|
226
221
|
|
|
227
222
|
### `st-skills` (project binary)
|
|
@@ -295,6 +290,47 @@ bash uninstall.sh
|
|
|
295
290
|
- bash or zsh
|
|
296
291
|
- [Cursor IDE](https://cursor.com)
|
|
297
292
|
|
|
293
|
+
## Changelog
|
|
294
|
+
|
|
295
|
+
### v1.5.0
|
|
296
|
+
|
|
297
|
+
- **`--latest` flag** — run `skill-tags --latest` to check npm for updates and self-update in place
|
|
298
|
+
- **Project-level categories** — `skill-tags --categories --local` runs the category wizard using only project skills (`.agents/skills/`), stores config in `.cursor/skill-tags-categories.conf`, and generates category files in `.cursor/commands/`
|
|
299
|
+
- **Simplified category wizard** — streamlined to three actions: Add categories (multi-select from predefined list), Edit categories (multi-select to remove), and Save changes (runs keyword matching on save)
|
|
300
|
+
- **Quieter sync from wizard** — `sync.sh` accepts `--quiet` to suppress verbose scan output; the category wizard uses it for cleaner post-save output with an aligned summary table
|
|
301
|
+
- **Removed custom category input** — categories are now limited to the predefined list for consistency
|
|
302
|
+
|
|
303
|
+
### v1.4.0
|
|
304
|
+
|
|
305
|
+
- Interactive `--categories` wizard with keyword-based auto-assignment
|
|
306
|
+
- Category config persistence at `~/.cursor/skill-tags-categories.conf`
|
|
307
|
+
- Auto-regeneration of category files on every sync
|
|
308
|
+
- Predefined categories: frontend, backend, database, testing, design, accessibility, performance, ai-agents, devops, marketing, mobile, documentation
|
|
309
|
+
|
|
310
|
+
### v1.3.0
|
|
311
|
+
|
|
312
|
+
- `--local` flag for project-level skill scanning
|
|
313
|
+
- `st-skills` binary for project-level add/remove/update with auto-sync
|
|
314
|
+
- Project setup mode in `--setup` wizard
|
|
315
|
+
|
|
316
|
+
### v1.2.0
|
|
317
|
+
|
|
318
|
+
- `--global` flag to skip local skills
|
|
319
|
+
- `--setup` wizard with Global and Project modes
|
|
320
|
+
- Shell wrapper auto-sync on `skills add/remove`
|
|
321
|
+
|
|
322
|
+
### v1.1.0
|
|
323
|
+
|
|
324
|
+
- Deduplication across skill sources (first-found wins by priority)
|
|
325
|
+
- Support for nested plugin cache directories
|
|
326
|
+
- Description extraction from YAML frontmatter and first content line
|
|
327
|
+
|
|
328
|
+
### v1.0.0
|
|
329
|
+
|
|
330
|
+
- Initial release
|
|
331
|
+
- Scans all known skill locations and generates `~/.cursor/commands/skill-tags.md`
|
|
332
|
+
- Priority-ordered skill sources with deduplication
|
|
333
|
+
|
|
298
334
|
## License
|
|
299
335
|
|
|
300
336
|
MIT
|
package/bin/categories.js
CHANGED
|
@@ -5,18 +5,26 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
-
const { checkbox, select
|
|
8
|
+
const { checkbox, select } = require('@inquirer/prompts');
|
|
9
9
|
const { spawnSync } = require('child_process');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const os = require('os');
|
|
13
13
|
|
|
14
14
|
const HOME = os.homedir();
|
|
15
|
-
const
|
|
16
|
-
const COMMANDS_DIR = path.join(HOME, '.cursor', 'commands');
|
|
17
|
-
const SKILL_TAGS_FILE = path.join(COMMANDS_DIR, 'skill-tags.md');
|
|
15
|
+
const IS_LOCAL = process.argv.includes('--local');
|
|
18
16
|
const SYNC_SCRIPT = path.join(__dirname, '..', 'sync.sh');
|
|
19
17
|
|
|
18
|
+
const CATEGORIES_CONFIG = IS_LOCAL
|
|
19
|
+
? path.join(process.cwd(), '.cursor', 'skill-tags-categories.conf')
|
|
20
|
+
: path.join(HOME, '.cursor', 'skill-tags-categories.conf');
|
|
21
|
+
const COMMANDS_DIR = IS_LOCAL
|
|
22
|
+
? path.join(process.cwd(), '.cursor', 'commands')
|
|
23
|
+
: path.join(HOME, '.cursor', 'commands');
|
|
24
|
+
const SKILL_TAGS_FILE = IS_LOCAL
|
|
25
|
+
? path.join(process.cwd(), '.cursor', 'commands', 'project-skill-tags.md')
|
|
26
|
+
: path.join(HOME, '.cursor', 'commands', 'skill-tags.md');
|
|
27
|
+
|
|
20
28
|
const PREDEFINED_CATEGORIES = [
|
|
21
29
|
'frontend',
|
|
22
30
|
'backend',
|
|
@@ -119,8 +127,10 @@ const CATEGORY_KEYWORDS = {
|
|
|
119
127
|
|
|
120
128
|
function ensureSkillTagsFile() {
|
|
121
129
|
if (fs.existsSync(SKILL_TAGS_FILE)) return;
|
|
122
|
-
|
|
123
|
-
|
|
130
|
+
const label = IS_LOCAL ? 'project-skill-tags.md' : 'skill-tags.md';
|
|
131
|
+
console.log(` ${label} not found — running initial sync...\n`);
|
|
132
|
+
const args = IS_LOCAL ? [SYNC_SCRIPT, '--local'] : [SYNC_SCRIPT];
|
|
133
|
+
spawnSync('bash', args, { stdio: 'inherit' });
|
|
124
134
|
}
|
|
125
135
|
|
|
126
136
|
function loadSkillsFromIndex() {
|
|
@@ -214,95 +224,64 @@ function toTitleCase(str) {
|
|
|
214
224
|
|
|
215
225
|
// ─── Wizard actions ──────────────────────────────────────────────────────────
|
|
216
226
|
|
|
217
|
-
async function addCategories(
|
|
227
|
+
async function addCategories(config) {
|
|
218
228
|
const existing = new Set(Object.keys(config));
|
|
219
229
|
const available = PREDEFINED_CATEGORIES.filter(c => !existing.has(c));
|
|
220
230
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
message: 'Select categories to add (space to toggle)',
|
|
225
|
-
choices: available.map(c => ({ name: toTitleCase(c), value: c })),
|
|
226
|
-
pageSize: 14,
|
|
227
|
-
});
|
|
228
|
-
} else {
|
|
229
|
-
console.log('\n All predefined categories already exist.');
|
|
231
|
+
if (available.length === 0) {
|
|
232
|
+
console.log('\n All categories already added.\n');
|
|
233
|
+
return;
|
|
230
234
|
}
|
|
231
235
|
|
|
232
|
-
const
|
|
233
|
-
message: '
|
|
236
|
+
const selected = await checkbox({
|
|
237
|
+
message: 'Select categories to add (space to toggle)',
|
|
238
|
+
choices: available.map(c => ({ name: toTitleCase(c), value: c })),
|
|
239
|
+
pageSize: 14,
|
|
234
240
|
});
|
|
235
241
|
|
|
236
|
-
if (custom.trim()) {
|
|
237
|
-
const normalized = custom.trim().toLowerCase()
|
|
238
|
-
.replace(/[^a-z0-9-]/g, '-')
|
|
239
|
-
.replace(/-+/g, '-')
|
|
240
|
-
.replace(/^-|-$/g, '');
|
|
241
|
-
if (normalized && !existing.has(normalized)) {
|
|
242
|
-
selected.push(normalized);
|
|
243
|
-
} else if (existing.has(normalized)) {
|
|
244
|
-
console.log(` "${normalized}" already exists — use Edit instead.`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
242
|
if (selected.length === 0) {
|
|
249
243
|
console.log(' No categories selected.\n');
|
|
250
244
|
return;
|
|
251
245
|
}
|
|
252
246
|
|
|
253
247
|
for (const cat of selected) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
console.log(` ✓ ${toTitleCase(cat)}: ${matched.length} skill(s) auto-assigned\n`);
|
|
248
|
+
config[cat] = [];
|
|
249
|
+
console.log(` + ${toTitleCase(cat)}`);
|
|
257
250
|
}
|
|
251
|
+
console.log();
|
|
258
252
|
}
|
|
259
253
|
|
|
260
|
-
async function
|
|
261
|
-
const cats = Object.keys(config);
|
|
262
|
-
if (cats.length === 0) {
|
|
263
|
-
console.log(' No categories yet. Add one first.\n');
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const cat = await select({
|
|
268
|
-
message: 'Which category to edit?',
|
|
269
|
-
choices: cats.map(c => ({
|
|
270
|
-
name: `${toTitleCase(c)} (${config[c].length} skills)`,
|
|
271
|
-
value: c,
|
|
272
|
-
})),
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
const matched = skills.filter(s => matchKeywords(s, cat));
|
|
276
|
-
config[cat] = matched.map(s => s.name);
|
|
277
|
-
console.log(` ✓ Re-generated ${toTitleCase(cat)}: ${matched.length} skill(s) auto-assigned\n`);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
async function deleteCategory(config) {
|
|
254
|
+
async function editCategories(config) {
|
|
281
255
|
const cats = Object.keys(config);
|
|
282
256
|
if (cats.length === 0) {
|
|
283
|
-
console.log(' No categories yet.\n');
|
|
257
|
+
console.log(' No categories yet. Add some first.\n');
|
|
284
258
|
return;
|
|
285
259
|
}
|
|
286
260
|
|
|
287
|
-
const
|
|
288
|
-
message: '
|
|
261
|
+
const kept = await checkbox({
|
|
262
|
+
message: 'Uncheck categories to remove (space to toggle)',
|
|
289
263
|
choices: cats.map(c => ({
|
|
290
264
|
name: `${toTitleCase(c)} (${config[c].length} skills)`,
|
|
291
265
|
value: c,
|
|
266
|
+
checked: true,
|
|
292
267
|
})),
|
|
268
|
+
pageSize: 14,
|
|
293
269
|
});
|
|
294
270
|
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
default: false,
|
|
298
|
-
});
|
|
271
|
+
const keptSet = new Set(kept);
|
|
272
|
+
const removed = cats.filter(c => !keptSet.has(c));
|
|
299
273
|
|
|
300
|
-
|
|
274
|
+
for (const cat of removed) {
|
|
301
275
|
delete config[cat];
|
|
302
276
|
const genFile = path.join(COMMANDS_DIR, `skills-${cat}.md`);
|
|
303
277
|
try { fs.unlinkSync(genFile); } catch {}
|
|
304
|
-
console.log(`
|
|
278
|
+
console.log(` - Removed: ${toTitleCase(cat)}`);
|
|
305
279
|
}
|
|
280
|
+
|
|
281
|
+
if (removed.length === 0) {
|
|
282
|
+
console.log(' No changes.');
|
|
283
|
+
}
|
|
284
|
+
console.log();
|
|
306
285
|
}
|
|
307
286
|
|
|
308
287
|
function printCurrentCategories(config) {
|
|
@@ -321,11 +300,13 @@ function printCurrentCategories(config) {
|
|
|
321
300
|
// ─── Main loop ───────────────────────────────────────────────────────────────
|
|
322
301
|
|
|
323
302
|
async function main() {
|
|
324
|
-
|
|
303
|
+
const mode = IS_LOCAL ? 'project' : 'global';
|
|
304
|
+
const indexFile = IS_LOCAL ? 'project-skill-tags.md' : 'skill-tags.md';
|
|
305
|
+
console.log(`\n skill-tags: category wizard (${mode})\n`);
|
|
325
306
|
|
|
326
307
|
ensureSkillTagsFile();
|
|
327
308
|
|
|
328
|
-
console.log(
|
|
309
|
+
console.log(` Loading skills from ${indexFile}...`);
|
|
329
310
|
const skills = loadSkillsFromIndex();
|
|
330
311
|
console.log(` Found ${skills.length} skill(s)\n`);
|
|
331
312
|
|
|
@@ -338,30 +319,60 @@ async function main() {
|
|
|
338
319
|
message: 'What would you like to do?',
|
|
339
320
|
choices: [
|
|
340
321
|
{ name: 'Add categories', value: 'add' },
|
|
341
|
-
{ name: 'Edit
|
|
342
|
-
{ name: '
|
|
343
|
-
{ name: 'Save & generate files', value: 'save' },
|
|
322
|
+
{ name: 'Edit categories', value: 'edit' },
|
|
323
|
+
{ name: 'Save changes', value: 'save' },
|
|
344
324
|
{ name: 'Quit without saving', value: 'quit' },
|
|
345
325
|
],
|
|
346
326
|
});
|
|
347
327
|
|
|
348
328
|
switch (action) {
|
|
349
329
|
case 'add':
|
|
350
|
-
await addCategories(
|
|
330
|
+
await addCategories(config);
|
|
351
331
|
break;
|
|
352
332
|
case 'edit':
|
|
353
|
-
await
|
|
354
|
-
break;
|
|
355
|
-
case 'delete':
|
|
356
|
-
await deleteCategory(config);
|
|
333
|
+
await editCategories(config);
|
|
357
334
|
break;
|
|
358
335
|
case 'save': {
|
|
336
|
+
const cats = Object.keys(config);
|
|
337
|
+
if (cats.length === 0) {
|
|
338
|
+
console.log(' No categories to save.\n');
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const pad = Math.max(...cats.map(c => toTitleCase(c).length));
|
|
343
|
+
|
|
344
|
+
console.log();
|
|
345
|
+
console.log(' ┌─ Matching skills to categories ─────────────────');
|
|
346
|
+
for (const cat of cats) {
|
|
347
|
+
const matched = skills.filter(s => matchKeywords(s, cat));
|
|
348
|
+
config[cat] = matched.map(s => s.name);
|
|
349
|
+
const label = toTitleCase(cat).padEnd(pad);
|
|
350
|
+
const count = String(matched.length).padStart(2);
|
|
351
|
+
console.log(` │ ✓ ${label} ${count} skill${matched.length === 1 ? '' : 's'}`);
|
|
352
|
+
}
|
|
353
|
+
console.log(' └─────────────────────────────────────────────────');
|
|
354
|
+
|
|
359
355
|
writeConfig(config);
|
|
360
|
-
|
|
361
|
-
const
|
|
356
|
+
|
|
357
|
+
const syncArgs = IS_LOCAL
|
|
358
|
+
? [SYNC_SCRIPT, '--local', '--quiet']
|
|
359
|
+
: [SYNC_SCRIPT, '--quiet'];
|
|
360
|
+
console.log(`\n Syncing ${skills.length} skills...\n`);
|
|
361
|
+
const result = spawnSync('bash', syncArgs, { stdio: 'inherit' });
|
|
362
362
|
if (result.error) {
|
|
363
|
-
console.error(` Error
|
|
363
|
+
console.error(` Error: ${result.error.message}`);
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const configPath = IS_LOCAL
|
|
368
|
+
? '.cursor/skill-tags-categories.conf'
|
|
369
|
+
: '~/.cursor/skill-tags-categories.conf';
|
|
370
|
+
console.log(` ✓ Config saved → ${configPath}`);
|
|
371
|
+
console.log(` ✓ ${cats.length} category file${cats.length === 1 ? '' : 's'} generated`);
|
|
372
|
+
for (const cat of cats) {
|
|
373
|
+
console.log(` @skills-${cat}.md`);
|
|
364
374
|
}
|
|
375
|
+
console.log(`\n Tip: type @skills-<category>.md in Cursor chat to use a category.\n`);
|
|
365
376
|
process.exit(result.status ?? 0);
|
|
366
377
|
}
|
|
367
378
|
case 'quit':
|
package/bin/postinstall.js
CHANGED
|
@@ -1,48 +1,82 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// bin/postinstall.js
|
|
3
3
|
// Runs automatically after `npm install skill-tags`.
|
|
4
|
-
//
|
|
4
|
+
// Launches interactive setup wizard on first install; re-syncs on reinstall.
|
|
5
|
+
// Falls back gracefully for non-interactive environments (CI, piped input).
|
|
5
6
|
// Never throws — a failed postinstall should never break npm install.
|
|
6
7
|
|
|
7
8
|
'use strict';
|
|
8
9
|
|
|
9
|
-
const { spawnSync } = require('child_process');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const fs = require('fs');
|
|
12
|
+
const os = require('os');
|
|
12
13
|
|
|
13
|
-
// Skip entirely on Windows
|
|
14
14
|
if (process.platform === 'win32') {
|
|
15
15
|
process.exit(0);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
const HOME = os.homedir();
|
|
18
19
|
const isGlobal = process.env.npm_config_global === 'true';
|
|
20
|
+
const SYNC_SCRIPT = path.join(__dirname, '..', 'sync.sh');
|
|
21
|
+
const WRAPPER_MARKER = '# ─── skill-tags / Cursor Skill Command Sync';
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
function isAlreadyConfigured() {
|
|
24
|
+
if (isGlobal) {
|
|
25
|
+
const shellName = path.basename(process.env.SHELL || 'bash');
|
|
26
|
+
let rcFile;
|
|
27
|
+
if (shellName === 'zsh') rcFile = path.join(HOME, '.zshrc');
|
|
28
|
+
else if (process.platform === 'darwin') rcFile = path.join(HOME, '.bash_profile');
|
|
29
|
+
else rcFile = path.join(HOME, '.bashrc');
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
try {
|
|
32
|
+
return fs.readFileSync(rcFile, 'utf-8').includes(WRAPPER_MARKER);
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
29
37
|
|
|
30
|
-
|
|
38
|
+
try {
|
|
39
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'));
|
|
40
|
+
return pkg.scripts?.skills === 'st-skills';
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
31
45
|
|
|
32
|
-
|
|
33
|
-
|
|
46
|
+
function runSync() {
|
|
47
|
+
if (!fs.existsSync(SYNC_SCRIPT)) return;
|
|
48
|
+
const { spawnSync } = require('child_process');
|
|
49
|
+
spawnSync('bash', [SYNC_SCRIPT], { stdio: 'inherit' });
|
|
34
50
|
}
|
|
35
51
|
|
|
36
|
-
|
|
52
|
+
async function main() {
|
|
53
|
+
if (isAlreadyConfigured()) {
|
|
54
|
+
if (isGlobal) {
|
|
55
|
+
console.log('\n skill-tags: re-syncing...\n');
|
|
56
|
+
runSync();
|
|
57
|
+
} else {
|
|
58
|
+
console.log('\n skill-tags: already configured.\n');
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
37
62
|
|
|
38
|
-
try {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
} catch (
|
|
42
|
-
|
|
63
|
+
try {
|
|
64
|
+
const runSetup = require('./setup');
|
|
65
|
+
await runSetup();
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if (err.name === 'ExitPromptError') {
|
|
68
|
+
console.log();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (isGlobal) {
|
|
72
|
+
console.log('\n skill-tags: running initial sync...\n');
|
|
73
|
+
runSync();
|
|
74
|
+
console.log(' To configure auto-sync, run: skill-tags --setup\n');
|
|
75
|
+
} else {
|
|
76
|
+
console.log('\n skill-tags installed as a project dependency.');
|
|
77
|
+
console.log(' Run setup to configure: npx skill-tags --setup\n');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
43
80
|
}
|
|
44
81
|
|
|
45
|
-
|
|
46
|
-
console.log(' skill-tags --setup\n');
|
|
47
|
-
|
|
48
|
-
process.exit(0);
|
|
82
|
+
main();
|
package/bin/skill-tags.js
CHANGED
|
@@ -37,9 +37,11 @@ if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
|
37
37
|
Options:
|
|
38
38
|
(none) Scan all skill sources and generate skill-tags.md
|
|
39
39
|
--categories Interactive category wizard (arrow keys + space to toggle)
|
|
40
|
+
--categories --local Category wizard for project-level skills only
|
|
40
41
|
--global Skip local skills (.agents/skills in CWD); scan global sources only
|
|
41
42
|
--local Scan only .agents/skills in CWD; write to .cursor/commands/project-skill-tags.md
|
|
42
43
|
--setup Interactive setup: choose Global (shell profile) or Project (package.json)
|
|
44
|
+
--latest Update skill-tags to the latest version
|
|
43
45
|
--version, -v Print version
|
|
44
46
|
--help, -h Show this help
|
|
45
47
|
|
|
@@ -55,9 +57,11 @@ if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
|
55
57
|
~/.cursor/commands/skill-tags.md full index of all skills (global)
|
|
56
58
|
~/.cursor/commands/skills-<category>.md generated by --categories
|
|
57
59
|
./.cursor/commands/project-skill-tags.md generated by --local
|
|
60
|
+
./.cursor/commands/skills-<category>.md generated by --categories --local
|
|
58
61
|
|
|
59
62
|
Config:
|
|
60
|
-
~/.cursor/skill-tags-categories.conf
|
|
63
|
+
~/.cursor/skill-tags-categories.conf global category config
|
|
64
|
+
./.cursor/skill-tags-categories.conf project category config (--categories --local)
|
|
61
65
|
|
|
62
66
|
Setup modes:
|
|
63
67
|
Global Adds skills() wrapper to ~/.zshrc — use: skills add <pkg>
|
|
@@ -77,6 +81,64 @@ if (process.argv.includes('--categories')) {
|
|
|
77
81
|
require('./categories');
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
// ─── --latest ────────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
else if (process.argv.includes('--latest')) {
|
|
87
|
+
const https = require('https');
|
|
88
|
+
|
|
89
|
+
function getLatestVersion() {
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
https.get('https://registry.npmjs.org/skill-tags/latest', { headers: { Accept: 'application/json' } }, res => {
|
|
92
|
+
let data = '';
|
|
93
|
+
res.on('data', chunk => { data += chunk; });
|
|
94
|
+
res.on('end', () => {
|
|
95
|
+
try { resolve(JSON.parse(data).version); }
|
|
96
|
+
catch { reject(new Error('Failed to parse registry response')); }
|
|
97
|
+
});
|
|
98
|
+
}).on('error', reject);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
(async () => {
|
|
103
|
+
console.log(`\n skill-tags v${VERSION}\n`);
|
|
104
|
+
|
|
105
|
+
let latest;
|
|
106
|
+
try {
|
|
107
|
+
process.stdout.write(' Checking for updates...');
|
|
108
|
+
latest = await getLatestVersion();
|
|
109
|
+
process.stdout.write(` v${latest}\n`);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.error(`\n Could not reach npm registry: ${err.message}\n`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (latest === VERSION) {
|
|
116
|
+
console.log(' Already on the latest version.\n');
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log(` Updating: v${VERSION} → v${latest}\n`);
|
|
121
|
+
const result = spawnSync('npm', ['install', '-g', 'skill-tags@latest'], {
|
|
122
|
+
stdio: 'inherit',
|
|
123
|
+
shell: true,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (result.error) {
|
|
127
|
+
console.error(`\n Update failed: ${result.error.message}`);
|
|
128
|
+
console.error(' Try manually: npm install -g skill-tags@latest\n');
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (result.status !== 0) {
|
|
133
|
+
console.error('\n Update failed. You may need to run with sudo:');
|
|
134
|
+
console.error(' sudo npm install -g skill-tags@latest\n');
|
|
135
|
+
process.exit(result.status);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log(`\n ✓ Updated to v${latest}\n`);
|
|
139
|
+
})();
|
|
140
|
+
}
|
|
141
|
+
|
|
80
142
|
// ─── --setup ─────────────────────────────────────────────────────────────────
|
|
81
143
|
|
|
82
144
|
else if (process.argv.includes('--setup')) {
|
package/package.json
CHANGED
package/sync.sh
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
set -euo pipefail
|
|
9
9
|
|
|
10
|
-
VERSION="1.
|
|
10
|
+
VERSION="1.5.0"
|
|
11
11
|
|
|
12
12
|
GLOBAL_COMMANDS_DIR="${HOME}/.cursor/commands"
|
|
13
13
|
OUTPUT_FILE="${GLOBAL_COMMANDS_DIR}/skill-tags.md"
|
|
@@ -36,11 +36,13 @@ trap 'rm -f "$SKILLS_TEMP"; rm -rf "$SKILLS_META_DIR"' EXIT
|
|
|
36
36
|
|
|
37
37
|
GLOBAL_ONLY=false
|
|
38
38
|
PROJECT_ONLY=false
|
|
39
|
+
QUIET=false
|
|
39
40
|
|
|
40
41
|
for arg in "$@"; do
|
|
41
42
|
case "$arg" in
|
|
42
43
|
--global) GLOBAL_ONLY=true ;;
|
|
43
44
|
--local) PROJECT_ONLY=true ;;
|
|
45
|
+
--quiet) QUIET=true ;;
|
|
44
46
|
esac
|
|
45
47
|
done
|
|
46
48
|
|
|
@@ -49,8 +51,8 @@ done
|
|
|
49
51
|
count_found=0
|
|
50
52
|
count_dupes=0
|
|
51
53
|
|
|
52
|
-
log() { printf " %s\n" "$*"; }
|
|
53
|
-
success() { printf " ✓ %s\n" "$*"; }
|
|
54
|
+
log() { [[ "$QUIET" == "true" ]] || printf " %s\n" "$*"; }
|
|
55
|
+
success() { [[ "$QUIET" == "true" ]] || printf " ✓ %s\n" "$*"; }
|
|
54
56
|
|
|
55
57
|
# Tracks which skill names have already been processed (deduplication).
|
|
56
58
|
# Uses a delimited string for bash 3.2 compatibility (no associative arrays).
|
|
@@ -192,10 +194,14 @@ scan_tree() {
|
|
|
192
194
|
|
|
193
195
|
# ─── Generate category files ────────────────────────────────────────────────────
|
|
194
196
|
|
|
195
|
-
# Reads
|
|
196
|
-
#
|
|
197
|
+
# Reads a categories config and writes a skills-<category>.md command file for each.
|
|
198
|
+
# Usage: generate_category_files [config_file] [commands_dir]
|
|
199
|
+
# Defaults to global paths when called without arguments.
|
|
197
200
|
generate_category_files() {
|
|
198
|
-
|
|
201
|
+
local config_file="${1:-$CATEGORIES_CONFIG}"
|
|
202
|
+
local commands_dir="${2:-$GLOBAL_COMMANDS_DIR}"
|
|
203
|
+
|
|
204
|
+
[[ -f "$config_file" ]] || return 0
|
|
199
205
|
|
|
200
206
|
local gen_count=0
|
|
201
207
|
|
|
@@ -205,7 +211,7 @@ generate_category_files() {
|
|
|
205
211
|
|
|
206
212
|
local title
|
|
207
213
|
title="$(to_title_case "$cat_name")"
|
|
208
|
-
local out="${
|
|
214
|
+
local out="${commands_dir}/skills-${cat_name}.md"
|
|
209
215
|
|
|
210
216
|
local skills_section=""
|
|
211
217
|
while IFS= read -r sname; do
|
|
@@ -247,16 +253,18 @@ EOF
|
|
|
247
253
|
|
|
248
254
|
gen_count=$(( gen_count + 1 ))
|
|
249
255
|
success "Generated: ${out/#$HOME/~}"
|
|
250
|
-
done < "$
|
|
256
|
+
done < "$config_file"
|
|
251
257
|
|
|
252
|
-
if [[ $gen_count -gt 0 ]]; then
|
|
258
|
+
if [[ $gen_count -gt 0 && "$QUIET" != "true" ]]; then
|
|
253
259
|
printf " Category files: %d generated\n" "$gen_count"
|
|
254
260
|
fi
|
|
255
261
|
}
|
|
256
262
|
|
|
257
263
|
# ─── Main ──────────────────────────────────────────────────────────────────────
|
|
258
264
|
|
|
259
|
-
|
|
265
|
+
if [[ "$QUIET" != "true" ]]; then
|
|
266
|
+
printf "\n skill-tags v%s — syncing skills\n\n" "$VERSION"
|
|
267
|
+
fi
|
|
260
268
|
|
|
261
269
|
if [[ "$PROJECT_ONLY" == "true" ]]; then
|
|
262
270
|
# ─── --local: scan only .agents/skills in CWD ───────────────────────────────
|
|
@@ -297,14 +305,19 @@ ${OPENING}
|
|
|
297
305
|
$(cat "$SKILLS_TEMP")
|
|
298
306
|
EOF
|
|
299
307
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
printf "
|
|
308
|
+
PROJECT_CATEGORIES_CONFIG="$(pwd)/.cursor/skill-tags-categories.conf"
|
|
309
|
+
generate_category_files "$PROJECT_CATEGORIES_CONFIG" "$PROJECT_COMMANDS_DIR"
|
|
310
|
+
|
|
311
|
+
if [[ "$QUIET" != "true" ]]; then
|
|
312
|
+
printf "\n"
|
|
313
|
+
if [[ "$is_update" == "true" ]]; then
|
|
314
|
+
printf " ✓ Updated: .cursor/commands/project-skill-tags.md\n"
|
|
315
|
+
else
|
|
316
|
+
printf " ✓ Generated: .cursor/commands/project-skill-tags.md\n"
|
|
317
|
+
fi
|
|
318
|
+
printf " Skills: %d indexed\n" "$count_found"
|
|
319
|
+
printf "\n Tip: type @project-skill-tags.md in Cursor chat to load the project skills reference.\n\n"
|
|
305
320
|
fi
|
|
306
|
-
printf " Skills: %d indexed\n" "$count_found"
|
|
307
|
-
printf "\n Tip: type @project-skill-tags.md in Cursor chat to load the project skills reference.\n\n"
|
|
308
321
|
|
|
309
322
|
else
|
|
310
323
|
# ─── Default: scan all sources ───────────────────────────────────────────────
|
|
@@ -352,16 +365,18 @@ EOF
|
|
|
352
365
|
|
|
353
366
|
generate_category_files
|
|
354
367
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
368
|
+
if [[ "$QUIET" != "true" ]]; then
|
|
369
|
+
printf "\n"
|
|
370
|
+
if [[ "$is_update" == "true" ]]; then
|
|
371
|
+
printf " ✓ Updated: %s\n" "${OUTPUT_FILE/#$HOME/~}"
|
|
372
|
+
else
|
|
373
|
+
printf " ✓ Generated: %s\n" "${OUTPUT_FILE/#$HOME/~}"
|
|
374
|
+
fi
|
|
375
|
+
printf " Skills: %d indexed\n" "$count_found"
|
|
376
|
+
if [[ $count_dupes -gt 0 ]]; then
|
|
377
|
+
printf " Duplicates: %d skipped (covered by higher-priority source)\n" "$count_dupes"
|
|
378
|
+
fi
|
|
379
|
+
printf "\n Tip: type @skill-tags.md in Cursor chat to load the full skills reference.\n\n"
|
|
364
380
|
fi
|
|
365
|
-
printf "\n Tip: type @skill-tags.md in Cursor chat to load the full skills reference.\n\n"
|
|
366
381
|
|
|
367
382
|
fi
|