Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,14 @@ 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 <capability> --json
```

The category overview is available in `1.23.4-alexandria-beta.9` onward. `list` and `list-tools` are interchangeable, including under `firecrawl alexandria`. The root explains how to find and call tools and lists live category descriptions. Choose a category to see its providers, or jump directly to a provider. Selecting a capability reveals its inputs, response, examples, and price. Browsable results expose next commands in JSON; the text guide explains how to select each category. Selecting a capability displays the final contract. Generated commands use `firecrawl`; when using `npx`, replace that prefix with `npx firecrawl-cli@alexandria`.

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

Expand Down
4 changes: 1 addition & 3 deletions beta-skills/firecrawl-alexandria/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <returned-capability-id> --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.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "firecrawl-cli",
"version": "1.23.4-alexandria-beta.9",
"version": "1.23.4-alexandria-beta.10",
"publishConfig": {
"tag": "alexandria"
},
Expand Down
24 changes: 15 additions & 9 deletions src/__tests__/alexandria-beta.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 },
Expand Down Expand Up @@ -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', []);
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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([
Expand Down
63 changes: 16 additions & 47 deletions src/commands/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import { getApiKey, getConfig } from '../utils/config';
type Selectors = Record<string, unknown>;
type ListOptions = AlexandriaOptions & {
category?: boolean;
groups?: boolean;
group?: boolean;
limit?: number;
request?: string;
providers?: boolean;
Expand Down Expand Up @@ -48,7 +46,6 @@ type Item = {
name?: string;
description?: string;
attribution?: string;
group?: string;
capability?: string;
toolCount?: number;
creditsCost?: number;
Expand Down Expand Up @@ -200,22 +197,21 @@ 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))
) ||
!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',
Expand All @@ -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}`;
}

Expand Down Expand Up @@ -291,8 +286,6 @@ export async function handleList(
options.request &&
(path.length ||
options.category ||
options.groups ||
options.group ||
options.providers ||
options.limit !== undefined)
)
Expand All @@ -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.'
);
Expand Down Expand Up @@ -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) {
Expand All @@ -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();
Expand Down Expand Up @@ -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 <number>',
'Provider/tool results per page (1-100; default: 20); the root shows all categories',
Expand All @@ -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 <capability> --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 <capability> --json\n\nProvider IDs take precedence over categories.\nNo listed tool is executed. Search by task with firecrawl search --sources alexandria.\n'
)
.action(handleList);
}
Loading