@@ -18,13 +18,17 @@ async function writeSkill(skillDir: string, name: string, description: string):
1818
1919describe ( 'listSkills' , ( ) => {
2020 const originalHome = process . env . HOME
21+ const originalClaudeConfigDir = process . env . CLAUDE_CONFIG_DIR
22+ const originalCodexHome = process . env . CODEX_HOME
2123 let sandboxDir : string
2224 let homeDir : string
2325
2426 beforeEach ( async ( ) => {
2527 sandboxDir = await mkdtemp ( join ( tmpdir ( ) , 'hapi-skills-' ) )
2628 homeDir = join ( sandboxDir , 'home' )
2729 process . env . HOME = homeDir
30+ delete process . env . CLAUDE_CONFIG_DIR
31+ delete process . env . CODEX_HOME
2832 await mkdir ( homeDir , { recursive : true } )
2933 } )
3034
@@ -35,6 +39,18 @@ describe('listSkills', () => {
3539 process . env . HOME = originalHome
3640 }
3741
42+ if ( originalClaudeConfigDir === undefined ) {
43+ delete process . env . CLAUDE_CONFIG_DIR
44+ } else {
45+ process . env . CLAUDE_CONFIG_DIR = originalClaudeConfigDir
46+ }
47+
48+ if ( originalCodexHome === undefined ) {
49+ delete process . env . CODEX_HOME
50+ } else {
51+ process . env . CODEX_HOME = originalCodexHome
52+ }
53+
3854 await rm ( sandboxDir , { recursive : true , force : true } )
3955 } )
4056
@@ -69,16 +85,139 @@ describe('listSkills', () => {
6985 expect ( skills . find ( ( s ) => s . name === 'alpha' ) ?. description ) . toBe ( 'Alpha from agents' )
7086 } )
7187
72- it ( 'lists user skills from ~/.codex/skills including Codex bundled system skills' , async ( ) => {
88+ it ( 'lists user skills from ~/.codex/skills including Codex bundled system skills for Codex ' , async ( ) => {
7389 await writeSkill ( join ( homeDir , '.agents' , 'skills' , 'amis' ) , 'amis' , 'AMIS guide' )
7490 await writeSkill ( join ( homeDir , '.codex' , 'skills' , 'hello-agents' ) , 'helloagents' , 'Main skill' )
7591 await writeSkill ( join ( homeDir , '.codex' , 'skills' , '.system' , 'skill-creator' ) , 'skill-creator' , 'Create skills' )
7692
77- const skills = await listSkills ( )
93+ const skills = await listSkills ( undefined , { flavor : 'codex' } )
7894
7995 expect ( skills . map ( ( skill ) => skill . name ) ) . toEqual ( [ 'amis' , 'helloagents' , 'skill-creator' ] )
8096 } )
8197
98+ it ( 'scopes user skills to the requested flavor' , async ( ) => {
99+ await writeSkill ( join ( homeDir , '.agents' , 'skills' , 'shared' ) , 'shared' , 'Shared skill' )
100+ await writeSkill ( join ( homeDir , '.claude' , 'skills' , 'claude-only' ) , 'claude-only' , 'Claude skill' )
101+ await writeSkill ( join ( homeDir , '.codex' , 'skills' , 'codex-only' ) , 'codex-only' , 'Codex skill' )
102+
103+ const claudeSkills = await listSkills ( undefined , { flavor : 'claude' } )
104+ const codexSkills = await listSkills ( undefined , { flavor : 'codex' } )
105+
106+ expect ( claudeSkills . map ( ( skill ) => skill . name ) ) . toEqual ( [ 'claude-only' , 'shared' ] )
107+ expect ( codexSkills . map ( ( skill ) => skill . name ) ) . toEqual ( [ 'codex-only' , 'shared' ] )
108+ } )
109+
110+ it ( 'uses configured Claude and Codex homes for agent-specific user skills' , async ( ) => {
111+ const claudeConfigDir = join ( sandboxDir , 'custom-claude' )
112+ const codexHome = join ( sandboxDir , 'custom-codex' )
113+ process . env . CLAUDE_CONFIG_DIR = claudeConfigDir
114+ process . env . CODEX_HOME = codexHome
115+
116+ await writeSkill ( join ( homeDir , '.agents' , 'skills' , 'shared' ) , 'shared' , 'Shared skill' )
117+ await writeSkill ( join ( homeDir , '.claude' , 'skills' , 'default-claude' ) , 'default-claude' , 'Default Claude skill' )
118+ await writeSkill ( join ( homeDir , '.codex' , 'skills' , 'default-codex' ) , 'default-codex' , 'Default Codex skill' )
119+ await writeSkill ( join ( claudeConfigDir , 'skills' , 'custom-claude' ) , 'custom-claude' , 'Custom Claude skill' )
120+ await writeSkill ( join ( codexHome , 'skills' , 'custom-codex' ) , 'custom-codex' , 'Custom Codex skill' )
121+
122+ const claudeSkills = await listSkills ( undefined , { flavor : 'claude' } )
123+ const codexSkills = await listSkills ( undefined , { flavor : 'codex' } )
124+
125+ expect ( claudeSkills . map ( ( skill ) => skill . name ) ) . toEqual ( [ 'custom-claude' , 'shared' ] )
126+ expect ( codexSkills . map ( ( skill ) => skill . name ) ) . toEqual ( [ 'custom-codex' , 'shared' ] )
127+ } )
128+
129+ it ( 'includes Codex system skills from a configured CODEX_HOME' , async ( ) => {
130+ const codexHome = join ( sandboxDir , 'custom-codex' )
131+ process . env . CODEX_HOME = codexHome
132+
133+ await writeSkill ( join ( codexHome , 'skills' , 'custom-codex' ) , 'custom-codex' , 'Custom Codex skill' )
134+ await writeSkill ( join ( codexHome , 'skills' , '.system' , 'custom-system' ) , 'custom-system' , 'Custom Codex system skill' )
135+
136+ const skills = await listSkills ( undefined , { flavor : 'codex' } )
137+
138+ expect ( skills . map ( ( skill ) => skill . name ) ) . toEqual ( [ 'custom-codex' , 'custom-system' ] )
139+ } )
140+
141+ it ( 'includes installed marketplace skills for the requested flavor' , async ( ) => {
142+ const claudeInstallPath = join ( homeDir , '.claude' , 'plugins' , 'cache' , 'owner' , 'claude-plugin' , '1.0.0' )
143+ const codexInstallPath = join ( homeDir , '.codex' , 'plugins' , 'cache' , 'owner' , 'codex-plugin' , '1.0.0' )
144+ await writeSkill ( join ( claudeInstallPath , 'skills' , 'claude-market' ) , 'claude-market' , 'Claude marketplace skill' )
145+ await writeSkill ( join ( codexInstallPath , 'skills' , 'codex-market' ) , 'codex-market' , 'Codex marketplace skill' )
146+ await writeFile ( join ( homeDir , '.claude' , 'plugins' , 'installed_plugins.json' ) , JSON . stringify ( {
147+ plugins : {
148+ 'claude-plugin@owner' : [ { installPath : claudeInstallPath , lastUpdated : '2026-01-02T00:00:00.000Z' } ]
149+ }
150+ } ) )
151+ await writeFile ( join ( homeDir , '.codex' , 'plugins' , 'installed_plugins.json' ) , JSON . stringify ( {
152+ plugins : {
153+ 'codex-plugin@owner' : [ { installPath : codexInstallPath , lastUpdated : '2026-01-02T00:00:00.000Z' } ]
154+ }
155+ } ) )
156+
157+ const claudeSkills = await listSkills ( undefined , { flavor : 'claude' } )
158+ const codexSkills = await listSkills ( undefined , { flavor : 'codex' } )
159+
160+ expect ( claudeSkills . map ( ( skill ) => skill . name ) ) . toEqual ( [ 'claude-market' ] )
161+ expect ( codexSkills . map ( ( skill ) => skill . name ) ) . toEqual ( [ 'codex-market' ] )
162+ } )
163+
164+ it ( 'uses configured Claude and Codex homes for installed marketplace skills' , async ( ) => {
165+ const claudeConfigDir = join ( sandboxDir , 'custom-claude' )
166+ const codexHome = join ( sandboxDir , 'custom-codex' )
167+ process . env . CLAUDE_CONFIG_DIR = claudeConfigDir
168+ process . env . CODEX_HOME = codexHome
169+
170+ const claudeInstallPath = join ( claudeConfigDir , 'plugins' , 'cache' , 'owner' , 'claude-plugin' , '1.0.0' )
171+ const codexInstallPath = join ( codexHome , 'plugins' , 'cache' , 'owner' , 'codex-plugin' , '1.0.0' )
172+ await writeSkill ( join ( claudeInstallPath , 'skills' , 'claude-market' ) , 'claude-market' , 'Claude marketplace skill' )
173+ await writeSkill ( join ( codexInstallPath , 'skills' , 'codex-market' ) , 'codex-market' , 'Codex marketplace skill' )
174+ await writeFile ( join ( claudeConfigDir , 'plugins' , 'installed_plugins.json' ) , JSON . stringify ( {
175+ plugins : {
176+ 'claude-plugin@owner' : [ { installPath : claudeInstallPath , lastUpdated : '2026-01-02T00:00:00.000Z' } ]
177+ }
178+ } ) )
179+ await writeFile ( join ( codexHome , 'plugins' , 'installed_plugins.json' ) , JSON . stringify ( {
180+ plugins : {
181+ 'codex-plugin@owner' : [ { installPath : codexInstallPath , lastUpdated : '2026-01-02T00:00:00.000Z' } ]
182+ }
183+ } ) )
184+
185+ const claudeSkills = await listSkills ( undefined , { flavor : 'claude' } )
186+ const codexSkills = await listSkills ( undefined , { flavor : 'codex' } )
187+
188+ expect ( claudeSkills . map ( ( skill ) => skill . name ) ) . toEqual ( [ 'claude-market' ] )
189+ expect ( codexSkills . map ( ( skill ) => skill . name ) ) . toEqual ( [ 'codex-market' ] )
190+ } )
191+
192+ it ( 'does not list cached marketplace skills that are not installed' , async ( ) => {
193+ await writeSkill ( join ( homeDir , '.codex' , 'plugins' , 'cache' , 'owner' , 'stale-plugin' , '1.0.0' , 'skills' , 'stale-market' ) , 'stale-market' , 'Stale marketplace skill' )
194+ await mkdir ( join ( homeDir , '.codex' , 'plugins' ) , { recursive : true } )
195+ await writeFile ( join ( homeDir , '.codex' , 'plugins' , 'installed_plugins.json' ) , JSON . stringify ( { plugins : { } } ) )
196+
197+ const skills = await listSkills ( undefined , { flavor : 'codex' } )
198+
199+ expect ( skills . map ( ( skill ) => skill . name ) ) . toEqual ( [ ] )
200+ } )
201+
202+ it ( 'uses the newest installed plugin path when multiple installations exist' , async ( ) => {
203+ const oldInstallPath = join ( homeDir , '.codex' , 'plugins' , 'cache' , 'owner' , 'codex-plugin' , '1.0.0' )
204+ const newInstallPath = join ( homeDir , '.codex' , 'plugins' , 'cache' , 'owner' , 'codex-plugin' , '2.0.0' )
205+ await writeSkill ( join ( oldInstallPath , 'skills' , 'plugin-skill' ) , 'plugin-skill' , 'Old marketplace skill' )
206+ await writeSkill ( join ( newInstallPath , 'skills' , 'plugin-skill' ) , 'plugin-skill' , 'New marketplace skill' )
207+ await writeFile ( join ( homeDir , '.codex' , 'plugins' , 'installed_plugins.json' ) , JSON . stringify ( {
208+ plugins : {
209+ 'codex-plugin@owner' : [
210+ { installPath : oldInstallPath , lastUpdated : '2026-01-01T00:00:00.000Z' } ,
211+ { installPath : newInstallPath , lastUpdated : '2026-01-02T00:00:00.000Z' }
212+ ]
213+ }
214+ } ) )
215+
216+ const skills = await listSkills ( undefined , { flavor : 'codex' } )
217+
218+ expect ( skills ) . toEqual ( [ { name : 'plugin-skill' , description : 'New marketplace skill' } ] )
219+ } )
220+
82221 it ( 'falls back to directory name when frontmatter is missing' , async ( ) => {
83222 const skillDir = join ( homeDir , '.agents' , 'skills' , 'no-frontmatter' )
84223 await mkdir ( skillDir , { recursive : true } )
@@ -100,7 +239,7 @@ describe('listSkills', () => {
100239 await writeSkill ( join ( workingDirectory , '.agents' , 'skills' , 'local-skill' ) , 'local-skill' , 'Local skill' )
101240 await writeSkill ( join ( sandboxDir , '.agents' , 'skills' , 'outside-skill' ) , 'outside-skill' , 'Outside repo skill' )
102241
103- const skills = await listSkills ( workingDirectory )
242+ const skills = await listSkills ( workingDirectory , { flavor : 'claude' } )
104243
105244 expect ( skills . map ( ( skill ) => skill . name ) ) . toEqual ( [ 'local-skill' , 'package-skill' , 'root-skill' ] )
106245 } )
@@ -113,7 +252,7 @@ describe('listSkills', () => {
113252 await writeSkill ( join ( repoRoot , '.claude' , 'skills' , 'claude-root' ) , 'claude-root' , 'Claude root skill' )
114253 await writeSkill ( join ( workingDirectory , '.claude' , 'skills' , 'claude-local' ) , 'claude-local' , 'Claude local skill' )
115254
116- const skills = await listSkills ( workingDirectory )
255+ const skills = await listSkills ( workingDirectory , { flavor : 'claude' } )
117256
118257 expect ( skills . map ( ( skill ) => skill . name ) ) . toEqual ( [ 'claude-local' , 'claude-root' ] )
119258 } )
@@ -127,7 +266,7 @@ describe('listSkills', () => {
127266 await writeSkill ( join ( workingDirectory , '.codex' , 'skills' , 'codex-local' ) , 'codex-local' , 'Codex local skill' )
128267 await writeSkill ( join ( workingDirectory , '.codex' , 'skills' , '.system' , 'codex-system' ) , 'codex-system' , 'Codex system skill' )
129268
130- const skills = await listSkills ( workingDirectory )
269+ const skills = await listSkills ( workingDirectory , { flavor : 'codex' } )
131270
132271 expect ( skills . map ( ( skill ) => skill . name ) ) . toEqual ( [ 'codex-local' , 'codex-root' , 'codex-system' ] )
133272 } )
0 commit comments