@victortomaili/skill-cli 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,13 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ### Added
11
- - "Star the repo" call to action in `skill --help` (with the `gh repo star` command) and the README.
12
-
13
10
  ### Planned
14
11
  - Cursor adapter (`.cursor/rules` format) for `init -g` bootstrap.
15
12
  - Per-agent hook adapters for automatic `/X` triggering (push model).
16
13
 
14
+ ## [0.1.1] - 2026-07-07
15
+
16
+ ### Fixed
17
+ - `skill trigger <name>` now loads a skill by exact name when no `triggers:` keyword matches. Skills without a `triggers:` field (e.g. description-triggered skills imported from the `skills` / `vercel-labs` ecosystem) were previously unreachable via `trigger`. A passive skill matched by name now shows an enable hint (`skill enable` / `skill cat`) instead of a dead-end "No active skill" message.
18
+
17
19
  ## [0.1.0] - 2026-07-07
18
20
 
19
21
  ### Added
@@ -46,5 +48,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
46
48
  - Windows `npx` spawn runs with `shell:false` + an args array and rejects sources
47
49
  containing shell metacharacters (`& | < > ^`).
48
50
 
49
- [Unreleased]: https://github.com/victortomaili/skill-cli/compare/v0.1.0...HEAD
51
+ [Unreleased]: https://github.com/victortomaili/skill-cli/compare/v0.1.1...HEAD
52
+ [0.1.1]: https://github.com/victortomaili/skill-cli/compare/v0.1.0...v0.1.1
50
53
  [0.1.0]: https://github.com/victortomaili/skill-cli/releases/tag/v0.1.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@victortomaili/skill-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Cross-agent skill manager — one global store, clean agent folders, activation via skill.config",
5
5
  "license": "MIT",
6
6
  "author": "Victor Tomaili <victor@tomaili.com>",
package/src/cli.js CHANGED
@@ -29,7 +29,7 @@ ${c.bold('Activation')}
29
29
  ${c.bold('Usage (agent)')}
30
30
  ${c.cyan('skill show')} ${c.gray('<name>')} metadata + path + triggers
31
31
  ${c.cyan('skill cat')} ${c.gray('<name>')} dump content to context
32
- ${c.cyan('skill trigger')} ${c.gray('<keyword>')} /X trigger: single→content, multi→candidates
32
+ ${c.cyan('skill trigger')} ${c.gray('<keyword|name>')} /X trigger or skill name content
33
33
 
34
34
  ${c.bold('Maintenance')}
35
35
  ${c.cyan('skill update')} ${c.gray('[name|--all]')} refresh store from source
@@ -4,43 +4,69 @@ import { readGlobalConfig, readProjectConfig, computeEffective } from '../lib/co
4
4
  import { normalizeTrigger } from '../lib/frontmatter.js'
5
5
  import { trunc } from '../lib/format.js'
6
6
 
