@@ -98,6 +98,109 @@ export const TEMPLATES: TemplateEntry[] = [
9898 } ,
9999] ;
100100
101+ /**
102+ * Install one skill repo quietly. Captures `npx skills add` output instead of
103+ * inheriting it, so users see a single line per repo. Returns the number of
104+ * skills installed (parsed from the captured stdout), or null if unknown.
105+ *
106+ * In a TTY, shows a transient "↓ <repo>" line that gets overwritten by
107+ * "✓ <repo> (N skills)" on success. In a non-TTY, prints only the final line.
108+ */
109+ async function installSkillRepoQuiet (
110+ repo : string ,
111+ options : InitOptions
112+ ) : Promise < number | null > {
113+ if ( hasNpx ( ) ) {
114+ const args = buildSkillsInstallArgs ( {
115+ repo,
116+ agent : options . agent ,
117+ yes : options . yes || options . all || true ,
118+ global : true ,
119+ includeNpxYes : true ,
120+ } ) ;
121+
122+ const isTty = process . stdout . isTTY ;
123+ if ( isTty ) {
124+ process . stdout . write ( ` ${ dim } ↓ ${ repo } ${ reset } ` ) ;
125+ }
126+ try {
127+ const stdout = execSync ( args . join ( ' ' ) , {
128+ stdio : [ 'ignore' , 'pipe' , 'pipe' ] ,
129+ env : cleanNpmEnv ( ) ,
130+ } ) ;
131+ const count = parseSkillCount ( stdout ?. toString ( ) ?? '' ) ;
132+ const suffix = count != null ? ` ${ dim } (${ count } skills)${ reset } ` : '' ;
133+ if ( isTty ) {
134+ process . stdout . write (
135+ `\r ${ green } ✓${ reset } ${ repo } ${ suffix } \n`
136+ ) ;
137+ } else {
138+ console . log ( ` ${ green } ✓${ reset } ${ repo } ${ suffix } ` ) ;
139+ }
140+ return count ;
141+ } catch ( err ) {
142+ if ( isTty ) {
143+ process . stdout . write ( `\r ${ dim } ✗${ reset } ${ repo } \n` ) ;
144+ } else {
145+ console . log ( ` ${ dim } ✗${ reset } ${ repo } ` ) ;
146+ }
147+ const stderr =
148+ err && typeof err === 'object' && 'stderr' in err
149+ ? String ( ( err as { stderr : Buffer | string } ) . stderr || '' )
150+ : '' ;
151+ if ( stderr . trim ( ) ) {
152+ console . error (
153+ stderr
154+ . trim ( )
155+ . split ( '\n' )
156+ . map ( ( l ) => ` ${ dim } ${ l } ${ reset } ` )
157+ . join ( '\n' )
158+ ) ;
159+ }
160+ throw err ;
161+ }
162+ }
163+
164+ // No npx — fall back to native installer. It prints its own status.
165+ await installSkillsNative ( repo ) ;
166+ return null ;
167+ }
168+
169+ /** Parse "Found N skills" or "Installed N skills" from npx skills output. */
170+ function parseSkillCount ( output : string ) : number | null {
171+ const match = output . match ( / (?: F o u n d | I n s t a l l e d ) \s + ( \d + ) \s + s k i l l s ? / ) ;
172+ return match ? parseInt ( match [ 1 ] , 10 ) : null ;
173+ }
174+
175+ /**
176+ * Print the post-install next-steps block. Brief by design — confirms what was
177+ * installed and gives 4 entry points (AI prompt, direct CLI, MCP, help).
178+ */
179+ function printNextSteps ( skillCount : number | null ) : void {
180+ const arrow = `${ dim } →${ reset } ` ;
181+ const summary =
182+ skillCount != null
183+ ? `${ green } ✓${ reset } Installed ${ bold } ${ skillCount } skills${ reset } ${ dim } across your AI coding agents${ reset } `
184+ : `${ green } ✓${ reset } Skills installed ${ dim } across your AI coding agents${ reset } ` ;
185+
186+ console . log ( '' ) ;
187+ console . log ( ` ${ summary } ` ) ;
188+ console . log ( '' ) ;
189+ console . log (
190+ ` ${ arrow } ${ dim } Ask your AI:${ reset } "Use firecrawl to scrape pricing into JSON"`
191+ ) ;
192+ console . log (
193+ ` ${ arrow } ${ dim } Run direct: ${ reset } ${ bold } firecrawl scrape${ reset } https://example.com`
194+ ) ;
195+ console . log (
196+ ` ${ arrow } ${ dim } Add MCP: ${ reset } ${ bold } firecrawl setup mcp${ reset } `
197+ ) ;
198+ console . log (
199+ ` ${ arrow } ${ dim } All commands:${ reset } ${ bold } firecrawl --help${ reset } `
200+ ) ;
201+ console . log ( '' ) ;
202+ }
203+
101204async function stepInstall ( ) : Promise < boolean > {
102205 const { confirm } = await import ( '@inquirer/prompts' ) ;
103206 const shouldInstall = await confirm ( {
@@ -182,15 +285,15 @@ async function stepAuth(options: InitOptions): Promise<boolean> {
182285 }
183286}
184287
185- async function stepIntegrations ( options : InitOptions ) : Promise < void > {
288+ async function stepIntegrations ( options : InitOptions ) : Promise < number | null > {
186289 const { checkbox, confirm } = await import ( '@inquirer/prompts' ) ;
187290
188291 const wantIntegrations = await confirm ( {
189292 message : 'Set up integrations (skills, MCP, env)?' ,
190293 default : true ,
191294 } ) ;
192295
193- if ( ! wantIntegrations ) return ;
296+ if ( ! wantIntegrations ) return null ;
194297
195298 const integrations = await checkbox < string > ( {
196299 message : 'Which integrations?' ,
@@ -213,41 +316,22 @@ async function stepIntegrations(options: InitOptions): Promise<void> {
213316
214317 if ( integrations . length === 0 ) {
215318 console . log ( ` ${ dim } No integrations selected.${ reset } \n` ) ;
216- return ;
319+ return null ;
217320 }
218321
322+ let totalSkills : number | null = null ;
219323 for ( const integration of integrations ) {
220324 switch ( integration ) {
221325 case 'skills' : {
222- console . log ( `\n Setting up skills...` ) ;
326+ console . log ( `\n Installing skills...` ) ;
223327 for ( const repo of SKILL_REPOS ) {
224- if ( hasNpx ( ) ) {
225- const args = buildSkillsInstallArgs ( {
226- repo,
227- agent : options . agent ,
228- yes : options . yes || options . all ,
229- global : true ,
230- includeNpxYes : true ,
231- } ) ;
232- try {
233- execSync ( args . join ( ' ' ) , {
234- stdio : 'inherit' ,
235- env : cleanNpmEnv ( ) ,
236- } ) ;
237- console . log ( ` ${ green } ✓${ reset } Skills installed from ${ repo } ` ) ;
238- } catch {
239- console . error (
240- ` Failed to install skills from ${ repo } . Run "firecrawl setup skills" later.`
241- ) ;
242- }
243- } else {
244- try {
245- await installSkillsNative ( repo ) ;
246- } catch {
247- console . error (
248- ` Failed to install skills from ${ repo } . Run "firecrawl setup skills" later.`
249- ) ;
250- }
328+ try {
329+ const count = await installSkillRepoQuiet ( repo , options ) ;
330+ if ( count != null ) totalSkills = ( totalSkills ?? 0 ) + count ;
331+ } catch {
332+ console . error (
333+ ` ${ dim } Run "firecrawl setup skills" later to retry.${ reset } `
334+ ) ;
251335 }
252336 }
253337 break ;
@@ -297,7 +381,7 @@ async function stepIntegrations(options: InitOptions): Promise<void> {
297381 }
298382 }
299383 }
300- console . log ( '' ) ;
384+ return totalSkills ;
301385}
302386
303387function copyTemplateFiles (
@@ -555,16 +639,15 @@ export async function handleInitCommand(
555639 }
556640
557641 // Step 3: Integrations (skills, MCP, env)
642+ let skillCount : number | null = null ;
558643 if ( ! options . skipSkills ) {
559- await stepIntegrations ( options ) ;
644+ skillCount = await stepIntegrations ( options ) ;
560645 }
561646
562647 // Step 4: Template
563648 await stepTemplate ( ) ;
564649
565- console . log (
566- `${ green } ${ bold } Setup complete!${ reset } Run ${ dim } firecrawl --help${ reset } to get started.\n`
567- ) ;
650+ printNextSteps ( skillCount ) ;
568651}
569652
570653async function runNonInteractive ( options : InitOptions ) : Promise < void > {
@@ -633,46 +716,23 @@ async function runNonInteractive(options: InitOptions): Promise<void> {
633716 }
634717 }
635718
719+ let skillCount : number | null = null ;
636720 if ( ! options . skipSkills ) {
637721 console . log (
638722 `${ stepLabel ( ) } Installing firecrawl skills for AI coding agents...`
639723 ) ;
640724 for ( const repo of SKILL_REPOS ) {
641- if ( hasNpx ( ) ) {
642- const args = buildSkillsInstallArgs ( {
643- repo,
644- agent : options . agent ,
645- yes : true ,
646- global : true ,
647- includeNpxYes : true ,
648- } ) ;
649- try {
650- execSync ( args . join ( ' ' ) , {
651- stdio : 'inherit' ,
652- env : cleanNpmEnv ( ) ,
653- } ) ;
654- console . log ( `${ green } ✓${ reset } Skills installed from ${ repo } ` ) ;
655- } catch {
656- console . error (
657- `\nFailed to install skills from ${ repo } . You can retry with: firecrawl setup skills`
658- ) ;
659- process . exit ( 1 ) ;
660- }
661- } else {
662- try {
663- await installSkillsNative ( repo ) ;
664- } catch {
665- console . error (
666- `\nFailed to install skills from ${ repo } . You can retry with: firecrawl setup skills`
667- ) ;
668- process . exit ( 1 ) ;
669- }
725+ try {
726+ const count = await installSkillRepoQuiet ( repo , options ) ;
727+ if ( count != null ) skillCount = ( skillCount ?? 0 ) + count ;
728+ } catch {
729+ console . error (
730+ `\n${ dim } Failed to install skills from ${ repo } . Retry with: firecrawl setup skills${ reset } `
731+ ) ;
732+ process . exit ( 1 ) ;
670733 }
671734 }
672- console . log ( '' ) ;
673735 }
674736
675- console . log (
676- `${ green } ${ bold } Setup complete!${ reset } Run ${ dim } firecrawl --help${ reset } to get started.\n`
677- ) ;
737+ printNextSteps ( skillCount ) ;
678738}
0 commit comments