From bb9a307a88de7356c85368ff956bc2aca5ce5184 Mon Sep 17 00:00:00 2001 From: Developers Digest <124798203+developersdigest@users.noreply.github.com> Date: Wed, 16 Sep 2026 15:41:27 -0400 Subject: [PATCH] fix(cli): simplify Alexandria browsing without groups --- README.md | 4 +- beta-skills/firecrawl-alexandria/SKILL.md | 4 +- package.json | 2 +- src/__tests__/alexandria-beta.test.ts | 24 +++++---- src/commands/list.ts | 63 ++++++----------------- 5 files changed, 34 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 07e5fb9a64..d63b4772f9 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,6 @@ npx firecrawl-cli@alexandria list --providers # flat provider list npx firecrawl-cli@alexandria list finance # providers in a category npx firecrawl-cli@alexandria list benzinga # provider's tools npx firecrawl-cli@alexandria list-tools benzinga # same browsing interface -npx firecrawl-cli@alexandria list benzinga --groups # optional tool groups -npx firecrawl-cli@alexandria list benzinga calendar --group npx firecrawl-cli@alexandria list benzinga --json ``` @@ -37,7 +35,7 @@ The category overview is available in `1.23.4-alexandria-beta.9` onward. `list` Discovery is free and never executes the listed tools. The root reads `GET /exchange/discover` on the configured Firecrawl API using your existing credentials; provider and tool lookups use the Find Tools meta tool through Scrape. Category membership and descriptions stay on the server. The root shows all returned categories; `--limit` controls provider/tool page size (default 20, maximum 100). Follow `More` to continue a page. Root `--json` exposes categories at `data.items`; provider/tool JSON keeps the Scrape envelope at `data.alexandria[0].data`. Both include request IDs and navigation where available. -Provider IDs take precedence over category IDs; use `--category` to select a category explicitly. Display names such as `retail`, `developer`, and `public-records` also resolve to their catalog category IDs. Exact capability IDs take precedence over group IDs; use `--group` to select a group explicitly. Use `firecrawl search --sources alexandria` to find tools by task, or `firecrawl find-tools` for URL lookup and raw catalog selectors. +Provider IDs take precedence over category IDs; use `--category` to select a category explicitly. Display names such as `retail`, `developer`, and `public-records` also resolve to their catalog category IDs. Follow a provider with a complete capability ID, such as `calendar/ratings`, to inspect its contract. Use `firecrawl search --sources alexandria` to find tools by task, or `firecrawl find-tools` for URL lookup and raw catalog selectors. ### Setup Skills, Workflows, and MCP diff --git a/beta-skills/firecrawl-alexandria/SKILL.md b/beta-skills/firecrawl-alexandria/SKILL.md index 854241071d..8830a596f5 100644 --- a/beta-skills/firecrawl-alexandria/SKILL.md +++ b/beta-skills/firecrawl-alexandria/SKILL.md @@ -31,12 +31,10 @@ With beta `1.23.4-alexandria-beta.9` or newer, use `list` when the user wants to npx firecrawl-cli@alexandria alexandria list npx firecrawl-cli@alexandria list finance npx firecrawl-cli@alexandria list benzinga -npx firecrawl-cli@alexandria list benzinga --groups -npx firecrawl-cli@alexandria list benzinga calendar --group npx firecrawl-cli@alexandria list benzinga --json ``` -The root shows an introduction, discovery/execution commands, and live categories with descriptions. A category lists its providers; a provider lists compact tools directly. Selecting a capability expands only that contract, including price, inputs, response, and examples. Categories are optional: use returned provider and capability IDs directly. Use `list --providers` only when a flat provider inventory is needed. `list-tools` is an alias for `list`; both also work under `alexandria`. `--category` and `--group` resolve ambiguous IDs explicitly. Category display names such as `retail`, `developer`, and `public-records` are accepted alongside the returned canonical IDs. +The root shows an introduction, discovery/execution commands, and live categories with descriptions. A category lists its providers; a provider lists compact tools directly. Selecting a complete capability ID, such as `calendar/ratings`, expands only that contract, including price, inputs, response, and examples. Categories are optional: use returned provider and capability IDs directly. Use `list --providers` only when a flat provider inventory is needed. `list-tools` is an alias for `list`; both also work under `alexandria`. `--category` resolves ambiguous category/provider IDs explicitly. Category display names such as `retail`, `developer`, and `public-records` are accepted alongside the returned canonical IDs. The root reads the free public `GET /exchange/discover` route on the configured Firecrawl API with existing credentials. Category rows are at `data.items` in JSON. Provider and tool lookups use the free Find Tools meta tool through Scrape, with rows at `data.alexandria[0].data.items`. Both include `discoveryRequests` and `nextCommand` navigation; no listed tool is executed. The root shows all categories. Provider/tool page size defaults to 20 (`--limit 1–100`); follow `More`/`nextCommand` only when needed. Generated commands start with `firecrawl`; replace that prefix with `npx firecrawl-cli@alexandria` to stay on this beta. Raw `--request` next requests preserve selectors and pagination and must not be mixed with a path or filters. diff --git a/package.json b/package.json index 3b0d453160..958fc273ce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firecrawl-cli", - "version": "1.23.4-alexandria-beta.9", + "version": "1.23.4-alexandria-beta.10", "publishConfig": { "tag": "alexandria" }, diff --git a/src/__tests__/alexandria-beta.test.ts b/src/__tests__/alexandria-beta.test.ts index d0aa03e1f6..602454ec31 100644 --- a/src/__tests__/alexandria-beta.test.ts +++ b/src/__tests__/alexandria-beta.test.ts @@ -171,9 +171,6 @@ it('browses live provider IDs directly or through a category without expanding c ]) { expect((await cli(['list', ...args])).code).toBe(0); } - expect((await cli(['alexandria', 'list', 'benzinga', '--groups'])).code).toBe( - 0 - ); expect((await cli(['list-tools', 'benzinga'])).code).toBe(0); expect((await cli(['alexandria', 'list-tools', 'benzinga'])).code).toBe(0); expect((await cli(['list', 'Retail', '--category'])).code).toBe(0); @@ -192,7 +189,6 @@ it('browses live provider IDs directly or through a category without expanding c level: 'tools', limit: 20, }, - { providers: ['benzinga'], level: 'groups', limit: 20 }, { providers: ['benzinga'], level: 'tools', limit: 20 }, { providers: ['benzinga'], level: 'tools', limit: 20 }, { categories: ['Retail'], level: 'providers', limit: 20 }, @@ -221,7 +217,7 @@ it('browses live provider IDs directly or through a category without expanding c ]); }); -it('expands only a selected tool and falls back to a compact group listing', async () => { +it('expands only a selected tool without falling back to a group', async () => { responseFor = (body) => { const options = body.alexandria[0].options; if (options.capabilities?.[0] === 'calendar') return catalogue('tools', []); @@ -246,15 +242,17 @@ it('expands only a selected tool and falls back to a compact group listing', asy capabilities: ['calendar/earnings'], expand: ['options', 'response', 'examples'], }); - const group = await cli(['list', 'benzinga', 'calendar']); - expect(group.code).toBe(0); - expect(group.stdout).not.toContain('Inputs:'); + const missing = await cli(['list', 'benzinga', 'calendar']); + expect(missing.code).toBe(0); + expect(missing.stdout).toContain('No matching tools'); expect(requests.at(-1)?.body.alexandria[0].options).toEqual({ providers: ['benzinga'], - groups: ['calendar'], + capabilities: ['calendar'], + expand: ['options', 'response', 'examples'], level: 'tools', limit: 20, }); + expect(requests).toHaveLength(4); }); it('preserves scoped next requests, pagination and discovery receipts', async () => { @@ -307,6 +305,14 @@ it('preserves scoped next requests, pagination and discovery receipts', async () }); it('refuses execution through list and propagates discovery access errors', async () => { + for (const args of [ + ['alexandria', 'list', 'benzinga', '--groups'], + ['list', 'benzinga', 'calendar', '--group'], + ]) { + const removed = await cli(args); + expect(removed.code).toBe(1); + expect(removed.stderr).toContain('unknown option'); + } expect( ( await cli([ diff --git a/src/commands/list.ts b/src/commands/list.ts index d46df267bd..78f4a8f404 100644 --- a/src/commands/list.ts +++ b/src/commands/list.ts @@ -13,8 +13,6 @@ import { getApiKey, getConfig } from '../utils/config'; type Selectors = Record; type ListOptions = AlexandriaOptions & { category?: boolean; - groups?: boolean; - group?: boolean; limit?: number; request?: string; providers?: boolean; @@ -48,7 +46,6 @@ type Item = { name?: string; description?: string; attribution?: string; - group?: string; capability?: string; toolCount?: number; creditsCost?: number; @@ -200,7 +197,7 @@ function itemCommand(item: Item): string | undefined { : Array.isArray(value) && value.length === 1 && value[0] === id; // Keep scoped and future selectors intact when a short path cannot express them. if ( - ![item.provider, item.capability, item.group, category].every( + ![item.provider, item.capability, category].every( (id) => id === undefined || (typeof id === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9_./:-]*$/.test(id)) @@ -208,14 +205,13 @@ function itemCommand(item: Item): string | undefined { !only(options.providers, item.provider) || !only(options.categories, category) || !only(options.capabilities, item.capability) || - (options.groups !== undefined && !only(options.groups, item.group)) || + options.groups !== undefined || (options.offset !== undefined && options.offset !== 0) || Object.keys(options).some( (key) => ![ 'providers', 'categories', - 'groups', 'capabilities', 'level', 'limit', @@ -232,7 +228,6 @@ function itemCommand(item: Item): string | undefined { ? ` --limit ${options.limit}` : ''); if (item.capability) return `${base} ${quote(item.capability)}${flags}`; - if (item.group) return `${base} ${quote(item.group)} --group${flags}`; return `${base}${flags}`; } @@ -291,8 +286,6 @@ export async function handleList( options.request && (path.length || options.category || - options.groups || - options.group || options.providers || options.limit !== undefined) ) @@ -301,16 +294,9 @@ export async function handleList( ); if (path.some((part) => !part.trim() || part.length > 200)) throw new Error('Use non-empty catalogue IDs of at most 200 characters.'); - if (options.group && (options.groups || path.length < 2)) - throw new Error( - '--group requires a provider and group path, without --groups.' - ); - if ((options.category || options.groups) && !path.length) + if (options.category && !path.length) throw new Error('Provide a category or provider ID.'); - if ( - options.providers && - (path.length || options.category || options.groups || options.group) - ) + if (options.providers && (path.length || options.category)) throw new Error( '--providers lists all providers; omit the path and other selectors.' ); @@ -373,14 +359,13 @@ export async function handleList( if (options.request) return fetchPage(parseFindToolsRequest(options.request).options); if (!path.length) return fetchPage({ level: 'providers', limit }); - const providerLevel = options.groups ? 'groups' : 'tools'; let scope: Selectors = { providers: [path[0]] }; let remaining = path.slice(1); let result = options.category ? undefined : await fetchPage({ ...scope, - level: remaining.length ? 'providers' : providerLevel, + level: remaining.length ? 'providers' : 'tools', limit, }); if (!result?.page.total) { @@ -393,29 +378,18 @@ export async function handleList( if (!result.page.total || !remaining.length) return result; scope.providers = [remaining[0]]; remaining = remaining.slice(1); - if (!remaining.length) { - if (options.group) - throw new Error('Provide a group ID after the provider ID.'); - return fetchPage({ ...scope, level: providerLevel, limit }); - } + if (!remaining.length) + return fetchPage({ ...scope, level: 'tools', limit }); } if (!remaining.length) return result; - if (options.groups) - throw new Error( - '--groups lists a provider’s groups; omit it when selecting a tool.' - ); const selected = remaining.join('/'); - if (!options.group) { - const contract = await fetchPage({ - ...scope, - capabilities: [selected], - level: 'tools', - expand: ['options', 'response', 'examples'], - limit, - }); - if (contract.page.total) return contract; - } - return fetchPage({ ...scope, groups: [selected], level: 'tools', limit }); + return fetchPage({ + ...scope, + capabilities: [selected], + level: 'tools', + expand: ['options', 'response', 'examples'], + limit, + }); } const { envelope, page } = await resolve(); @@ -462,18 +436,13 @@ export function createListCommand(): Command { ) .argument( '[path...]', - 'Provider or category, optionally followed by a group or capability' + 'Provider or category, optionally followed by a capability' ) .option( '--category', 'Treat the first ID as a category when a provider has the same ID' ) - .option('--groups', 'List a provider’s tool groups instead of its tools') .option('--providers', 'List all providers instead of the category index') - .option( - '--group', - 'Treat the final path as a group instead of a capability' - ) .option( '--limit ', 'Provider/tool results per page (1-100; default: 20); the root shows all categories', @@ -495,7 +464,7 @@ export function createListCommand(): Command { .option('--pretty', 'Format JSON') .addHelpText( 'after', - '\nExamples:\n firecrawl alexandria list\n firecrawl list --providers\n firecrawl list finance\n firecrawl list benzinga\n firecrawl list benzinga --groups\n firecrawl list benzinga calendar --group\n firecrawl list benzinga --json\n\nProvider IDs take precedence over categories; exact capabilities take precedence over groups.\nNo listed tool is executed. Search by task with firecrawl search --sources alexandria.\n' + '\nExamples:\n firecrawl alexandria list\n firecrawl list --providers\n firecrawl list finance\n firecrawl list benzinga\n firecrawl list benzinga --json\n\nProvider IDs take precedence over categories.\nNo listed tool is executed. Search by task with firecrawl search --sources alexandria.\n' ) .action(handleList); }