@@ -186,4 +186,107 @@ describe("setupSkills", () => {
186186 const brewNote = notes . find ( ( n ) => n . title === "Homebrew recommended" ) ;
187187 expect ( brewNote ) . toBeDefined ( ) ;
188188 } ) ;
189+
190+ it ( "separates keyless dependency installs from keyed credential prompts" , async ( ) => {
191+ mocks . detectBinary . mockResolvedValue ( true ) ;
192+ mocks . installSkill . mockResolvedValue ( {
193+ ok : true ,
194+ message : "Installed" ,
195+ stdout : "" ,
196+ stderr : "" ,
197+ code : 0 ,
198+ } ) ;
199+ mocks . buildWorkspaceSkillStatus . mockReturnValue ( {
200+ workspaceDir : "/tmp/ws" ,
201+ managedSkillsDir : "/tmp/managed" ,
202+ skills : [
203+ {
204+ ...createBundledSkill ( {
205+ name : "openai-whisper" ,
206+ description : "Local Whisper transcription (no API key required)" ,
207+ bins : [ "whisper" ] ,
208+ installLabel : "Install whisper" ,
209+ } ) ,
210+ skillKey : "openai-whisper" ,
211+ primaryEnv : undefined ,
212+ missing : { bins : [ "whisper" ] , anyBins : [ ] , env : [ ] , config : [ ] , os : [ ] } ,
213+ } ,
214+ {
215+ ...createBundledSkill ( {
216+ name : "openai-whisper-api" ,
217+ description : "Cloud Whisper API" ,
218+ bins : [ "curl" ] ,
219+ installLabel : "Install curl" ,
220+ } ) ,
221+ skillKey : "openai-whisper-api" ,
222+ primaryEnv : "OPENAI_API_KEY" ,
223+ missing : {
224+ bins : [ "curl" ] ,
225+ anyBins : [ ] ,
226+ env : [ "OPENAI_API_KEY" ] ,
227+ config : [ ] ,
228+ os : [ ] ,
229+ } ,
230+ } ,
231+ {
232+ ...createBundledSkill ( {
233+ name : "sag" ,
234+ description : "Speech generation" ,
235+ bins : [ "ffmpeg" ] ,
236+ installLabel : "Install ffmpeg" ,
237+ } ) ,
238+ skillKey : "sag" ,
239+ primaryEnv : "ELEVENLABS_API_KEY" ,
240+ missing : {
241+ bins : [ "ffmpeg" ] ,
242+ anyBins : [ ] ,
243+ env : [ "ELEVENLABS_API_KEY" ] ,
244+ config : [ ] ,
245+ os : [ ] ,
246+ } ,
247+ } ,
248+ ] ,
249+ } as never ) ;
250+
251+ const notes : Array < { title ?: string ; message : string } > = [ ] ;
252+ const confirmMessages : string [ ] = [ ] ;
253+ const promptTexts : string [ ] = [ ] ;
254+ const prompter : WizardPrompter = {
255+ intro : vi . fn ( async ( ) => { } ) ,
256+ outro : vi . fn ( async ( ) => { } ) ,
257+ note : vi . fn ( async ( message : string , title ?: string ) => {
258+ notes . push ( { title, message } ) ;
259+ } ) ,
260+ select : vi . fn ( async ( ) => "npm" ) as unknown as WizardPrompter [ "select" ] ,
261+ multiselect : vi . fn ( async ( ) => [ "__skip__" ] ) as unknown as WizardPrompter [ "multiselect" ] ,
262+ text : vi . fn ( async ( { message } : { message : string } ) => {
263+ promptTexts . push ( message ) ;
264+ return "secret" ;
265+ } ) as unknown as WizardPrompter [ "text" ] ,
266+ confirm : vi . fn ( async ( { message } : { message : string } ) => {
267+ confirmMessages . push ( message ) ;
268+ if ( message === "Configure skills now? (recommended)" ) {
269+ return true ;
270+ }
271+ if ( message . startsWith ( "Set OPENAI_API_KEY" ) ) {
272+ return true ;
273+ }
274+ return false ;
275+ } ) as unknown as WizardPrompter [ "confirm" ] ,
276+ progress : vi . fn ( ( ) => ( { update : vi . fn ( ) , stop : vi . fn ( ) } ) ) ,
277+ } ;
278+
279+ await setupSkills ( { } as OpenClawConfig , "/tmp/ws" , runtime , prompter ) ;
280+
281+ const credentialNote = notes . find ( ( entry ) => entry . title === "Skill credentials" ) ;
282+ expect ( credentialNote ?. message ) . toContain ( "openai-whisper-api: OPENAI_API_KEY" ) ;
283+ expect ( credentialNote ?. message ) . toContain ( "sag: ELEVENLABS_API_KEY" ) ;
284+ expect ( credentialNote ?. message ) . not . toContain ( "openai-whisper:" ) ;
285+
286+ expect ( confirmMessages ) . toContain ( "Set OPENAI_API_KEY for openai-whisper-api?" ) ;
287+ expect ( confirmMessages ) . toContain ( "Set ELEVENLABS_API_KEY for sag?" ) ;
288+ expect ( confirmMessages ) . not . toContain ( "Set OPENAI_API_KEY for openai-whisper?" ) ;
289+ expect ( promptTexts ) . toContain ( "Enter OPENAI_API_KEY" ) ;
290+ expect ( promptTexts ) . not . toContain ( "Enter ELEVENLABS_API_KEY" ) ;
291+ } ) ;
189292} ) ;
0 commit comments