happyskills 0.24.0 → 0.25.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/CHANGELOG.md +10 -0
- package/package.json +1 -1
- package/src/commands/convert.js +22 -1
- package/src/commands/init.js +28 -8
- package/src/validation/skill_md_rules.js +13 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.25.0] - 2026-03-30
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Add `--agents` flag to `init` and `convert` commands for explicit agent targeting, matching `install` behavior
|
|
14
|
+
- Add `recommendations` array to `validate` description max_length error — provides a 4-step procedure (audit, lossless compression, lossy compression, verify) for safely shortening descriptions without breaking auto-invocation triggers
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Fix `init` not creating symlinks to secondary agents (Cursor, Windsurf, Codex, etc.) — newly scaffolded skills now use the same multi-agent linking as `install`
|
|
18
|
+
- Fix `convert` not creating symlinks to secondary agents — converted skills now get symlinked to all detected agents
|
|
19
|
+
|
|
10
20
|
## [0.24.0] - 2026-03-30
|
|
11
21
|
|
|
12
22
|
### Added
|
package/package.json
CHANGED
package/src/commands/convert.js
CHANGED
|
@@ -13,6 +13,7 @@ const { write_lock, update_lock_skills } = require('../lock/writer')
|
|
|
13
13
|
const { hash_directory } = require('../lock/integrity')
|
|
14
14
|
const { skills_dir, find_project_root, lock_root } = require('../config/paths')
|
|
15
15
|
const { file_exists } = require('../utils/fs')
|
|
16
|
+
const { resolve_agents, link_to_agents } = require('../agents')
|
|
16
17
|
const { create_spinner } = require('../ui/spinner')
|
|
17
18
|
const { print_help, print_success, print_error, print_info, print_warn, print_label, print_json } = require('../ui/output')
|
|
18
19
|
const { exit_with_error, UsageError, CliError } = require('../utils/errors')
|
|
@@ -26,10 +27,12 @@ Arguments:
|
|
|
26
27
|
skill-name Name of the skill in .claude/skills/
|
|
27
28
|
|
|
28
29
|
Options:
|
|
29
|
-
-g, --global Look in global skills (~/.
|
|
30
|
+
-g, --global Look in global skills (~/.agents/skills/)
|
|
30
31
|
--workspace <slug> Target workspace (if you have multiple)
|
|
31
32
|
--version <ver> Initial version (default: 1.0.0)
|
|
32
33
|
--keywords <tags> Comma-separated keywords (e.g., "deploy,aws,iac")
|
|
34
|
+
--agents <list> Target specific agents (comma-separated, e.g., claude,cursor)
|
|
35
|
+
Default: auto-detect. Env: HAPPYSKILLS_AGENTS
|
|
33
36
|
-y, --yes Skip confirmation prompt
|
|
34
37
|
|
|
35
38
|
Examples:
|
|
@@ -190,8 +193,25 @@ const run = (args) => catch_errors('Convert failed', async () => {
|
|
|
190
193
|
const [lock_err] = await write_lock(lock_dir, new_skills)
|
|
191
194
|
if (lock_err) { pub_spinner.fail('Failed to write lock file'); throw e('Lock write failed', lock_err) }
|
|
192
195
|
|
|
196
|
+
// Link to detected agents (non-fatal — warnings only)
|
|
197
|
+
const linked_agents = []
|
|
198
|
+
const [agents_err, agents_result] = await resolve_agents(args.flags.agents || undefined)
|
|
199
|
+
if (!agents_err && agents_result?.agents?.length > 0) {
|
|
200
|
+
pub_spinner.update(`Linking to ${agents_result.agents.length} agent(s)...`)
|
|
201
|
+
const [link_errs] = await link_to_agents(skill_dir, agents_result.agents, { global: is_global, project_root, skill_name })
|
|
202
|
+
if (link_errs) {
|
|
203
|
+
print_warn(`Warning: failed to link ${skill_name} to some agents`)
|
|
204
|
+
} else {
|
|
205
|
+
linked_agents.push(...agents_result.agents.map(a => a.id))
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
193
209
|
pub_spinner.succeed(`Converted ${full_name}@${merged_version}`)
|
|
194
210
|
|
|
211
|
+
if (linked_agents.length > 0) {
|
|
212
|
+
print_info(`Linked to: ${agents_result.agents.map(a => a.display_name).join(', ')}`)
|
|
213
|
+
}
|
|
214
|
+
|
|
195
215
|
if (args.flags.json) {
|
|
196
216
|
const json_data = {
|
|
197
217
|
skill: full_name,
|
|
@@ -199,6 +219,7 @@ const run = (args) => catch_errors('Convert failed', async () => {
|
|
|
199
219
|
workspace: workspace.slug,
|
|
200
220
|
description: merged_description || ''
|
|
201
221
|
}
|
|
222
|
+
if (linked_agents.length > 0) json_data.linked_agents = linked_agents
|
|
202
223
|
if (frontmatter_warnings.length > 0) json_data.warnings = frontmatter_warnings
|
|
203
224
|
print_json({ data: json_data })
|
|
204
225
|
return
|
package/src/commands/init.js
CHANGED
|
@@ -2,26 +2,29 @@ const path = require('path')
|
|
|
2
2
|
const { error: { catch_errors, wrap_errors: e } } = require('puffy-core')
|
|
3
3
|
const { write_manifest } = require('../manifest/writer')
|
|
4
4
|
const { write_file, file_exists, ensure_dir } = require('../utils/fs')
|
|
5
|
-
const { print_success, print_error, print_help, print_hint, print_json, code } = require('../ui/output')
|
|
5
|
+
const { print_success, print_error, print_help, print_hint, print_json, print_info, print_warn, code } = require('../ui/output')
|
|
6
6
|
const { exit_with_error, CliError, UsageError } = require('../utils/errors')
|
|
7
7
|
const { SKILL_JSON, SKILL_MD, EXIT_CODES, SKILL_TYPES, KIT_PREFIX } = require('../constants')
|
|
8
8
|
const { find_project_root, skills_dir } = require('../config/paths')
|
|
9
|
+
const { resolve_agents, link_to_agents } = require('../agents')
|
|
9
10
|
|
|
10
11
|
const HELP_TEXT = `Usage: happyskills init <name> [options]
|
|
11
12
|
|
|
12
|
-
Scaffold a new skill in .
|
|
13
|
+
Scaffold a new skill in .agents/skills/<name>/.
|
|
13
14
|
|
|
14
15
|
Creates:
|
|
15
|
-
.
|
|
16
|
-
.
|
|
16
|
+
.agents/skills/<name>/skill.json Skill manifest (name, version, deps)
|
|
17
|
+
.agents/skills/<name>/SKILL.md Skill instructions for AI agents
|
|
17
18
|
|
|
18
19
|
Arguments:
|
|
19
20
|
name Skill name (required, e.g., my-deploy-skill)
|
|
20
21
|
|
|
21
22
|
Options:
|
|
22
|
-
-g, --global
|
|
23
|
-
--kit
|
|
24
|
-
--
|
|
23
|
+
-g, --global Create in global skills (~/.agents/skills/)
|
|
24
|
+
--kit Initialize as a kit (collection of skills)
|
|
25
|
+
--agents <list> Target specific agents (comma-separated, e.g., claude,cursor)
|
|
26
|
+
Default: auto-detect. Env: HAPPYSKILLS_AGENTS
|
|
27
|
+
--json Output as JSON
|
|
25
28
|
|
|
26
29
|
Examples:
|
|
27
30
|
happyskills init my-deploy-skill
|
|
@@ -104,14 +107,31 @@ const run = (args) => catch_errors('Init failed', async () => {
|
|
|
104
107
|
|
|
105
108
|
const label = is_kit ? 'kit' : 'skill'
|
|
106
109
|
|
|
110
|
+
// Link to detected agents (non-fatal — warnings only)
|
|
111
|
+
const linked_agents = []
|
|
112
|
+
const [agents_err, agents_result] = await resolve_agents(args.flags.agents || undefined)
|
|
113
|
+
if (!agents_err && agents_result?.agents?.length > 0) {
|
|
114
|
+
const [link_errs] = await link_to_agents(dir, agents_result.agents, { global: is_global, project_root, skill_name: final_name })
|
|
115
|
+
if (link_errs) {
|
|
116
|
+
print_warn(`Warning: failed to link ${final_name} to some agents`)
|
|
117
|
+
} else {
|
|
118
|
+
linked_agents.push(...agents_result.agents.map(a => a.id))
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
107
122
|
if (args.flags.json) {
|
|
108
|
-
|
|
123
|
+
const data = { name: final_name, type: is_kit ? SKILL_TYPES.KIT : SKILL_TYPES.SKILL, files_created, directory: dir }
|
|
124
|
+
if (linked_agents.length > 0) data.linked_agents = linked_agents
|
|
125
|
+
print_json({ data })
|
|
109
126
|
return
|
|
110
127
|
}
|
|
111
128
|
|
|
112
129
|
print_success(`Initialized ${label} "${final_name}" at ${dir}`)
|
|
113
130
|
console.log(` ${SKILL_JSON} — manifest`)
|
|
114
131
|
console.log(` ${SKILL_MD} — ${is_kit ? 'kit description' : 'instructions'}`)
|
|
132
|
+
if (linked_agents.length > 0) {
|
|
133
|
+
print_info(`Linked to: ${agents_result.agents.map(a => a.display_name).join(', ')}`)
|
|
134
|
+
}
|
|
115
135
|
console.log()
|
|
116
136
|
print_hint(`Edit these files, then run ${code('happyskills publish')} to share.`)
|
|
117
137
|
}).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
|
|
@@ -74,7 +74,19 @@ const validate_description = (fm) => {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
if (desc.length > 1024) {
|
|
77
|
-
|
|
77
|
+
const deficit = desc.length - 1024
|
|
78
|
+
results.push({
|
|
79
|
+
...result('description', 'max_length', 'error', `Description is ${desc.length} chars (max 1024). Must reduce by ${deficit} chars.`, desc),
|
|
80
|
+
recommendations: [
|
|
81
|
+
'STEP 1 - AUDIT: Before changing anything, read the skill\'s routing table or capability list. Map each phrase in the description to the capability it triggers. Mark each phrase as: IDENTITY (describes what the skill is, usually one phrase), UNIQUE (the only phrase matching a specific capability), or REINFORCING (overlaps with another phrase\'s coverage).',
|
|
82
|
+
'STEP 2 - LOSSLESS COMPRESSION: Apply these transformations that reduce characters without changing semantic meaning: (a) Remove articles (a, an, the). (b) Remove possessives (my, your) when the subject is implied. (c) Remove filler verbs (do, does, can, have, is, am). (d) Merge parallel structures that share the same verb (e.g., \'install kit. publish kit\' becomes \'install, publish kits\') or the same object (e.g., \'find kits, search kits\' becomes \'find, search kits\'). Stop here if now under the limit.',
|
|
83
|
+
'STEP 3 - LOSSY COMPRESSION (only if still over the limit after step 2): Remove REINFORCING phrases only. When two phrases overlap semantically, remove the more general one and keep the more specific one. In synonym clusters, keep the two most commonly used verbs and remove the rest.',
|
|
84
|
+
'NEVER remove an IDENTITY phrase — it anchors the understanding of the skill\'s purpose and provides context for all other trigger phrases.',
|
|
85
|
+
'NEVER remove a UNIQUE trigger phrase — it is the only semantic path to that capability. Removing it makes that capability unreachable via auto-invocation.',
|
|
86
|
+
'NEVER rephrase a trigger in a way that changes the core verb or noun (e.g., \'how many skills installed\' must not become \'skill count\'). The original phrasing matches how users naturally speak.',
|
|
87
|
+
'STEP 4 - VERIFY: Cross-check the shortened description against the skill\'s routing table or capability list. Every documented capability must still have at least one semantically matching phrase in the description. If any capability lost coverage, restore its trigger phrase and find different savings.'
|
|
88
|
+
]
|
|
89
|
+
})
|
|
78
90
|
} else {
|
|
79
91
|
results.push(result('description', 'max_length', 'pass', `Description: ${desc.length} chars (max 1024)`))
|
|
80
92
|
}
|