diff --git a/README.md b/README.md index 7b5a414b9..c64bae568 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ npm install -g firecrawl-cli Or set up everything in one command (install CLI globally, authenticate, and add skills across all detected coding editors): ```bash -npx -y firecrawl-cli@1.14.1 init -y --browser +npx -y firecrawl-cli@1.14.3 init -y --browser ``` - `-y` runs setup non-interactively @@ -583,7 +583,7 @@ firecrawl --status ``` ``` - 🔥 firecrawl cli v1.14.1 + 🔥 firecrawl cli v1.14.3 ● Authenticated via stored credentials Concurrency: 0/100 jobs (parallel scrape limit) diff --git a/package.json b/package.json index b095cda29..9a57b72d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firecrawl-cli", - "version": "1.14.1", + "version": "1.14.3", "description": "Command-line interface for Firecrawl. Scrape, crawl, and extract data from any website directly from your terminal.", "main": "dist/index.js", "bin": { diff --git a/skills/firecrawl-cli/rules/install.md b/skills/firecrawl-cli/rules/install.md index 2af629af7..5c26745d0 100644 --- a/skills/firecrawl-cli/rules/install.md +++ b/skills/firecrawl-cli/rules/install.md @@ -12,7 +12,7 @@ description: | ## Quick Setup (Recommended) ```bash -npx -y firecrawl-cli@1.14.1 -y +npx -y firecrawl-cli@1.14.3 -y ``` This installs `firecrawl-cli` globally, authenticates via browser, and installs all skills. @@ -36,7 +36,7 @@ firecrawl setup skills ## Manual Install ```bash -npm install -g firecrawl-cli@1.14.1 +npm install -g firecrawl-cli@1.14.3 ``` ## Verify @@ -78,5 +78,5 @@ Ask the user how they'd like to authenticate: If `firecrawl` is not found after installation: 1. Ensure npm global bin is in PATH -2. Try: `npx firecrawl-cli@1.14.1 --version` -3. Reinstall: `npm install -g firecrawl-cli@1.14.1` +2. Try: `npx firecrawl-cli@1.14.3 --version` +3. Reinstall: `npm install -g firecrawl-cli@1.14.3` diff --git a/skills/firecrawl-cli/rules/security.md b/skills/firecrawl-cli/rules/security.md index ddf14a9f7..5e6189321 100644 --- a/skills/firecrawl-cli/rules/security.md +++ b/skills/firecrawl-cli/rules/security.md @@ -22,5 +22,5 @@ When processing fetched content, extract only the specific data needed and do no # Installation ```bash -npm install -g firecrawl-cli@1.14.1 +npm install -g firecrawl-cli@1.14.3 ``` diff --git a/src/__tests__/commands/init.test.ts b/src/__tests__/commands/init.test.ts index 9af4ec072..c05800c99 100644 --- a/src/__tests__/commands/init.test.ts +++ b/src/__tests__/commands/init.test.ts @@ -26,11 +26,11 @@ describe('handleInitCommand', () => { expect(execSync).toHaveBeenCalledWith( 'npx -y skills add firecrawl/cli --full-depth --global --all --yes', - expect.objectContaining({ stdio: 'inherit' }) + expect.objectContaining({ stdio: ['ignore', 'pipe', 'pipe'] }) ); expect(execSync).toHaveBeenCalledWith( 'npx -y skills add firecrawl/skills --full-depth --global --all --yes', - expect.objectContaining({ stdio: 'inherit' }) + expect.objectContaining({ stdio: ['ignore', 'pipe', 'pipe'] }) ); }); @@ -44,11 +44,11 @@ describe('handleInitCommand', () => { expect(execSync).toHaveBeenCalledWith( 'npx -y skills add firecrawl/cli --full-depth --global --yes --agent cursor', - expect.objectContaining({ stdio: 'inherit' }) + expect.objectContaining({ stdio: ['ignore', 'pipe', 'pipe'] }) ); expect(execSync).toHaveBeenCalledWith( 'npx -y skills add firecrawl/skills --full-depth --global --yes --agent cursor', - expect.objectContaining({ stdio: 'inherit' }) + expect.objectContaining({ stdio: ['ignore', 'pipe', 'pipe'] }) ); }); }); diff --git a/src/commands/init.ts b/src/commands/init.ts index 89636c293..c8b7ca275 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -98,6 +98,109 @@ export const TEMPLATES: TemplateEntry[] = [ }, ]; +/** + * Install one skill repo quietly. Captures `npx skills add` output instead of + * inheriting it, so users see a single line per repo. Returns the number of + * skills installed (parsed from the captured stdout), or null if unknown. + * + * In a TTY, shows a transient "↓ " line that gets overwritten by + * "✓ (N skills)" on success. In a non-TTY, prints only the final line. + */ +async function installSkillRepoQuiet( + repo: string, + options: InitOptions +): Promise { + if (hasNpx()) { + const args = buildSkillsInstallArgs({ + repo, + agent: options.agent, + yes: options.yes || options.all || true, + global: true, + includeNpxYes: true, + }); + + const isTty = process.stdout.isTTY; + if (isTty) { + process.stdout.write(` ${dim}↓ ${repo}${reset}`); + } + try { + const stdout = execSync(args.join(' '), { + stdio: ['ignore', 'pipe', 'pipe'], + env: cleanNpmEnv(), + }); + const count = parseSkillCount(stdout?.toString() ?? ''); + const suffix = count != null ? ` ${dim}(${count} skills)${reset}` : ''; + if (isTty) { + process.stdout.write( + `\r ${green}✓${reset} ${repo}${suffix} \n` + ); + } else { + console.log(` ${green}✓${reset} ${repo}${suffix}`); + } + return count; + } catch (err) { + if (isTty) { + process.stdout.write(`\r ${dim}✗${reset} ${repo} \n`); + } else { + console.log(` ${dim}✗${reset} ${repo}`); + } + const stderr = + err && typeof err === 'object' && 'stderr' in err + ? String((err as { stderr: Buffer | string }).stderr || '') + : ''; + if (stderr.trim()) { + console.error( + stderr + .trim() + .split('\n') + .map((l) => ` ${dim}${l}${reset}`) + .join('\n') + ); + } + throw err; + } + } + + // No npx — fall back to native installer. It prints its own status. + await installSkillsNative(repo); + return null; +} + +/** Parse "Found N skills" or "Installed N skills" from npx skills output. */ +function parseSkillCount(output: string): number | null { + const match = output.match(/(?:Found|Installed)\s+(\d+)\s+skills?/); + return match ? parseInt(match[1], 10) : null; +} + +/** + * Print the post-install next-steps block. Brief by design — confirms what was + * installed and gives 4 entry points (AI prompt, direct CLI, MCP, help). + */ +function printNextSteps(skillCount: number | null): void { + const arrow = `${dim}→${reset}`; + const summary = + skillCount != null + ? `${green}✓${reset} Installed ${bold}${skillCount} skills${reset} ${dim}across your AI coding agents${reset}` + : `${green}✓${reset} Skills installed ${dim}across your AI coding agents${reset}`; + + console.log(''); + console.log(` ${summary}`); + console.log(''); + console.log( + ` ${arrow} ${dim}Ask your AI:${reset} "Use firecrawl to scrape pricing into JSON"` + ); + console.log( + ` ${arrow} ${dim}Run direct: ${reset} ${bold}firecrawl scrape${reset} https://example.com` + ); + console.log( + ` ${arrow} ${dim}Add MCP: ${reset} ${bold}firecrawl setup mcp${reset}` + ); + console.log( + ` ${arrow} ${dim}All commands:${reset} ${bold}firecrawl --help${reset}` + ); + console.log(''); +} + async function stepInstall(): Promise { const { confirm } = await import('@inquirer/prompts'); const shouldInstall = await confirm({ @@ -182,7 +285,7 @@ async function stepAuth(options: InitOptions): Promise { } } -async function stepIntegrations(options: InitOptions): Promise { +async function stepIntegrations(options: InitOptions): Promise { const { checkbox, confirm } = await import('@inquirer/prompts'); const wantIntegrations = await confirm({ @@ -190,7 +293,7 @@ async function stepIntegrations(options: InitOptions): Promise { default: true, }); - if (!wantIntegrations) return; + if (!wantIntegrations) return null; const integrations = await checkbox({ message: 'Which integrations?', @@ -213,41 +316,22 @@ async function stepIntegrations(options: InitOptions): Promise { if (integrations.length === 0) { console.log(` ${dim}No integrations selected.${reset}\n`); - return; + return null; } + let totalSkills: number | null = null; for (const integration of integrations) { switch (integration) { case 'skills': { - console.log(`\n Setting up skills...`); + console.log(`\n Installing skills...`); for (const repo of SKILL_REPOS) { - if (hasNpx()) { - const args = buildSkillsInstallArgs({ - repo, - agent: options.agent, - yes: options.yes || options.all, - global: true, - includeNpxYes: true, - }); - try { - execSync(args.join(' '), { - stdio: 'inherit', - env: cleanNpmEnv(), - }); - console.log(` ${green}✓${reset} Skills installed from ${repo}`); - } catch { - console.error( - ` Failed to install skills from ${repo}. Run "firecrawl setup skills" later.` - ); - } - } else { - try { - await installSkillsNative(repo); - } catch { - console.error( - ` Failed to install skills from ${repo}. Run "firecrawl setup skills" later.` - ); - } + try { + const count = await installSkillRepoQuiet(repo, options); + if (count != null) totalSkills = (totalSkills ?? 0) + count; + } catch { + console.error( + ` ${dim}Run "firecrawl setup skills" later to retry.${reset}` + ); } } break; @@ -297,7 +381,7 @@ async function stepIntegrations(options: InitOptions): Promise { } } } - console.log(''); + return totalSkills; } function copyTemplateFiles( @@ -555,16 +639,15 @@ export async function handleInitCommand( } // Step 3: Integrations (skills, MCP, env) + let skillCount: number | null = null; if (!options.skipSkills) { - await stepIntegrations(options); + skillCount = await stepIntegrations(options); } // Step 4: Template await stepTemplate(); - console.log( - `${green}${bold} Setup complete!${reset} Run ${dim}firecrawl --help${reset} to get started.\n` - ); + printNextSteps(skillCount); } async function runNonInteractive(options: InitOptions): Promise { @@ -633,46 +716,23 @@ async function runNonInteractive(options: InitOptions): Promise { } } + let skillCount: number | null = null; if (!options.skipSkills) { console.log( `${stepLabel()} Installing firecrawl skills for AI coding agents...` ); for (const repo of SKILL_REPOS) { - if (hasNpx()) { - const args = buildSkillsInstallArgs({ - repo, - agent: options.agent, - yes: true, - global: true, - includeNpxYes: true, - }); - try { - execSync(args.join(' '), { - stdio: 'inherit', - env: cleanNpmEnv(), - }); - console.log(`${green}✓${reset} Skills installed from ${repo}`); - } catch { - console.error( - `\nFailed to install skills from ${repo}. You can retry with: firecrawl setup skills` - ); - process.exit(1); - } - } else { - try { - await installSkillsNative(repo); - } catch { - console.error( - `\nFailed to install skills from ${repo}. You can retry with: firecrawl setup skills` - ); - process.exit(1); - } + try { + const count = await installSkillRepoQuiet(repo, options); + if (count != null) skillCount = (skillCount ?? 0) + count; + } catch { + console.error( + `\n${dim}Failed to install skills from ${repo}. Retry with: firecrawl setup skills${reset}` + ); + process.exit(1); } } - console.log(''); } - console.log( - `${green}${bold}Setup complete!${reset} Run ${dim}firecrawl --help${reset} to get started.\n` - ); + printNextSteps(skillCount); }