diff --git a/README.md b/README.md index cf79d1b45..e4dbc61d3 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.13.0 init -y --browser +npx -y firecrawl-cli@1.14.0 init -y --browser ``` - `-y` runs setup non-interactively @@ -583,7 +583,7 @@ firecrawl --status ``` ``` - 🔥 firecrawl cli v1.13.0 + 🔥 firecrawl cli v1.14.0 ● Authenticated via stored credentials Concurrency: 0/100 jobs (parallel scrape limit) diff --git a/package.json b/package.json index 91d4c948f..60d453afe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firecrawl-cli", - "version": "1.13.0", + "version": "1.14.0", "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 bc1a4fff6..bd65dd7ed 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.13.0 -y +npx -y firecrawl-cli@1.14.0 -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.13.0 +npm install -g firecrawl-cli@1.14.0 ``` ## 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.13.0 --version` -3. Reinstall: `npm install -g firecrawl-cli@1.13.0` +2. Try: `npx firecrawl-cli@1.14.0 --version` +3. Reinstall: `npm install -g firecrawl-cli@1.14.0` diff --git a/skills/firecrawl-cli/rules/security.md b/skills/firecrawl-cli/rules/security.md index 028dfe6c0..2edfd9d6d 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.13.0 +npm install -g firecrawl-cli@1.14.0 ``` diff --git a/src/__tests__/commands/init.test.ts b/src/__tests__/commands/init.test.ts index 2528cfb5e..ad8bb2504 100644 --- a/src/__tests__/commands/init.test.ts +++ b/src/__tests__/commands/init.test.ts @@ -17,7 +17,7 @@ describe('handleInitCommand', () => { vi.restoreAllMocks(); }); - it('installs skills globally across all detected agents in non-interactive mode', async () => { + it('installs skills from both repos globally across all detected agents in non-interactive mode', async () => { await handleInitCommand({ yes: true, skipInstall: true, @@ -28,9 +28,13 @@ describe('handleInitCommand', () => { 'npx -y skills add firecrawl/cli --full-depth --global --all --yes', { stdio: 'inherit' } ); + expect(execSync).toHaveBeenCalledWith( + 'npx -y skills add firecrawl/skills --full-depth --global --all --yes', + { stdio: 'inherit' } + ); }); - it('scopes non-interactive skills install to one agent when provided', async () => { + it('scopes non-interactive skills install to one agent across both repos when provided', async () => { await handleInitCommand({ yes: true, skipInstall: true, @@ -42,5 +46,9 @@ describe('handleInitCommand', () => { 'npx -y skills add firecrawl/cli --full-depth --global --yes --agent cursor', { stdio: 'inherit' } ); + expect(execSync).toHaveBeenCalledWith( + 'npx -y skills add firecrawl/skills --full-depth --global --yes --agent cursor', + { stdio: 'inherit' } + ); }); }); diff --git a/src/__tests__/commands/setup.test.ts b/src/__tests__/commands/setup.test.ts index 78723c46b..5e4d7d09c 100644 --- a/src/__tests__/commands/setup.test.ts +++ b/src/__tests__/commands/setup.test.ts @@ -15,21 +15,29 @@ describe('handleSetupCommand', () => { vi.restoreAllMocks(); }); - it('installs skills globally across all detected agents by default', async () => { + it('installs skills globally across all detected agents from both repos by default', async () => { await handleSetupCommand('skills', {}); expect(execSync).toHaveBeenCalledWith( 'npx -y skills add firecrawl/cli --full-depth --global --all', { stdio: 'inherit' } ); + expect(execSync).toHaveBeenCalledWith( + 'npx -y skills add firecrawl/skills --full-depth --global --all', + { stdio: 'inherit' } + ); }); - it('installs skills globally for a specific agent without using --all', async () => { + it('installs skills globally for a specific agent from both repos without using --all', async () => { await handleSetupCommand('skills', { agent: 'cursor' }); expect(execSync).toHaveBeenCalledWith( 'npx -y skills add firecrawl/cli --full-depth --global --agent cursor', { stdio: 'inherit' } ); + expect(execSync).toHaveBeenCalledWith( + 'npx -y skills add firecrawl/skills --full-depth --global --agent cursor', + { stdio: 'inherit' } + ); }); }); diff --git a/src/commands/init.ts b/src/commands/init.ts index 3360a4674..2ed99ed75 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -8,7 +8,7 @@ import { execSync } from 'child_process'; import { isAuthenticated, browserLogin, interactiveLogin } from '../utils/auth'; import { saveCredentials } from '../utils/credentials'; import { updateConfig, getApiKey } from '../utils/config'; -import { buildSkillsInstallArgs } from './skills-install'; +import { buildSkillsInstallArgs, SKILL_REPOS } from './skills-install'; import { hasNpx, installSkillsNative } from './skills-native'; export interface InitOptions { @@ -216,28 +216,31 @@ async function stepIntegrations(options: InitOptions): Promise { switch (integration) { case 'skills': { console.log(`\n Setting up skills...`); - if (hasNpx()) { - const args = buildSkillsInstallArgs({ - agent: options.agent, - yes: options.yes || options.all, - global: true, - includeNpxYes: true, - }); - try { - execSync(args.join(' '), { stdio: 'inherit' }); - console.log(` ${green}✓${reset} Skills installed`); - } catch { - console.error( - ' Failed to install skills. Run "firecrawl setup skills" later.' - ); - } - } else { - try { - await installSkillsNative(); - } catch { - console.error( - ' Failed to install skills. Run "firecrawl setup skills" later.' - ); + 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' }); + 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.` + ); + } } } break; @@ -625,35 +628,38 @@ async function runNonInteractive(options: InitOptions): Promise { if (!options.skipSkills) { console.log( - `${stepLabel()} Installing firecrawl skill for AI coding agents...` + `${stepLabel()} Installing firecrawl skills for AI coding agents...` ); - if (hasNpx()) { - const args = buildSkillsInstallArgs({ - agent: options.agent, - yes: true, - global: true, - includeNpxYes: true, - }); - try { - execSync(args.join(' '), { stdio: 'inherit' }); - console.log(`${green}✓${reset} Skills installed\n`); - } catch { - console.error( - '\nFailed to install skills. You can retry with: firecrawl setup skills' - ); - process.exit(1); - } - } else { - try { - await installSkillsNative(); - console.log(''); - } catch { - console.error( - '\nFailed to install skills. You can retry with: firecrawl setup skills' - ); - process.exit(1); + 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' }); + 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); + } } } + console.log(''); } console.log( diff --git a/src/commands/setup.ts b/src/commands/setup.ts index 820fb281d..ebc3dc76e 100644 --- a/src/commands/setup.ts +++ b/src/commands/setup.ts @@ -5,7 +5,7 @@ import { execSync } from 'child_process'; import { getApiKey } from '../utils/config'; -import { buildSkillsInstallArgs } from './skills-install'; +import { buildSkillsInstallArgs, SKILL_REPOS } from './skills-install'; import { hasNpx, installSkillsNative } from './skills-native'; export type SetupSubcommand = 'skills' | 'mcp'; @@ -41,34 +41,37 @@ export async function handleSetupCommand( } async function installSkills(options: SetupOptions): Promise { - if (hasNpx()) { - const args = buildSkillsInstallArgs({ - agent: options.agent, - global: true, - includeNpxYes: true, - }); + for (const repo of SKILL_REPOS) { + if (hasNpx()) { + const args = buildSkillsInstallArgs({ + repo, + agent: options.agent, + global: true, + includeNpxYes: true, + }); + + const cmd = args.join(' '); + console.log(`Running: ${cmd}\n`); - const cmd = args.join(' '); - console.log(`Running: ${cmd}\n`); + try { + execSync(cmd, { stdio: 'inherit' }); + continue; + } catch { + process.exit(1); + } + } + // Fallback: native install (no npx/Node required) try { - execSync(cmd, { stdio: 'inherit' }); - return; - } catch { + await installSkillsNative(repo); + } catch (error) { + console.error( + `Failed to install skills from ${repo}:`, + error instanceof Error ? error.message : 'Unknown error' + ); process.exit(1); } } - - // Fallback: native install (no npx/Node required) - try { - await installSkillsNative(); - } catch (error) { - console.error( - 'Failed to install skills:', - error instanceof Error ? error.message : 'Unknown error' - ); - process.exit(1); - } } async function installMcp(options: SetupOptions): Promise { diff --git a/src/commands/skills-install.ts b/src/commands/skills-install.ts index b44a0ebe2..9738332b4 100644 --- a/src/commands/skills-install.ts +++ b/src/commands/skills-install.ts @@ -1,9 +1,21 @@ +/** + * Repos to pull skills from during install. + * + * - firecrawl/cli: core CLI skills bundled with this repo + * - firecrawl/skills: additional "build" skills for working with Firecrawl + * + * Both are installed during `firecrawl init` and `firecrawl setup skills`. + */ +export const SKILL_REPOS = ['firecrawl/cli', 'firecrawl/skills'] as const; + export interface SkillsInstallCommandOptions { agent?: string; all?: boolean; yes?: boolean; global?: boolean; includeNpxYes?: boolean; + /** Repo to install from (defaults to firecrawl/cli) */ + repo?: string; } export function buildSkillsInstallArgs( @@ -15,7 +27,7 @@ export function buildSkillsInstallArgs( args.push('-y'); } - args.push('skills', 'add', 'firecrawl/cli', '--full-depth'); + args.push('skills', 'add', options.repo ?? 'firecrawl/cli', '--full-depth'); if (options.global ?? true) { args.push('--global'); diff --git a/src/commands/skills-native.ts b/src/commands/skills-native.ts index a9b03afa1..bae2d1f2a 100644 --- a/src/commands/skills-native.ts +++ b/src/commands/skills-native.ts @@ -13,8 +13,7 @@ const dim = '\x1b[2m'; const bold = '\x1b[1m'; const reset = '\x1b[0m'; -const REPO = 'firecrawl/cli'; -const REPO_URL = `https://github.com/${REPO}.git`; +const DEFAULT_REPO = 'firecrawl/cli'; const SKILLS_SUBDIR = 'skills'; /** Where each agent stores global skills */ @@ -285,9 +284,11 @@ function hasDownloader(): 'curl' | 'wget' | null { } /** Clone repo to temp directory */ -function cloneRepo(tmpDir: string): void { +function cloneRepo(tmpDir: string, repo: string): void { + const repoUrl = `https://github.com/${repo}.git`; + if (hasGit()) { - execSync(`git clone --depth 1 "${REPO_URL}" "${tmpDir}"`, { + execSync(`git clone --depth 1 "${repoUrl}" "${tmpDir}"`, { stdio: 'pipe', }); return; @@ -301,7 +302,7 @@ function cloneRepo(tmpDir: string): void { fs.mkdirSync(tmpDir, { recursive: true }); const tarball = path.join(tmpDir, 'repo.tar.gz'); - const tarballUrl = `https://api.github.com/repos/${REPO}/tarball`; + const tarballUrl = `https://api.github.com/repos/${repo}/tarball`; if (downloader === 'curl') { execSync( @@ -324,21 +325,24 @@ function cloneRepo(tmpDir: string): void { /** * Install skills natively — no npx required. * - * Replicates: npx skills add firecrawl/cli --full-depth --global --all + * Replicates: npx skills add --full-depth --global --all */ -export async function installSkillsNative(): Promise { +export async function installSkillsNative( + repo: string = DEFAULT_REPO +): Promise { const home = os.homedir(); const canonicalBase = path.join(home, CANONICAL_DIR); const lockFilePath = path.join(home, LOCK_FILE); + const repoUrl = `https://github.com/${repo}.git`; // Clone repo const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'firecrawl-skills-')); try { console.log( - ` ${dim}Downloading skills from github.com/${REPO}...${reset}` + ` ${dim}Downloading skills from github.com/${repo}...${reset}` ); - cloneRepo(tmpDir); + cloneRepo(tmpDir, repo); // Discover skills const skillsDir = path.join(tmpDir, SKILLS_SUBDIR); @@ -430,9 +434,9 @@ export async function installSkillsNative(): Promise { const canonicalPath = path.join(canonicalBase, skill.name); const existing = lock.skills[skill.name]; lock.skills[skill.name] = { - source: REPO, + source: repo, sourceType: 'github', - sourceUrl: REPO_URL, + sourceUrl: repoUrl, skillPath: skill.skillPath, skillFolderHash: hashDir(canonicalPath), installedAt: existing?.installedAt ?? now,