7
- // /X trigger: keyword match across ACTIVE skills.
8
- // single → dump skill content (drops into context)
9
- // multi → candidate list (name + description); agent picks with `skill cat <name>`
10
- // none → informational
7
+ // Dump a single skill's body into context. Shared by the trigger-keyword and
8
+ // exact-name code paths so both render identically.
9
+ function loadSingle(skill, via) {
10
+ const s = readSkill(skill.name)
11
+ if (!s) { console.error(c.red('Skill not readable: ' + skill.name)); process.exit(1) }
12
+ console.log(c.green('▼ skill: ') + c.bold(s.name) + c.gray(' (' + via + ')'))
13
+ console.log(c.gray('━'.repeat(56)))
14
+ console.log(s.body.trim())
15
+ }
16
+
17
+ // `skill trigger <keyword|name>`:
18
+ // 1. keyword matches a trigger across ACTIVE skills
19
+ // single → dump content (drops into context)
20
+ // multi → candidate list (name + description); pick with `skill cat <name>`
21
+ // 2. no trigger match → exact NAME match among active skills. Lets you load
22
+ // description-triggered skills that have no `triggers:` field (e.g. skills
23
+ // imported from the `skills` / vercel-labs ecosystem) by name.
24
+ // 3. name matches only a PASSIVE skill → hint how to activate (or read via cat).
25
+ // 4. nothing → informational.
11
26
  export function cmdTrigger(args) {
12
27
  const keyword = normalizeTrigger(args[0] || '')
13
28
  if (!keyword) {
14
- console.error(c.red('Usage: skill trigger <keyword> (e.g. /research)'))
29
+ console.error(c.red('Usage: skill trigger <keyword|name> (e.g. /research, web-design-guidelines)'))
15
30
  process.exit(1)
16
31
  }
17
32
 
18
33
  const installed = listStore()
19
34
  const effective = computeEffective(installed, readGlobalConfig(), readProjectConfig())
20
- // Search ONLY active skills — a passive skill cannot trigger.
35
+
36
+ // 1) trigger-keyword match across ACTIVE skills — a passive skill cannot trigger.
21
37
  const candidates = installed.filter(s => effective.includes(s.name) && s.triggers.includes(keyword))
22
38
 
23
- if (candidates.length === 0) {
24
- console.log(c.yellow('No active skill has the ') + c.cyan('/' + keyword) + c.yellow(' trigger.'))
25
- console.log(c.gray(' Run skill list to see active skills and their triggers.'))
39
+ if (candidates.length >= 2) {
40
+ console.log(c.yellow('/' + keyword) + ' matched ' + c.bold(candidates.length + ' skills') + ' — pick one:')
41
+ console.log()
42
+ for (const s of candidates) {
43
+ console.log(' ' + c.cyan(s.name.padEnd(20)) + c.gray(' ' + trunc(s.description, 54)))
44
+ }
45
+ console.log()
46
+ console.log(c.gray('Pick: ') + 'skill cat <name>')
26
47
  return
27
48
  }
28
-
29
49
  if (candidates.length === 1) {
30
- const s = readSkill(candidates[0].name)
31
- if (!s) { console.error(c.red('Skill not readable: ' + candidates[0].name)); process.exit(1) }
32
- console.log(c.green('▼ skill: ') + c.bold(s.name) + c.gray(' (triggered: /' + keyword + ')'))
33
- console.log(c.gray('━'.repeat(56)))
34
- console.log(s.body.trim())
50
+ loadSingle(candidates[0], 'triggered: /' + keyword)
51
+ return
52
+ }
53
+
54
+ // 2) no trigger match → exact-name fallback among ACTIVE skills.
55
+ const byName = installed.filter(s => effective.includes(s.name) && s.name.toLowerCase() === keyword)
56
+ if (byName.length === 1) {
57
+ loadSingle(byName[0], 'name: ' + byName[0].name)
35
58
  return
36
59
  }
37
60
 
38
- console.log(c.yellow('/' + keyword) + ' matched ' + c.bold(candidates.length + ' skills') + ' — pick one:')
39
- console.log()
40
- for (const s of candidates) {
41
- console.log(' ' + c.cyan(s.name.padEnd(20)) + c.gray(' ' + trunc(s.description, 54)))
61
+ // 3) name matches only a PASSIVE skill it's installed but not active.
62
+ const passive = installed.find(s => !effective.includes(s.name) && s.name.toLowerCase() === keyword)
63
+ if (passive) {
64
+ console.log(c.yellow('Skill ') + c.bold(passive.name) + c.yellow(' is installed but not active.'))
65
+ console.log(c.gray(' Enable: ') + c.cyan('skill enable [-g] ' + passive.name) + c.gray(' · read now: ') + c.cyan('skill cat ' + passive.name))
66
+ return
42
67
  }
43
- console.log()
44
- console.log(c.gray('Pick: ') + 'skill cat <name>')
45
- }
46
68
 
69
+ // 4) nothing matched.
70
+ console.log(c.yellow('No active skill has the ') + c.cyan('/' + keyword) + c.yellow(' trigger.'))
71
+ console.log(c.gray(' Run skill list to see active skills and their triggers.'))
72
+ }