@@ -42,6 +42,19 @@ import { configureWebDefaults } from '../../utils/web-defaults';
4242import { getApiKey } from '../../utils/config' ;
4343import { browserLogin , isAuthenticated } from '../../utils/auth' ;
4444import { saveCredentials } from '../../utils/credentials' ;
45+ import { getCatalogSkills } from '../../utils/skill-catalog' ;
46+
47+ vi . mock ( '../../utils/skill-catalog' , ( ) => ( {
48+ getCatalogSkills : vi . fn ( async ( ) => [
49+ ...CLI_SKILLS . map ( ( name ) => ( { name, family : 'core' , files : { } } ) ) ,
50+ ...BUILD_SKILLS . map ( ( name ) => ( { name, family : 'build' , files : { } } ) ) ,
51+ ...WORKFLOW_SKILLS . map ( ( name ) => ( {
52+ name,
53+ family : 'workflows' ,
54+ files : { } ,
55+ } ) ) ,
56+ ] ) ,
57+ } ) ) ;
4558
4659vi . mock ( 'child_process' , ( ) => ( {
4760 execFileSync : vi . fn ( ) ,
@@ -106,6 +119,63 @@ describe('handleSetupCommand', () => {
106119 expect ( CLI_SKILLS ) . toContain ( 'firecrawl-alexandria' ) ;
107120 } ) ;
108121
122+ it ( 'refreshes the current workflow catalog, including skills absent from this CLI release' , async ( ) => {
123+ vi . mocked ( getCatalogSkills ) . mockResolvedValueOnce ( [
124+ { name : 'firecrawl-new-workflow' , family : 'workflows' , files : { } } ,
125+ { name : 'firecrawl-build' , family : 'build' , files : { } } ,
126+ ] ) ;
127+ await handleSetupCommand ( 'workflows' , { agent : 'cursor' } ) ;
128+ expectSkillsInstall (
129+ 'npx -y skills add firecrawl/skills --full-depth --global --yes --agent cursor --skill firecrawl-new-workflow'
130+ ) ;
131+ } ) ;
132+
133+ it . each ( [ 'Catalog unavailable' , 'HTTP 403' , 'Invalid catalog response' ] ) (
134+ 'falls back to release skills with a warning when discovery fails: %s' ,
135+ async ( message ) => {
136+ vi . spyOn ( console , 'error' ) . mockImplementation ( ( ) => { } ) ;
137+ vi . mocked ( getCatalogSkills ) . mockRejectedValueOnce ( new Error ( message ) ) ;
138+ await handleSetupCommand ( 'workflows' , { } ) ;
139+ expectSkillsInstall (
140+ `npx -y skills add firecrawl/skills --full-depth --global --yes ${ workflowSkillFlags } `
141+ ) ;
142+ expect ( console . error ) . toHaveBeenCalledWith (
143+ expect . stringContaining ( "using this CLI release's skill list" )
144+ ) ;
145+ }
146+ ) ;
147+
148+ it ( 'installs a newly published skill by full name once the catalog lists it' , async ( ) => {
149+ vi . mocked ( getCatalogSkills ) . mockResolvedValueOnce ( [
150+ { name : 'firecrawl-new-workflow' , family : 'workflows' , files : { } } ,
151+ ] ) ;
152+ await handleSetupCommand ( 'firecrawl-new-workflow' , { } ) ;
153+ expectSkillsInstall (
154+ 'npx -y skills add firecrawl/skills --full-depth --global --yes --skill firecrawl-new-workflow'
155+ ) ;
156+ } ) ;
157+
158+ it ( 'rejects a full skill name the catalog does not list' , async ( ) => {
159+ const exit = vi . spyOn ( process , 'exit' ) . mockImplementation ( ( (
160+ code ?: number
161+ ) => {
162+ throw new Error ( `process.exit:${ code } ` ) ;
163+ } ) as never ) ;
164+ await expect (
165+ handleSetupCommand ( 'firecrawl-typo-workflow' , { } )
166+ ) . rejects . toThrow ( 'process.exit:1' ) ;
167+ expect ( execFileSync ) . not . toHaveBeenCalled ( ) ;
168+ exit . mockRestore ( ) ;
169+ } ) ;
170+
171+ it ( 'accepts a full skill name when the catalog cannot be reached' , async ( ) => {
172+ vi . mocked ( getCatalogSkills ) . mockRejectedValueOnce ( new Error ( 'offline' ) ) ;
173+ await handleSetupCommand ( 'firecrawl-new-workflow' , { } ) ;
174+ expectSkillsInstall (
175+ 'npx -y skills add firecrawl/skills --full-depth --global --yes --skill firecrawl-new-workflow'
176+ ) ;
177+ } ) ;
178+
109179 it ( 'passes --agent to npx as one argument, never through a shell' , async ( ) => {
110180 const agent = 'cursor; touch /tmp/pwned' ;
111181 await handleSetupCommand ( 'core' , { agent } ) ;
0 commit comments