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 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
- ### Global (recommended for most users)
34
-
35
- 1. **Install:** `npm install skill-tags -g`
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
- skill-tags --setup # choose: Global
40
+ # Setup wizard runs automatically — choose Global + Auto (recommended)
43
41
  source ~/.zshrc
44
- skill-tags
45
42
  ```
46
43
 
47
- ### Project-level
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
- npx skill-tags --setup # choose: Projectadds "skills" script to package.json
56
-
48
+ # Setup wizard runs automaticallychoose 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 create, edit, and delete categories. 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.
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`. You can also create custom categories with any name.
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. Run `skill-tags --setup` when prompted, choose Global and Auto (recommended)
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 `--setup` wizard adds a `"skills"` npm script backed by the bundled `st-skills` binary.
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
- npx skill-tags --setup # choose: Project
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 # sync all skills, generate/update the command file
219
- skill-tags --categories # open interactive category wizard (CRUD)
220
- skill-tags --setup # interactive setup: choose Global (shell profile) or Project (package.json)
221
- skill-tags --global # skip local skills (.agents/skills in CWD); scan global sources only
222
- skill-tags --local # scan only .agents/skills in CWD; write to .cursor/commands/project-skill-tags.md
223
- skill-tags --version # print version
224
- skill-tags --help # show usage
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, input, confirm } = require('@inquirer/prompts');
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 CATEGORIES_CONFIG = path.join(HOME, '.cursor', 'skill-tags-categories.conf');
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
- console.log(' skill-tags.md not found — running initial sync...\n');
123
- spawnSync('bash', [SYNC_SCRIPT], { stdio: 'inherit' });
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(skills, config) {
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
- let selected = [];
222
- if (available.length > 0) {
223
- selected = await checkbox({
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 custom = await input({
233
- message: 'Custom category name (blank to skip):',
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
- const matched = skills.filter(s => matchKeywords(s, cat));
255
- config[cat] = matched.map(s => s.name);
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 editCategory(skills, config) {
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 cat = await select({
288
- message: 'Which category to delete?',
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 yes = await confirm({
296
- message: `Delete "${cat}" and its generated command file?`,
297
- default: false,
298
- });
271
+ const keptSet = new Set(kept);
272
+ const removed = cats.filter(c => !keptSet.has(c));
299
273
 
300
- if (yes) {
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(` Deleted: ${cat}\n`);
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
- console.log('\n skill-tags: category wizard\n');
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(' Loading skills from skill-tags.md...');
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 a category', value: 'edit' },
342
- { name: 'Delete a category', value: 'delete' },
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(skills, config);
330
+ await addCategories(config);
351
331
  break;
352
332
  case 'edit':
353
- await editCategory(skills, config);
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
- console.log('\n Config saved. Running sync...\n');
361
- const result = spawnSync('bash', [SYNC_SCRIPT], { stdio: 'inherit' });
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 running sync: ${result.error.message}`);
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':
@@ -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
- // Global installs: perform initial sync. Local installs: print setup guidance.
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
- if (!isGlobal) {
21
- // Project (local) install — print setup guidance and exit cleanly
22
- console.log('\n skill-tags installed as a project dependency.');
23
- console.log(' Run setup to add the "skills" npm script to your package.json:\n');
24
- console.log(' npx skill-tags --setup\n');
25
- process.exit(0);
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
- // ─── Global install: run initial sync ────────────────────────────────────────
31
+ try {
32
+ return fs.readFileSync(rcFile, 'utf-8').includes(WRAPPER_MARKER);
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
29
37
 
30
- const syncScript = path.join(__dirname, '..', 'sync.sh');
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
- if (!fs.existsSync(syncScript)) {
33
- process.exit(0);
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
- console.log('\n skill-tags: running initial sync...\n');
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
- const result = spawnSync('bash', [syncScript], { stdio: 'inherit' });
40
- if (result.error) throw result.error;
41
- } catch (_) {
42
- // Never fail the install
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
- console.log(' To auto-sync after every `skills add`, run:');
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skill-tags",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "Generates consolidated command files (@skill-tags.md and custom @skills-<category>.md indexes) for all installed Cursor skills.",
5
5
  "bin": {
6
6
  "skill-tags": "bin/skill-tags.js",
package/sync.sh CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  set -euo pipefail
9
9
 
10
- VERSION="1.3.0"
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 ~/.cursor/skill-tags-categories.conf and writes a skills-<category>.md
196
- # command file for each category. Called automatically on every sync when config exists.
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
- [[ -f "$CATEGORIES_CONFIG" ]] || return 0
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="${GLOBAL_COMMANDS_DIR}/skills-${cat_name}.md"
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 < "$CATEGORIES_CONFIG"
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
- printf "\n skill-tags v%s syncing skills\n\n" "$VERSION"
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
- printf "\n"
301
- if [[ "$is_update" == "true" ]]; then
302
- printf " ✓ Updated: .cursor/commands/project-skill-tags.md\n"
303
- else
304
- printf " ✓ Generated: .cursor/commands/project-skill-tags.md\n"
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
- printf "\n"
356
- if [[ "$is_update" == "true" ]]; then
357
- printf " ✓ Updated: %s\n" "${OUTPUT_FILE/#$HOME/~}"
358
- else
359
- printf " ✓ Generated: %s\n" "${OUTPUT_FILE/#$HOME/~}"
360
- fi
361
- printf " Skills: %d indexed\n" "$count_found"
362
- if [[ $count_dupes -gt 0 ]]; then
363
- printf " Duplicates: %d skipped (covered by higher-priority source)\n" "$count_dupes"
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