From 2fd1738a7f2c53d6782d11c2ead1b1345f9142c6 Mon Sep 17 00:00:00 2001 From: Developers Digest <124798203+developersdigest@users.noreply.github.com> Date: Mon, 14 Sep 2026 16:54:46 -0400 Subject: [PATCH] Use published Alexandria SDK in CLI beta --- .github/workflows/test.yml | 2 +- package.json | 6 ++-- pnpm-lock.yaml | 50 ++++++++++++++++++++------- src/__tests__/alexandria-beta.test.ts | 38 +++++++++++++++++++- src/commands/alexandria.ts | 43 ++++++++++++++--------- 5 files changed, 104 insertions(+), 35 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4cc7925f18..4ac45813c7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: '18' + node-version: '22' - name: Setup pnpm uses: pnpm/action-setup@v4 diff --git a/package.json b/package.json index f989aaab7d..60ab3a13e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firecrawl-cli", - "version": "1.23.4-alexandria-beta.1", + "version": "1.23.4-alexandria-beta.2", "publishConfig": { "tag": "alexandria" }, @@ -67,7 +67,7 @@ }, "homepage": "https://docs.firecrawl.dev/cli", "engines": { - "node": ">=18.0.0" + "node": ">=22.0.0" }, "files": [ "dist", @@ -86,7 +86,7 @@ "dependencies": { "@inquirer/prompts": "^8.2.1", "commander": "^14.0.2", - "firecrawl": "4.24.0", + "firecrawl": "4.40.0", "yaml": "^2.9.0", "zod-to-json-schema": "3.24.6" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d5126333bf..eb1484dcf1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: ^14.0.2 version: 14.0.2 firecrawl: - specifier: 4.24.0 - version: 4.24.0 + specifier: 4.40.0 + version: 4.40.0 yaml: specifier: ^2.9.0 version: 2.9.0 @@ -507,6 +507,10 @@ packages: '@vitest/utils@4.0.16': resolution: {integrity: sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==} + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + ansi-escapes@7.2.0: resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==} engines: {node: '>=18'} @@ -526,8 +530,8 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - axios@1.15.2: - resolution: {integrity: sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==} + axios@1.18.0: + resolution: {integrity: sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -663,12 +667,12 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - firecrawl@4.24.0: - resolution: {integrity: sha512-kP9E3Up7eF/0Qe8x2tPU/Mcvx0v6Vks397Gkln1+gZKyLAGtIQNEW0+KZcDPS/meRPodGlVi4Z77doQ2Twq74w==} + firecrawl@4.40.0: + resolution: {integrity: sha512-YPNQvDyytR5cU4ETQwODI/D9ABs0bri70HAk43zCa07neP/c0DPQtea403iUvATzGy0wvn+bQjxqkJBxD7VTjw==} engines: {node: '>=22.0.0'} - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -720,6 +724,10 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -1391,6 +1399,12 @@ snapshots: '@vitest/pretty-format': 4.0.16 tinyrainbow: 3.0.3 + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + ansi-escapes@7.2.0: dependencies: environment: 1.1.0 @@ -1403,13 +1417,15 @@ snapshots: asynckit@0.4.0: {} - axios@1.15.2: + axios@1.18.0: dependencies: - follow-redirects: 1.15.11 + follow-redirects: 1.16.0 form-data: 4.0.5 + https-proxy-agent: 5.0.1 proxy-from-env: 2.1.0 transitivePeerDependencies: - debug + - supports-color braces@3.0.3: dependencies: @@ -1553,16 +1569,17 @@ snapshots: dependencies: to-regex-range: 5.0.1 - firecrawl@4.24.0: + firecrawl@4.40.0: dependencies: - axios: 1.15.2 + axios: 1.18.0 typescript-event-target: 1.1.2 zod: 3.25.76 zod-to-json-schema: 3.24.6(zod@3.25.76) transitivePeerDependencies: - debug + - supports-color - follow-redirects@1.15.11: {} + follow-redirects@1.16.0: {} form-data@4.0.5: dependencies: @@ -1611,6 +1628,13 @@ snapshots: dependencies: function-bind: 1.1.2 + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + human-signals@5.0.0: {} husky@9.1.7: {} diff --git a/src/__tests__/alexandria-beta.test.ts b/src/__tests__/alexandria-beta.test.ts index 5bd27e47b5..caa21c480c 100644 --- a/src/__tests__/alexandria-beta.test.ts +++ b/src/__tests__/alexandria-beta.test.ts @@ -162,7 +162,12 @@ it('relays terms refusals and keeps the request ID on failure', async () => { success: false, error: 'Accept provider terms', code: 'THIRD_PARTY_DATA_TERMS_REQUIRED', - requiresAction: { url: 'https://firecrawl.dev/terms/provider' }, + requiresAction: { + type: 'accept_terms', + terms: 'provider', + version: '1.0', + url: 'https://firecrawl.dev/terms/provider', + }, }; const result = await cli([ 'scrape', @@ -196,6 +201,37 @@ it('executes Find Tools through the same API and refuses keyless access', async }); }); +it('preserves charged SDK failures', async () => { + status = 402; + response = { + success: false, + error: 'Provider call failed after billing', + code: 'PROVIDER_ERROR', + chargeId: 'charge-1', + }; + const result = await cli(['scrape', '--alexandria', 'provider/lookup']); + expect(result.code).toBe(1); + expect(JSON.parse(result.stdout)).toMatchObject(response); + expect(requests).toHaveLength(1); +}); + +it('retains successful results and billing when one provider fails', async () => { + response.data.alexandria.push({ + provider: 'other', + capability: 'lookup', + error: { code: 'PROVIDER_ERROR', message: 'Unavailable', status: 503 }, + }); + const result = await cli([ + 'scrape', + '--alexandria', + 'provider/lookup', + 'other/lookup', + ]); + expect(result.code).toBe(1); + expect(JSON.parse(result.stdout)).toMatchObject(response); + expect(requests).toHaveLength(1); +}); + it('keeps URL scrape tool contracts in the output', async () => { response = { success: true, diff --git a/src/commands/alexandria.ts b/src/commands/alexandria.ts index ee85f339b1..7cb350027c 100644 --- a/src/commands/alexandria.ts +++ b/src/commands/alexandria.ts @@ -1,14 +1,11 @@ import { randomUUID } from 'node:crypto'; import { Command, Option } from 'commander'; +import { SdkError, type AlexandriaCall } from 'firecrawl'; import { getClient } from '../utils/client'; import { getApiKey } from '../utils/config'; import { writeOutput } from '../utils/output'; -type Call = { - provider: string; - capability: string; - options: Record; -}; +type Call = AlexandriaCall & { options: Record }; type Options = { apiKey?: string; apiUrl?: string; @@ -55,7 +52,17 @@ export function buildCalls(addresses: string[], values: string[] = []): Call[] { } export function apiFailure(error: unknown): Record { - const body = (error as any)?.response?.data; + const body = + (error as any)?.response?.data ?? + (error instanceof SdkError + ? { + error: error.message, + code: error.code, + chargeId: error.chargeId, + requiresAction: + (error.details as any)?.requiresAction ?? error.requiresAction, + } + : undefined); return { success: false, error: @@ -83,18 +90,20 @@ export async function handleAlexandria( let envelope: Record; try { const app = getClient({ apiKey: options.apiKey, apiUrl: options.apiUrl }); - const response = await (app as any).http.post( - '/v2/scrape', - { - alexandria: calls, - integration: 'cli', - timeout: options.timeout, + const result = await app.scrape({ + alexandria: calls, + integration: 'cli', + timeout: options.timeout, + requestId, + }); + envelope = { + success: true, + ...(result.scrapeId && { scrape_id: result.scrapeId }), + data: { + alexandria: result.alexandria, + creditsCost: result.creditsCost, }, - { headers: { 'x-request-id': requestId } } - ); - envelope = response.data; - if (!envelope || typeof envelope.success !== 'boolean') - throw new Error('Invalid Alexandria response.'); + }; } catch (error) { envelope = apiFailure(error); }