From f737873e9dcf48e1dae015cf94917e6a48fb60e9 Mon Sep 17 00:00:00 2001 From: Developers Digest <124798203+developersdigest@users.noreply.github.com> Date: Mon, 4 May 2026 10:56:23 -0400 Subject: [PATCH 1/3] Add webhook/cancel support and JSON parsing Add support for webhook configuration and canceling jobs across agent, crawl, and scrape commands. Introduce JSON parsing helpers and CLI options for schema, actions, scrape-options, proxy, and webhook (inline or file), and validate payload shapes. Extend types to include webhook/cancel fields and normalize agent statuses to include "cancelled", updating polling and status handling accordingly. Propagate webhook/scrapeOptions through command handlers and improve CLI behavior for global --status handling and format resolution. --- src/commands/agent.ts | 84 +++++++++-- src/commands/crawl.ts | 37 ++++- src/commands/scrape.ts | 23 ++- src/index.ts | 310 ++++++++++++++++++++++++++++++++++++++++- src/types/agent.ts | 8 +- src/types/crawl.ts | 6 + src/types/scrape.ts | 8 ++ 7 files changed, 455 insertions(+), 21 deletions(-) diff --git a/src/commands/agent.ts b/src/commands/agent.ts index 65d8341d1..b7df0155c 100644 --- a/src/commands/agent.ts +++ b/src/commands/agent.ts @@ -5,8 +5,10 @@ import type { AgentOptions, AgentResult, + AgentStatus, AgentStatusResult, } from '../types/agent'; +import type { AgentWebhookConfig } from '@mendable/firecrawl-js'; import { getClient } from '../utils/client'; import { isJobId } from '../utils/job'; import { writeOutput } from '../utils/output'; @@ -62,6 +64,12 @@ function loadSchemaFromFile(filePath: string): Record { } } +type AgentStatusFromApi = 'processing' | 'completed' | 'failed'; + +function normalizeAgentStatus(status: AgentStatusFromApi): AgentStatus { + return status as AgentStatus; +} + /** * Execute agent status check (with optional wait/polling) */ @@ -75,11 +83,16 @@ async function checkAgentStatus( if (!options.wait) { try { const status = await app.getAgentStatus(jobId); + const normalizedStatus = normalizeAgentStatus( + status.status as AgentStatusFromApi + ); + const isCancelled = normalizedStatus === 'cancelled'; + return { - success: status.success, + success: isCancelled ? true : status.success, data: { id: jobId, - status: status.status, + status: normalizedStatus, data: status.data, creditsUsed: status.creditsUsed, expiresAt: status.expiresAt, @@ -113,16 +126,21 @@ async function checkAgentStatus( try { // Check initial status let agentStatus = await app.getAgentStatus(jobId); - spinner.update(`Agent ${agentStatus.status}... (Job ID: ${jobId})`); + const normalizedStatusInitial = normalizeAgentStatus( + agentStatus.status as AgentStatusFromApi + ); + spinner.update(`Agent ${normalizedStatusInitial}... (Job ID: ${jobId})`); while (true) { - if (agentStatus.status === 'completed') { + const currentNormalizedStatus = normalizeAgentStatus(agentStatus.status); + + if (currentNormalizedStatus === 'completed') { spinner.succeed('Agent completed'); return { success: agentStatus.success, data: { id: jobId, - status: agentStatus.status, + status: currentNormalizedStatus, data: agentStatus.data, creditsUsed: agentStatus.creditsUsed, expiresAt: agentStatus.expiresAt, @@ -130,13 +148,13 @@ async function checkAgentStatus( }; } - if (agentStatus.status === 'failed') { + if (currentNormalizedStatus === 'failed') { spinner.fail('Agent failed'); return { success: false, data: { id: jobId, - status: agentStatus.status, + status: currentNormalizedStatus, data: agentStatus.data, creditsUsed: agentStatus.creditsUsed, expiresAt: agentStatus.expiresAt, @@ -145,6 +163,20 @@ async function checkAgentStatus( }; } + if (currentNormalizedStatus === 'cancelled') { + spinner.succeed('Agent cancelled'); + return { + success: true, + data: { + id: jobId, + status: currentNormalizedStatus, + data: agentStatus.data, + creditsUsed: agentStatus.creditsUsed, + expiresAt: agentStatus.expiresAt, + }, + }; + } + // Check timeout if (timeoutMs && Date.now() - startTime > timeoutMs) { spinner.fail(`Timeout after ${options.timeout}s`); @@ -156,7 +188,10 @@ async function checkAgentStatus( await new Promise((resolve) => setTimeout(resolve, pollMs)); agentStatus = await app.getAgentStatus(jobId); - spinner.update(`Agent ${agentStatus.status}... (Job ID: ${jobId})`); + const loopNormalizedStatus = normalizeAgentStatus( + agentStatus.status as AgentStatusFromApi + ); + spinner.update(`Agent ${loopNormalizedStatus}... (Job ID: ${jobId})`); } } catch (error) { spinner.fail('Failed to check agent status'); @@ -177,7 +212,25 @@ export async function executeAgent( ): Promise { try { const app = getClient({ apiKey: options.apiKey, apiUrl: options.apiUrl }); - const { prompt, status, wait, pollInterval, timeout } = options; + const { prompt, status, cancel, wait, pollInterval, timeout } = options; + + if (cancel) { + const cancelled = await app.cancelAgent(prompt); + if (!cancelled) { + return { + success: false, + error: `Failed to cancel agent job ${prompt}`, + }; + } + + return { + success: true, + data: { + id: prompt, + status: 'cancelled', + }, + }; + } // If status flag is set or input looks like a job ID, check status if (status || isJobId(prompt)) { @@ -201,6 +254,7 @@ export async function executeAgent( maxCredits?: number; pollInterval?: number; timeout?: number; + webhook?: string | AgentWebhookConfig; integration?: string; } = { prompt, @@ -219,6 +273,9 @@ export async function executeAgent( if (options.maxCredits !== undefined) { agentParams.maxCredits = options.maxCredits; } + if (options.webhook) { + agentParams.webhook = options.webhook; + } // If wait mode, use polling with spinner if (wait) { @@ -259,15 +316,16 @@ export async function executeAgent( await new Promise((resolve) => setTimeout(resolve, pollMs)); const agentStatus = await app.getAgentStatus(jobId); + const normalizedStatus = normalizeAgentStatus(agentStatus.status); - if (agentStatus.status === 'completed') { + if (normalizedStatus === 'completed') { process.removeListener('SIGINT', handleInterrupt); spinner.succeed('Agent completed'); return { success: agentStatus.success, data: { id: jobId, - status: agentStatus.status, + status: normalizedStatus, data: agentStatus.data, creditsUsed: agentStatus.creditsUsed, expiresAt: agentStatus.expiresAt, @@ -275,14 +333,14 @@ export async function executeAgent( }; } - if (agentStatus.status === 'failed') { + if (normalizedStatus === 'failed') { process.removeListener('SIGINT', handleInterrupt); spinner.fail('Agent failed'); return { success: false, data: { id: jobId, - status: agentStatus.status, + status: normalizedStatus, data: agentStatus.data, creditsUsed: agentStatus.creditsUsed, expiresAt: agentStatus.expiresAt, diff --git a/src/commands/crawl.ts b/src/commands/crawl.ts index ab00bea61..9ebc2c8e1 100644 --- a/src/commands/crawl.ts +++ b/src/commands/crawl.ts @@ -49,7 +49,34 @@ export async function executeCrawl( ): Promise { try { const app = getClient({ apiKey: options.apiKey, apiUrl: options.apiUrl }); - const { urlOrJobId, status, wait, pollInterval, timeout } = options; + const { urlOrJobId, status, wait, pollInterval, timeout, cancel } = options; + + if (cancel) { + if (!isJobId(urlOrJobId)) { + return { + success: false, + error: 'Cancel mode requires a job ID', + }; + } + + const cancelled = await app.cancelCrawl(urlOrJobId); + if (!cancelled) { + return { + success: false, + error: `Failed to cancel crawl job ${urlOrJobId}`, + }; + } + + return { + success: true, + data: { + id: urlOrJobId, + status: 'cancelled', + total: 0, + completed: 0, + }, + }; + } // If status flag is set or input looks like a job ID, check status if (status || isJobId(urlOrJobId)) { @@ -95,6 +122,14 @@ export async function executeCrawl( crawlOptions.maxConcurrency = options.maxConcurrency; } + if (options.scrapeOptions) { + crawlOptions.scrapeOptions = options.scrapeOptions; + } + + if (options.webhook) { + crawlOptions.webhook = options.webhook; + } + // If wait mode, use the convenience crawl method with polling if (wait) { // Set polling options diff --git a/src/commands/scrape.ts b/src/commands/scrape.ts index ed2376d89..a06e889c7 100644 --- a/src/commands/scrape.ts +++ b/src/commands/scrape.ts @@ -80,13 +80,20 @@ export async function executeScrape( formats.push({ type: 'query', prompt: options.query } as any); } + const resolvedFormats = formats.map((format) => { + if (format === 'json' && options.schema) { + return { type: 'json', schema: options.schema } as FormatOption; + } + return format; + }); + // If no formats specified, default to markdown if (formats.length === 0) { - formats.push('markdown'); + resolvedFormats.push('markdown'); } const scrapeParams: Record = { - formats, + formats: resolvedFormats, integration: 'cli', }; @@ -118,6 +125,18 @@ export async function executeScrape( scrapeParams.profile = options.profile; } + if (options.actions) { + scrapeParams.actions = options.actions; + } + + if (options.proxy) { + scrapeParams.proxy = options.proxy; + } + + if (options.webhook) { + scrapeParams.webhook = options.webhook; + } + if (options.lockdown) { scrapeParams.lockdown = true; } diff --git a/src/index.ts b/src/index.ts index 7bb787dd6..eb57be2c7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ */ import { Command } from 'commander'; +import { readFileSync } from 'fs'; import { handleScrapeCommand, handleMultiScrapeCommand, @@ -46,6 +47,7 @@ import { ensureAuthenticated, printBanner } from './utils/auth'; import packageJson from '../package.json'; import type { SearchSource, SearchCategory } from './types/search'; import type { ScrapeFormat } from './types/scrape'; +import type { AgentWebhookConfig } from '@mendable/firecrawl-js'; import { createClaudeCommand, createCodexCommand, @@ -70,6 +72,195 @@ const AUTH_REQUIRED_COMMANDS = [ 'credit-usage', ]; +const commandSet = new Set([]); + +function collectTopLevelCommands(): void { + commandSet.clear(); + for (const command of program.commands) { + commandSet.add(command.name()); + for (const alias of command.aliases()) { + commandSet.add(alias); + } + } +} + +function parseJsonInput(raw: string, label: string): unknown { + try { + return JSON.parse(raw); + } catch (error) { + throw new Error( + `Invalid JSON in ${label}: ${error instanceof Error ? error.message : 'Unable to parse JSON'}` + ); + } +} + +function parseJsonFromFile(filePath: string, label: string): unknown { + try { + const content = readFileSync(filePath, 'utf-8'); + return parseJsonInput(content, `${label} file`); + } catch (error: any) { + if (error?.code === 'ENOENT') { + throw new Error(`Could not read ${label} file: ${filePath}`); + } + if (error instanceof SyntaxError) { + throw new Error(`Invalid JSON in ${label} file: ${filePath}`); + } + throw error; + } +} + +function parseJsonPayload( + inline: string | undefined, + filePath: string | undefined, + label: string +): unknown | undefined { + if (inline !== undefined) { + return parseJsonInput(inline, label); + } + + if (filePath !== undefined) { + return parseJsonFromFile(filePath, label); + } + + return undefined; +} + +function parseJsonObject( + inline: string | undefined, + filePath: string | undefined, + label: string +): Record | undefined { + const parsed = parseJsonPayload(inline, filePath, label); + if (parsed === undefined) { + return undefined; + } + + if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) { + throw new Error(`Invalid ${label}: expected a JSON object`); + } + + return parsed as Record; +} + +function parseJsonArray( + inline: string | undefined, + filePath: string | undefined, + label: string +): Record[] | undefined { + const parsed = parseJsonPayload(inline, filePath, label); + if (parsed === undefined) { + return undefined; + } + + if (!Array.isArray(parsed)) { + throw new Error(`Invalid ${label}: expected a JSON array`); + } + + return parsed as Record[]; +} + +function parseWebhookOption( + raw: string | undefined, + label: string +): string | Record | undefined { + if (!raw) { + return undefined; + } + + const trimmed = raw.trim(); + if (!trimmed) { + return undefined; + } + + if (trimmed.startsWith('{') || trimmed.startsWith('[')) { + const parsed = parseJsonPayload(trimmed, undefined, label); + if ( + typeof parsed !== 'object' || + parsed === null || + Array.isArray(parsed) + ) { + throw new Error(`Invalid ${label}: expected webhook object or URL`); + } + return parsed as Record; + } + + return trimmed; +} + +function parseAgentWebhookOption( + raw: string | undefined, + label: string +): string | AgentWebhookConfig | undefined { + const webhook = parseWebhookOption(raw, label); + + if (webhook === undefined || typeof webhook === 'string') { + return webhook; + } + + if (typeof webhook.url !== 'string' || webhook.url.trim().length === 0) { + throw new Error( + `Invalid ${label}: webhook object requires a non-empty "url"` + ); + } + + return webhook as unknown as AgentWebhookConfig; +} + +function getFirstPositionalArg(args: string[]): string | undefined { + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (!arg || arg === '--') { + continue; + } + + if (!arg.startsWith('-')) { + return arg; + } + + // Skip values for known global options and positional flags used before subcommand parsing. + if ( + [ + '-k', + '--api-key', + '--api-url', + '-u', + '--url', + '-f', + '--format', + ].includes(arg) && + args[i + 1] !== undefined + ) { + i += 1; + continue; + } + + if (arg.startsWith('--')) { + const equalsIndex = arg.indexOf('='); + if (equalsIndex !== -1) { + continue; + } + + // Single-dash option with attached value is rare in commander CLI usage; + // safest to ignore as positional-only command parser here. + if (args[i + 1] !== undefined && !args[i + 1].startsWith('-')) { + i += 1; + continue; + } + } + } + + return undefined; +} + +function shouldShowGlobalStatus(args: string[]): boolean { + const command = getFirstPositionalArg(args); + if (!command) { + return true; + } + + return !commandSet.has(command); +} + const program = new Command(); program @@ -176,6 +367,15 @@ function createScrapeCommand(): Command { 'Load existing profile data without saving changes (default: saves changes)' ) .option('--lockdown', 'Enable lockdown mode for the scrape', false) + .option('--schema ', 'JSON schema for structured extraction') + .option('--schema-file ', 'Path to JSON schema file') + .option('--actions ', 'JSON actions array to run during scrape') + .option('--actions-file ', 'Path to JSON actions file') + .option('--proxy ', 'Proxy mode for scraping (e.g., auto, basic)') + .option( + '--webhook ', + 'Webhook URL or webhook configuration object' + ) .action(async (positionalArgs, options) => { // Collect URLs from positional args and --url option @@ -203,6 +403,36 @@ function createScrapeCommand(): Command { process.exit(1); } + let schema: Record | undefined; + let actions: Record[] | undefined; + let webhook: string | Record | undefined; + + try { + schema = parseJsonObject(options.schema, undefined, '--schema'); + actions = parseJsonArray(options.actions, undefined, '--actions'); + webhook = parseWebhookOption(options.webhook, '--webhook'); + } catch (error) { + if (error instanceof Error) { + console.error('Error:', error.message); + process.exit(1); + } + } + + if (options.schemaFile) { + schema = parseJsonObject( + undefined, + options.schemaFile, + '--schema-file' + ); + } + if (options.actionsFile) { + actions = parseJsonArray( + undefined, + options.actionsFile, + '--actions-file' + ); + } + // Determine format let format: string; const positionalFormats = (positionalArgs || []).filter( @@ -225,11 +455,18 @@ function createScrapeCommand(): Command { url: urls[0], format, }); + const scrapeOptionsWithExtensions = { + ...scrapeOptions, + schema, + actions, + proxy: options.proxy, + webhook, + }; if (urls.length === 1) { - await handleScrapeCommand(scrapeOptions); + await handleScrapeCommand(scrapeOptionsWithExtensions); } else { - await handleMultiScrapeCommand(urls, scrapeOptions); + await handleMultiScrapeCommand(urls, scrapeOptionsWithExtensions); } }); @@ -386,6 +623,13 @@ function createCrawlCommand(): Command { 'Maximum concurrent requests', parseInt ) + .option( + '--scrape-options ', + 'JSON scrape options passed to each page crawl' + ) + .option('--scrape-options-file ', 'Path to scrape options JSON file') + .option('--webhook ', 'Webhook URL or webhook configuration') + .option('--cancel', 'Cancel active crawl job by job ID', false) .option( '-k, --api-key ', 'Firecrawl API key (overrides global --api-key)' @@ -403,6 +647,31 @@ function createCrawlCommand(): Command { process.exit(1); } + let scrapeOptions: Record | undefined; + let webhook: string | Record | undefined; + + try { + scrapeOptions = parseJsonObject( + options.scrapeOptions, + undefined, + '--scrape-options' + ); + webhook = parseWebhookOption(options.webhook, '--webhook'); + } catch (error) { + if (error instanceof Error) { + console.error('Error:', error.message); + process.exit(1); + } + } + + if (options.scrapeOptionsFile) { + scrapeOptions = parseJsonObject( + undefined, + options.scrapeOptionsFile, + '--scrape-options-file' + ); + } + // Auto-detect if it's a job ID (UUID format) const isStatusCheck = options.status || isJobId(urlOrJobId); @@ -432,6 +701,9 @@ function createCrawlCommand(): Command { allowSubdomains: options.allowSubdomains, delay: options.delay, maxConcurrency: options.maxConcurrency, + scrapeOptions, + webhook, + cancel: options.cancel, }; await handleCrawlCommand(crawlOptions); @@ -758,7 +1030,9 @@ function createAgentCommand(): Command { 'Maximum credits to spend (job fails if exceeded)', parseInt ) + .option('--webhook ', 'Webhook URL or webhook configuration') .option('--status', 'Check status of existing agent job', false) + .option('--cancel', 'Cancel active agent job by job ID', false) .option( '--wait', 'Wait for agent to complete before returning results', @@ -785,6 +1059,14 @@ function createAgentCommand(): Command { .action(async (promptOrJobId, options) => { // Auto-detect if it's a job ID (UUID format) const isStatusCheck = options.status || isJobId(promptOrJobId); + const isCancel = options.cancel; + + if ((isStatusCheck || isCancel) && !isJobId(promptOrJobId)) { + console.error( + 'Error: --status and --cancel require a job ID, not a prompt.' + ); + process.exit(1); + } // Parse URLs let urls: string[] | undefined; @@ -805,6 +1087,23 @@ function createAgentCommand(): Command { process.exit(1); } } + if (options.schemaFile) { + schema = parseJsonObject( + undefined, + options.schemaFile, + '--schema-file' + ); + } + + let webhook: string | AgentWebhookConfig | undefined; + try { + webhook = parseAgentWebhookOption(options.webhook, '--webhook'); + } catch (error) { + if (error instanceof Error) { + console.error('Error:', error.message); + process.exit(1); + } + } // Validate model const validModels = ['spark-1-pro', 'spark-1-mini']; @@ -819,10 +1118,10 @@ function createAgentCommand(): Command { prompt: promptOrJobId, urls, schema, - schemaFile: options.schemaFile, model: options.model, maxCredits: options.maxCredits, status: isStatusCheck, + cancel: isCancel, wait: options.wait, pollInterval: options.pollInterval, timeout: options.timeout, @@ -831,6 +1130,7 @@ function createAgentCommand(): Command { output: options.output, json: options.json, pretty: options.pretty, + webhook, }; await handleAgentCommand(agentOptions); @@ -1471,6 +1771,8 @@ program handleVersionCommand({ authStatus: options.authStatus }); }); +collectTopLevelCommands(); + // Parse arguments const args = process.argv.slice(2); @@ -1489,7 +1791,7 @@ async function main() { } // Handle --status flag - if (args.includes('--status')) { + if (args.includes('--status') && shouldShowGlobalStatus(args)) { await handleStatusCommand(); return; } diff --git a/src/types/agent.ts b/src/types/agent.ts index e2a5c9aaa..c9c4f5a87 100644 --- a/src/types/agent.ts +++ b/src/types/agent.ts @@ -2,9 +2,11 @@ * Types and interfaces for the agent command */ +import type { AgentWebhookConfig } from '@mendable/firecrawl-js'; + export type AgentModel = 'spark-1-pro' | 'spark-1-mini'; -export type AgentStatus = 'processing' | 'completed' | 'failed'; +export type AgentStatus = 'processing' | 'completed' | 'failed' | 'cancelled'; export interface AgentOptions { /** Natural language prompt describing the data to extract */ @@ -17,6 +19,10 @@ export interface AgentOptions { schema?: Record; /** Path to JSON schema file */ schemaFile?: string; + /** Webhook URL or webhook config */ + webhook?: string | AgentWebhookConfig; + /** Cancel active agent job by ID */ + cancel?: boolean; /** Maximum credits to spend (job fails if exceeded) */ maxCredits?: number; /** Check status of existing agent job */ diff --git a/src/types/crawl.ts b/src/types/crawl.ts index 5fc15e8fb..84b0599fa 100644 --- a/src/types/crawl.ts +++ b/src/types/crawl.ts @@ -45,6 +45,12 @@ export interface CrawlOptions { delay?: number; /** Maximum concurrency */ maxConcurrency?: number; + /** Pass-through options to scrape phase */ + scrapeOptions?: Record; + /** Webhook URL or webhook config */ + webhook?: string | Record; + /** Cancel active crawl job */ + cancel?: boolean; } export interface CrawlResult { diff --git a/src/types/scrape.ts b/src/types/scrape.ts index 517c43675..725b5bc55 100644 --- a/src/types/scrape.ts +++ b/src/types/scrape.ts @@ -57,6 +57,14 @@ export interface ScrapeOptions { location?: ScrapeLocation; /** Question to ask about the page content (query format) */ query?: string; + /** JSON schema for json format */ + schema?: Record; + /** Actions to run during scrape */ + actions?: Record[]; + /** Proxy mode */ + proxy?: string; + /** Webhook URL or webhook config */ + webhook?: string | Record; /** Persistent browser profile for maintaining state across scrapes */ profile?: { name: string; From db445ae1f6e8d60fb68622613be3743bde3196c2 Mon Sep 17 00:00:00 2001 From: Developers Digest <124798203+developersdigest@users.noreply.github.com> Date: Mon, 4 May 2026 11:08:10 -0400 Subject: [PATCH 2/3] remove unsupported scrape webhook flag --- src/__tests__/commands/scrape.test.ts | 42 +++++++++++++++++++++++++++ src/commands/scrape.ts | 4 --- src/index.ts | 7 ----- src/types/scrape.ts | 2 -- 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/__tests__/commands/scrape.test.ts b/src/__tests__/commands/scrape.test.ts index 28ddea26d..e57e8c5b8 100644 --- a/src/__tests__/commands/scrape.test.ts +++ b/src/__tests__/commands/scrape.test.ts @@ -308,6 +308,48 @@ describe('executeScrape', () => { }); }); + it('should include schema in json format when provided', async () => { + const mockResponse = { json: { title: 'Example Domain' } }; + const schema = { + type: 'object', + properties: { + title: { type: 'string' }, + }, + required: ['title'], + }; + mockClient.scrape.mockResolvedValue(mockResponse); + + await executeScrape({ + url: 'https://example.com', + formats: ['json'], + schema, + }); + + expect(mockClient.scrape).toHaveBeenCalledWith('https://example.com', { + formats: [{ type: 'json', schema }], + integration: 'cli', + }); + }); + + it('should include actions and proxy when provided', async () => { + const mockResponse = { markdown: '# Test' }; + const actions = [{ type: 'wait', milliseconds: 100 }]; + mockClient.scrape.mockResolvedValue(mockResponse); + + await executeScrape({ + url: 'https://example.com', + actions, + proxy: 'basic', + }); + + expect(mockClient.scrape).toHaveBeenCalledWith('https://example.com', { + formats: ['markdown'], + integration: 'cli', + actions, + proxy: 'basic', + }); + }); + it('should not include location parameter when not provided', async () => { const mockResponse = { markdown: '# Test' }; mockClient.scrape.mockResolvedValue(mockResponse); diff --git a/src/commands/scrape.ts b/src/commands/scrape.ts index a06e889c7..3973954a7 100644 --- a/src/commands/scrape.ts +++ b/src/commands/scrape.ts @@ -133,10 +133,6 @@ export async function executeScrape( scrapeParams.proxy = options.proxy; } - if (options.webhook) { - scrapeParams.webhook = options.webhook; - } - if (options.lockdown) { scrapeParams.lockdown = true; } diff --git a/src/index.ts b/src/index.ts index eb57be2c7..4ea6442bf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -372,10 +372,6 @@ function createScrapeCommand(): Command { .option('--actions ', 'JSON actions array to run during scrape') .option('--actions-file ', 'Path to JSON actions file') .option('--proxy ', 'Proxy mode for scraping (e.g., auto, basic)') - .option( - '--webhook ', - 'Webhook URL or webhook configuration object' - ) .action(async (positionalArgs, options) => { // Collect URLs from positional args and --url option @@ -405,12 +401,10 @@ function createScrapeCommand(): Command { let schema: Record | undefined; let actions: Record[] | undefined; - let webhook: string | Record | undefined; try { schema = parseJsonObject(options.schema, undefined, '--schema'); actions = parseJsonArray(options.actions, undefined, '--actions'); - webhook = parseWebhookOption(options.webhook, '--webhook'); } catch (error) { if (error instanceof Error) { console.error('Error:', error.message); @@ -460,7 +454,6 @@ function createScrapeCommand(): Command { schema, actions, proxy: options.proxy, - webhook, }; if (urls.length === 1) { diff --git a/src/types/scrape.ts b/src/types/scrape.ts index 725b5bc55..1387108ca 100644 --- a/src/types/scrape.ts +++ b/src/types/scrape.ts @@ -63,8 +63,6 @@ export interface ScrapeOptions { actions?: Record[]; /** Proxy mode */ proxy?: string; - /** Webhook URL or webhook config */ - webhook?: string | Record; /** Persistent browser profile for maintaining state across scrapes */ profile?: { name: string; From 1c3c5ab02d1145cc4501f08aa1c887dd0f625886 Mon Sep 17 00:00:00 2001 From: Developers Digest <124798203+developersdigest@users.noreply.github.com> Date: Mon, 4 May 2026 12:06:22 -0400 Subject: [PATCH 3/3] bump version to 1.16.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fcc50c6a7..66d6b8c93 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firecrawl-cli", - "version": "1.16.0", + "version": "1.16.1", "description": "Command-line interface for Firecrawl. Scrape, crawl, and extract data from any website directly from your terminal.", "main": "dist/index.js", "bin": {