happyskills 0.14.0 → 0.15.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 +7 -0
- package/package.json +1 -1
- package/src/agents/detector.js +7 -18
- package/src/agents/index.js +2 -2
- package/src/agents/linker.js +10 -10
- package/src/agents/registry.js +11 -20
- package/src/config/paths.js +3 -3
- package/src/config/paths.test.js +5 -5
- package/src/engine/installer.js +9 -9
- package/src/engine/uninstaller.js +4 -4
- package/src/integration/cli.test.js +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.15.0] - 2026-03-27
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Move canonical skill install directory from `.claude/skills/` to `.agents/skills/` — physical files now live in an agent-neutral location, and ALL agents (including Claude Code) receive symlinks
|
|
14
|
+
- Move global lock root from `~/.claude/` to `~/.agents/`
|
|
15
|
+
- Remove primary/secondary agent distinction — all agents are equal, each gets a symlink from `.agents/skills/`
|
|
16
|
+
|
|
10
17
|
## [0.14.0] - 2026-03-27
|
|
11
18
|
|
|
12
19
|
### Added
|
package/package.json
CHANGED
package/src/agents/detector.js
CHANGED
|
@@ -2,7 +2,7 @@ const fs = require('fs')
|
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const os = require('os')
|
|
4
4
|
const { error: { catch_errors } } = require('puffy-core')
|
|
5
|
-
const { AGENTS,
|
|
5
|
+
const { AGENTS, get_agent } = require('./registry')
|
|
6
6
|
|
|
7
7
|
const home_dir = os.homedir()
|
|
8
8
|
|
|
@@ -34,7 +34,7 @@ const detect_agents = () => catch_errors('Agent detection failed', async () => {
|
|
|
34
34
|
* Parse a comma-separated agent list and validate each id against the registry.
|
|
35
35
|
*
|
|
36
36
|
* @param {string} agents_str — e.g. "claude,cursor,windsurf"
|
|
37
|
-
* @returns {{
|
|
37
|
+
* @returns {{ agents: Agent[] }}
|
|
38
38
|
*/
|
|
39
39
|
const _parse_agents_flag = (agents_str) => {
|
|
40
40
|
const ids = agents_str.split(',').map(s => s.trim()).filter(Boolean)
|
|
@@ -51,14 +51,7 @@ const _parse_agents_flag = (agents_str) => {
|
|
|
51
51
|
throw new Error(`Unknown agent(s): ${unknown.join(', ')}. Available: ${AGENTS.map(a => a.id).join(', ')}`)
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
if (!agents.find(a => a.primary)) {
|
|
56
|
-
agents.unshift(PRIMARY_AGENT)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const primary = agents.find(a => a.primary)
|
|
60
|
-
const secondaries = agents.filter(a => !a.primary)
|
|
61
|
-
return { primary, secondaries }
|
|
54
|
+
return { agents }
|
|
62
55
|
}
|
|
63
56
|
|
|
64
57
|
/**
|
|
@@ -67,10 +60,11 @@ const _parse_agents_flag = (agents_str) => {
|
|
|
67
60
|
* 2. HAPPYSKILLS_AGENTS env var (persistent user preference)
|
|
68
61
|
* 3. Auto-detection (scan home dir for agent config folders)
|
|
69
62
|
*
|
|
70
|
-
*
|
|
63
|
+
* Physical files always go to the canonical `.agents/skills/` directory.
|
|
64
|
+
* Each resolved agent gets a symlink from the canonical location.
|
|
71
65
|
*
|
|
72
66
|
* @param {string|undefined} agents_flag — from --agents CLI flag
|
|
73
|
-
* @returns {Promise} [errors, {
|
|
67
|
+
* @returns {Promise} [errors, { agents: Agent[] }]
|
|
74
68
|
*/
|
|
75
69
|
const resolve_agents = (agents_flag) => catch_errors('Agent resolution failed', async () => {
|
|
76
70
|
// 1. Explicit --agents flag takes highest priority
|
|
@@ -88,12 +82,7 @@ const resolve_agents = (agents_flag) => catch_errors('Agent resolution failed',
|
|
|
88
82
|
const [errors, detected] = await detect_agents()
|
|
89
83
|
if (errors) throw errors[0]
|
|
90
84
|
|
|
91
|
-
|
|
92
|
-
const all = detected.find(a => a.primary) ? detected : [PRIMARY_AGENT, ...detected]
|
|
93
|
-
|
|
94
|
-
const primary = all.find(a => a.primary)
|
|
95
|
-
const secondaries = all.filter(a => !a.primary)
|
|
96
|
-
return { primary, secondaries }
|
|
85
|
+
return { agents: detected }
|
|
97
86
|
})
|
|
98
87
|
|
|
99
88
|
module.exports = { detect_agents, resolve_agents }
|
package/src/agents/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const { AGENTS,
|
|
1
|
+
const { AGENTS, get_agent, get_all_agent_ids } = require('./registry')
|
|
2
2
|
const { detect_agents, resolve_agents } = require('./detector')
|
|
3
3
|
const { link_to_agents, unlink_from_agents, is_symlink } = require('./linker')
|
|
4
4
|
|
|
5
5
|
module.exports = {
|
|
6
|
-
AGENTS,
|
|
6
|
+
AGENTS, get_agent, get_all_agent_ids,
|
|
7
7
|
detect_agents, resolve_agents,
|
|
8
8
|
link_to_agents, unlink_from_agents, is_symlink
|
|
9
9
|
}
|
package/src/agents/linker.js
CHANGED
|
@@ -57,19 +57,19 @@ const _link_or_copy = async (source, target) => {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
* Link a skill to
|
|
61
|
-
* from the
|
|
60
|
+
* Link a skill to detected agents. Creates symlinks (or copies as fallback)
|
|
61
|
+
* from the canonical `.agents/skills/` location to each agent's skills directory.
|
|
62
62
|
*
|
|
63
|
-
* @param {string} source_dir — absolute path to the
|
|
64
|
-
* @param {Agent[]}
|
|
63
|
+
* @param {string} source_dir — absolute path to the canonical skill dir (e.g. .agents/skills/deploy-aws)
|
|
64
|
+
* @param {Agent[]} agents — array of agent objects to link
|
|
65
65
|
* @param {object} options — { global, project_root, skill_name }
|
|
66
66
|
* @returns {Promise} [errors, { agent_id, path, method }[]]
|
|
67
67
|
*/
|
|
68
|
-
const link_to_agents = (source_dir,
|
|
68
|
+
const link_to_agents = (source_dir, agents, options = {}) => catch_errors('Agent linking failed', async () => {
|
|
69
69
|
const { global: is_global = false, project_root, skill_name } = options
|
|
70
70
|
const results = []
|
|
71
71
|
|
|
72
|
-
for (const agent of
|
|
72
|
+
for (const agent of agents) {
|
|
73
73
|
const target = agent_skill_install_dir(agent, is_global, project_root, skill_name)
|
|
74
74
|
|
|
75
75
|
// If target already exists and is a symlink pointing to source, skip
|
|
@@ -102,18 +102,18 @@ const link_to_agents = (source_dir, secondaries, options = {}) => catch_errors('
|
|
|
102
102
|
})
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
|
-
* Unlink a skill from
|
|
105
|
+
* Unlink a skill from agents. Removes symlinks or physical copies.
|
|
106
106
|
*
|
|
107
107
|
* @param {string} skill_name — skill directory name (e.g. "deploy-aws")
|
|
108
|
-
* @param {Agent[]}
|
|
108
|
+
* @param {Agent[]} agents — array of agent objects to unlink
|
|
109
109
|
* @param {object} options — { global, project_root }
|
|
110
110
|
* @returns {Promise} [errors, { agent_id, removed: boolean }[]]
|
|
111
111
|
*/
|
|
112
|
-
const unlink_from_agents = (skill_name,
|
|
112
|
+
const unlink_from_agents = (skill_name, agents, options = {}) => catch_errors('Agent unlinking failed', async () => {
|
|
113
113
|
const { global: is_global = false, project_root } = options
|
|
114
114
|
const results = []
|
|
115
115
|
|
|
116
|
-
for (const agent of
|
|
116
|
+
for (const agent of agents) {
|
|
117
117
|
const target = agent_skill_install_dir(agent, is_global, project_root, skill_name)
|
|
118
118
|
|
|
119
119
|
if (!(await _exists(target))) {
|
package/src/agents/registry.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent registry — pure-data definitions for supported AI agents.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Physical skill files live in the canonical `.agents/skills/` directory.
|
|
5
|
+
* Each agent listed here gets a symlink from `.agents/skills/` to its own skills directory.
|
|
5
6
|
* Adding a new agent = adding one object to the AGENTS array.
|
|
6
7
|
*/
|
|
7
8
|
|
|
@@ -11,71 +12,61 @@ const AGENTS = [
|
|
|
11
12
|
display_name: 'Claude Code',
|
|
12
13
|
skills_dir: '.claude/skills',
|
|
13
14
|
global_skills_dir: '.claude/skills',
|
|
14
|
-
detect_paths: ['.claude']
|
|
15
|
-
primary: true
|
|
15
|
+
detect_paths: ['.claude']
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
id: 'cursor',
|
|
19
19
|
display_name: 'Cursor',
|
|
20
20
|
skills_dir: '.cursor/skills',
|
|
21
21
|
global_skills_dir: '.cursor/skills',
|
|
22
|
-
detect_paths: ['.cursor']
|
|
23
|
-
primary: false
|
|
22
|
+
detect_paths: ['.cursor']
|
|
24
23
|
},
|
|
25
24
|
{
|
|
26
25
|
id: 'windsurf',
|
|
27
26
|
display_name: 'Windsurf',
|
|
28
27
|
skills_dir: '.windsurf/skills',
|
|
29
28
|
global_skills_dir: '.windsurf/skills',
|
|
30
|
-
detect_paths: ['.windsurf']
|
|
31
|
-
primary: false
|
|
29
|
+
detect_paths: ['.windsurf']
|
|
32
30
|
},
|
|
33
31
|
{
|
|
34
32
|
id: 'codex',
|
|
35
33
|
display_name: 'Codex',
|
|
36
34
|
skills_dir: '.codex/skills',
|
|
37
35
|
global_skills_dir: '.codex/skills',
|
|
38
|
-
detect_paths: ['.codex']
|
|
39
|
-
primary: false
|
|
36
|
+
detect_paths: ['.codex']
|
|
40
37
|
},
|
|
41
38
|
{
|
|
42
39
|
id: 'copilot',
|
|
43
40
|
display_name: 'GitHub Copilot',
|
|
44
41
|
skills_dir: '.github/skills',
|
|
45
42
|
global_skills_dir: '.github/skills',
|
|
46
|
-
detect_paths: ['.github/copilot']
|
|
47
|
-
primary: false
|
|
43
|
+
detect_paths: ['.github/copilot']
|
|
48
44
|
},
|
|
49
45
|
{
|
|
50
46
|
id: 'aider',
|
|
51
47
|
display_name: 'Aider',
|
|
52
48
|
skills_dir: '.aider/skills',
|
|
53
49
|
global_skills_dir: '.aider/skills',
|
|
54
|
-
detect_paths: ['.aider']
|
|
55
|
-
primary: false
|
|
50
|
+
detect_paths: ['.aider']
|
|
56
51
|
},
|
|
57
52
|
{
|
|
58
53
|
id: 'cline',
|
|
59
54
|
display_name: 'Cline',
|
|
60
55
|
skills_dir: '.cline/skills',
|
|
61
56
|
global_skills_dir: '.cline/skills',
|
|
62
|
-
detect_paths: ['.cline']
|
|
63
|
-
primary: false
|
|
57
|
+
detect_paths: ['.cline']
|
|
64
58
|
},
|
|
65
59
|
{
|
|
66
60
|
id: 'roo',
|
|
67
61
|
display_name: 'Roo Code',
|
|
68
62
|
skills_dir: '.roo/skills',
|
|
69
63
|
global_skills_dir: '.roo/skills',
|
|
70
|
-
detect_paths: ['.roo']
|
|
71
|
-
primary: false
|
|
64
|
+
detect_paths: ['.roo']
|
|
72
65
|
}
|
|
73
66
|
]
|
|
74
67
|
|
|
75
|
-
const PRIMARY_AGENT = AGENTS.find(a => a.primary)
|
|
76
|
-
|
|
77
68
|
const get_agent = (id) => AGENTS.find(a => a.id === id) || null
|
|
78
69
|
|
|
79
70
|
const get_all_agent_ids = () => AGENTS.map(a => a.id)
|
|
80
71
|
|
|
81
|
-
module.exports = { AGENTS,
|
|
72
|
+
module.exports = { AGENTS, get_agent, get_all_agent_ids }
|
package/src/config/paths.js
CHANGED
|
@@ -14,9 +14,9 @@ const credentials_path = () => path.join(config_dir(), 'credentials.json')
|
|
|
14
14
|
|
|
15
15
|
const log_dir = () => path.join(config_dir(), 'logs')
|
|
16
16
|
|
|
17
|
-
const global_skills_dir = () => path.join(home_dir, '.
|
|
17
|
+
const global_skills_dir = () => path.join(home_dir, '.agents', 'skills')
|
|
18
18
|
|
|
19
|
-
const project_skills_dir = (project_root = process.cwd()) => path.join(project_root, '.
|
|
19
|
+
const project_skills_dir = (project_root = process.cwd()) => path.join(project_root, '.agents', 'skills')
|
|
20
20
|
|
|
21
21
|
const skills_dir = (global = false, project_root) => {
|
|
22
22
|
return global ? global_skills_dir() : project_skills_dir(project_root)
|
|
@@ -27,7 +27,7 @@ const tmp_dir = (base_skills_dir) => path.join(base_skills_dir, '.tmp')
|
|
|
27
27
|
const install_lock_path = (base_skills_dir) => path.join(base_skills_dir, '.install.lock')
|
|
28
28
|
|
|
29
29
|
const lock_root = (is_global, project_root) => {
|
|
30
|
-
return is_global ? path.join(home_dir, '.
|
|
30
|
+
return is_global ? path.join(home_dir, '.agents') : project_root
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
const lock_file_path = (project_root = process.cwd()) => path.join(project_root, 'skills-lock.json')
|
package/src/config/paths.test.js
CHANGED
|
@@ -46,16 +46,16 @@ describe('paths', () => {
|
|
|
46
46
|
})
|
|
47
47
|
|
|
48
48
|
describe('global_skills_dir', () => {
|
|
49
|
-
it('returns path under home .
|
|
49
|
+
it('returns path under home .agents/skills', () => {
|
|
50
50
|
const result = paths.global_skills_dir()
|
|
51
|
-
assert.strictEqual(result, path.join(os.homedir(), '.
|
|
51
|
+
assert.strictEqual(result, path.join(os.homedir(), '.agents', 'skills'))
|
|
52
52
|
})
|
|
53
53
|
})
|
|
54
54
|
|
|
55
55
|
describe('project_skills_dir', () => {
|
|
56
56
|
it('returns path under project root', () => {
|
|
57
57
|
const result = paths.project_skills_dir('/my/project')
|
|
58
|
-
assert.strictEqual(result, path.join('/my/project', '.
|
|
58
|
+
assert.strictEqual(result, path.join('/my/project', '.agents', 'skills'))
|
|
59
59
|
})
|
|
60
60
|
})
|
|
61
61
|
|
|
@@ -109,10 +109,10 @@ describe('paths', () => {
|
|
|
109
109
|
}
|
|
110
110
|
})
|
|
111
111
|
|
|
112
|
-
it('does not walk up to a parent with .
|
|
112
|
+
it('does not walk up to a parent with .agents/skills', () => {
|
|
113
113
|
const tmp = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'hs-test-')))
|
|
114
114
|
try {
|
|
115
|
-
fs.mkdirSync(path.join(tmp, '.
|
|
115
|
+
fs.mkdirSync(path.join(tmp, '.agents', 'skills'), { recursive: true })
|
|
116
116
|
const sub = path.join(tmp, 'sub')
|
|
117
117
|
fs.mkdirSync(sub, { recursive: true })
|
|
118
118
|
// should return sub, not tmp
|
package/src/engine/installer.js
CHANGED
|
@@ -24,10 +24,10 @@ const install = (skill, options = {}) => catch_errors('Install failed', async ()
|
|
|
24
24
|
const { version, global: is_global = false, force = false, fresh = false, project_root, agents: agents_flag } = options
|
|
25
25
|
const base_dir = skills_dir(is_global, project_root)
|
|
26
26
|
|
|
27
|
-
// Resolve target agents
|
|
27
|
+
// Resolve target agents
|
|
28
28
|
const [agents_err, agents_result] = await resolve_agents(agents_flag)
|
|
29
29
|
if (agents_err) throw e('Agent resolution failed', agents_err)
|
|
30
|
-
const {
|
|
30
|
+
const { agents } = agents_result
|
|
31
31
|
const temp_dir = tmp_dir(base_dir)
|
|
32
32
|
const lock_dir = lock_root(is_global, project_root)
|
|
33
33
|
|
|
@@ -136,13 +136,13 @@ const install = (skill, options = {}) => catch_errors('Install failed', async ()
|
|
|
136
136
|
await fs.promises.rename(tmp_path, final_dir)
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
// Link to
|
|
140
|
-
if (
|
|
141
|
-
spinner.update(`Linking to ${
|
|
139
|
+
// Link to detected agents (non-fatal — warnings only)
|
|
140
|
+
if (agents.length > 0) {
|
|
141
|
+
spinner.update(`Linking to ${agents.length} agent(s)...`)
|
|
142
142
|
for (const { pkg } of downloaded) {
|
|
143
143
|
const name = pkg.skill.split('/')[1]
|
|
144
144
|
const source = skill_install_dir(base_dir, name)
|
|
145
|
-
const [link_errs] = await link_to_agents(source,
|
|
145
|
+
const [link_errs] = await link_to_agents(source, agents, { global: is_global, project_root, skill_name: name })
|
|
146
146
|
if (link_errs) {
|
|
147
147
|
print_warn(`Warning: failed to link ${pkg.skill} to some agents`)
|
|
148
148
|
}
|
|
@@ -181,8 +181,8 @@ const install = (skill, options = {}) => catch_errors('Install failed', async ()
|
|
|
181
181
|
|
|
182
182
|
spinner.succeed(`Installed ${packages_to_install.length} package(s)`)
|
|
183
183
|
|
|
184
|
-
if (
|
|
185
|
-
print_info(`Linked to: ${
|
|
184
|
+
if (agents.length > 0) {
|
|
185
|
+
print_info(`Linked to: ${agents.map(a => a.display_name).join(', ')}`)
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
const [, missing_deps] = await check_system_dependencies(packages)
|
|
@@ -201,7 +201,7 @@ const install = (skill, options = {}) => catch_errors('Install failed', async ()
|
|
|
201
201
|
const warnings = _format_warnings(missing_deps)
|
|
202
202
|
const forced = forced_pkgs.map(p => ({ skill: p.skill, version: p.version }))
|
|
203
203
|
|
|
204
|
-
const linked_agents =
|
|
204
|
+
const linked_agents = agents.map(a => a.id)
|
|
205
205
|
return { skill, version: packages[0]?.version, no_op: false, installed, skipped, warnings, forced, packages: packages_to_install.length, linked_agents }
|
|
206
206
|
} catch (err) {
|
|
207
207
|
await remove_dir(temp_dir)
|
|
@@ -25,7 +25,7 @@ const uninstall = (skill, options = {}) => catch_errors('Uninstall failed', asyn
|
|
|
25
25
|
// Resolve target agents
|
|
26
26
|
const [agents_err, agents_result] = await resolve_agents(agents_flag)
|
|
27
27
|
if (agents_err) throw e('Agent resolution failed', agents_err)
|
|
28
|
-
const {
|
|
28
|
+
const { agents } = agents_result
|
|
29
29
|
const base_dir = skills_dir(is_global, project_root)
|
|
30
30
|
const lock_dir = lock_root(is_global, project_root)
|
|
31
31
|
|
|
@@ -56,10 +56,10 @@ const uninstall = (skill, options = {}) => catch_errors('Uninstall failed', asyn
|
|
|
56
56
|
if (rm_errors) throw e(`Failed to remove ${name}`, rm_errors)
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
// Unlink from
|
|
60
|
-
if (
|
|
59
|
+
// Unlink from detected agents
|
|
60
|
+
if (agents.length > 0) {
|
|
61
61
|
for (const name of to_remove) {
|
|
62
|
-
await unlink_from_agents(name.split('/')[1],
|
|
62
|
+
await unlink_from_agents(name.split('/')[1], agents, { global: is_global, project_root })
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -315,8 +315,8 @@ describe('CLI — --json: success responses use { data } envelope', () => {
|
|
|
315
315
|
it('init --json error when skill.json already exists returns { error }', () => {
|
|
316
316
|
const tmp = make_tmp()
|
|
317
317
|
try {
|
|
318
|
-
// Create .
|
|
319
|
-
const skill_dir = path.join(tmp, '.
|
|
318
|
+
// Create .agents/skills/test-skill/skill.json first
|
|
319
|
+
const skill_dir = path.join(tmp, '.agents', 'skills', 'test-skill')
|
|
320
320
|
fs.mkdirSync(skill_dir, { recursive: true })
|
|
321
321
|
fs.writeFileSync(path.join(skill_dir, 'skill.json'), '{}')
|
|
322
322
|
const { stdout, code } = run(['init', 'test-skill', '--json'], {}, { cwd: tmp })
|
|
@@ -533,7 +533,7 @@ describe('CLI — validate command', () => {
|
|
|
533
533
|
|
|
534
534
|
it('exits 0 for a valid skill and reports all checks passed', () => {
|
|
535
535
|
const tmp = make_tmp()
|
|
536
|
-
const skill_dir = path.join(tmp, '.
|
|
536
|
+
const skill_dir = path.join(tmp, '.agents', 'skills', 'test-skill')
|
|
537
537
|
fs.mkdirSync(skill_dir, { recursive: true })
|
|
538
538
|
fs.writeFileSync(path.join(skill_dir, 'SKILL.md'), '---\nname: test-skill\ndescription: A valid test skill for unit testing\n---\n\n# Test Skill\n')
|
|
539
539
|
fs.writeFileSync(path.join(skill_dir, 'skill.json'), JSON.stringify({
|
|
@@ -551,7 +551,7 @@ describe('CLI — validate command', () => {
|
|
|
551
551
|
|
|
552
552
|
it('exits 1 for an invalid skill', () => {
|
|
553
553
|
const tmp = make_tmp()
|
|
554
|
-
const skill_dir = path.join(tmp, '.
|
|
554
|
+
const skill_dir = path.join(tmp, '.agents', 'skills', 'bad-skill')
|
|
555
555
|
fs.mkdirSync(skill_dir, { recursive: true })
|
|
556
556
|
fs.writeFileSync(path.join(skill_dir, 'SKILL.md'), '---\nname: bad-skill\n---\n')
|
|
557
557
|
fs.writeFileSync(path.join(skill_dir, 'skill.json'), JSON.stringify({ name: 'bad-skill' }, null, '\t'))
|
|
@@ -565,7 +565,7 @@ describe('CLI — validate command', () => {
|
|
|
565
565
|
|
|
566
566
|
it('--json returns correct schema for a valid skill', () => {
|
|
567
567
|
const tmp = make_tmp()
|
|
568
|
-
const skill_dir = path.join(tmp, '.
|
|
568
|
+
const skill_dir = path.join(tmp, '.agents', 'skills', 'test-skill')
|
|
569
569
|
fs.mkdirSync(skill_dir, { recursive: true })
|
|
570
570
|
fs.writeFileSync(path.join(skill_dir, 'SKILL.md'), '---\nname: test-skill\ndescription: A valid test skill for unit testing\n---\n\n# Test\n')
|
|
571
571
|
fs.writeFileSync(path.join(skill_dir, 'skill.json'), JSON.stringify({
|
|
@@ -592,7 +592,7 @@ describe('CLI — validate command', () => {
|
|
|
592
592
|
|
|
593
593
|
it('--json returns errors for an invalid skill', () => {
|
|
594
594
|
const tmp = make_tmp()
|
|
595
|
-
const skill_dir = path.join(tmp, '.
|
|
595
|
+
const skill_dir = path.join(tmp, '.agents', 'skills', 'bad-skill')
|
|
596
596
|
fs.mkdirSync(skill_dir, { recursive: true })
|
|
597
597
|
fs.writeFileSync(path.join(skill_dir, 'SKILL.md'), '---\nname: bad-skill\n---\n')
|
|
598
598
|
fs.writeFileSync(path.join(skill_dir, 'skill.json'), JSON.stringify({ name: 'bad-skill' }, null, '\t'))
|