Lucid Agents
Packages

@lucid-agents/catalog

YAML/CSV catalog-driven route generation for large product catalogs.

The catalog extension generates entrypoint routes from YAML or CSV product catalog files. Instead of writing hundreds of addEntrypoint() calls, define your catalog in a single file and let the extension register all routes at build time.

Installation

bun add @lucid-agents/catalog

Basic usage

Define a product catalog in YAML:

# products.yaml
products:
  - key: sentiment
    name: Sentiment Analysis
    description: Analyze text sentiment
    price: "0.05"
  - key: summarize
    name: Text Summarizer
    description: Summarize long-form content
    price:
      invoke: "0.50"
      stream: "0.10"
  - key: health
    name: Health Check
    description: Free health endpoint

Wire it into your agent:

import { createAgent } from '@lucid-agents/core';
import { http } from '@lucid-agents/http';
import { catalog } from '@lucid-agents/catalog';
import { createAgentApp } from '@lucid-agents/hono';

const agent = await createAgent({
  name: 'store-agent',
  version: '1.0.0',
})
  .use(http())
  .use(catalog({ file: './products.yaml' }))
  .build();

const { app } = await createAgentApp(agent);

Each item in the YAML file becomes a discoverable, invocable entrypoint on your agent. Items with a price field will require payment when combined with the payments or MPP extensions.

YAML format

YAML catalogs support a products wrapper object or a top-level array:

products:
  - key: translate
    name: Quick Translate
    description: Translate text between languages
    price: "0.10"
    network: "eip155:8453"
    metadata:
      category: nlp
      rateLimit: 200

Supported fields

FieldTypeRequiredDescription
keystringYesUnique route identifier. Becomes the entrypoint key.
namestringYesHuman-readable display name.
descriptionstringNoDescription shown in the agent manifest.
pricestring or objectNoFlat USD price, or { invoke, stream } for per-mode pricing.
networkstringNoCAIP-2 network identifier (e.g., eip155:8453).
metadataobjectNoArbitrary key-value pairs attached to the entrypoint.

Price variants

Flat price (same for invoke and stream):

- key: summarize
  name: Summarize
  price: "0.50"

Separate invoke and stream prices:

- key: generate
  name: Generate Text
  price:
    invoke: "1.00"
    stream: "0.20"

No price (free entrypoint):

- key: health
  name: Health Check

CSV format

CSV catalogs use a header row. Metadata columns use the meta_ prefix convention:

key,name,description,price,network,meta_category,meta_tier
ocr,OCR Scan,Extract text from images,0.50,,vision,standard
embed,Text Embedding,Generate vector embeddings,0.02,,ml,basic
classify,Text Classifier,Classify text,0.10,,nlp,basic
ColumnRequiredDescription
keyYesUnique route identifier.
nameYesDisplay name.
descriptionNoRoute description.
priceNoUSD price string. Empty = free.
networkNoCAIP-2 network identifier.
meta_*NoCollected into metadata with prefix stripped. meta_category becomes metadata.category.

CSV does not support the { invoke, stream } price object syntax. Use YAML if you need per-mode pricing.

Configuration

CatalogExtensionOptions

type CatalogExtensionOptions = {
  file: string;               // Path to .yaml, .yml, or .csv file
  keyPrefix?: string;         // Prefix for all item keys
  network?: string;           // Default CAIP-2 network
  handlerFactory?: HandlerFactory;  // Generate handlers from items
  inputSchema?: z.ZodTypeAny; // Custom input schema for all routes
};
OptionDefaultDescription
file(required)Path to the catalog file. Supports .yaml, .yml, and .csv.
keyPrefixundefinedPrepended to every item key. "store/" turns "widget" into "store/widget".
networkundefinedDefault network for items that don't specify their own.
handlerFactoryundefinedFunction receiving a CatalogItem, returning a handler function.
inputSchemaz.object({ params: z.record(...).optional() })Zod schema applied as input to all generated entrypoints.

Handler factory

The handler factory generates a route handler for each catalog item. This is where you connect catalog items to actual business logic:

import type { CatalogItem } from '@lucid-agents/catalog';

const handlerFactory = (item: CatalogItem) => {
  return async (ctx: { input: { params?: Record<string, unknown> } }) => {
    const model = item.metadata?.model as string ?? 'gpt-4o-mini';

    // Route to different backends based on catalog metadata
    const result = await callModel(model, ctx.input);

    return {
      output: {
        product: item.key,
        result,
      },
    };
  };
};

agent.use(catalog({
  file: './products.yaml',
  handlerFactory,
}));

Key prefix

Use keyPrefix to namespace routes, especially when loading multiple catalogs:

const agent = await createAgent({ name: 'multi-catalog', version: '1.0.0' })
  .use(http())
  .use(catalog({ file: './nlp-services.yaml', keyPrefix: 'nlp/' }))
  .use(catalog({ file: './vision-services.csv', keyPrefix: 'vision/' }))
  .build();

This produces routes like nlp/sentiment, nlp/translate, vision/classify.

Integration with payments

With x402

Combine with the payments extension for x402 payment gating. Any catalog item with a price field will automatically require payment:

import { payments, paymentsFromEnv } from '@lucid-agents/payments';
import { catalog } from '@lucid-agents/catalog';

const agent = await createAgent({
  name: 'paid-catalog',
  version: '1.0.0',
})
  .use(http())
  .use(payments({ config: paymentsFromEnv() }))
  .use(catalog({
    file: './products.yaml',
    handlerFactory: (item) => async ({ input }) => ({
      output: { product: item.name, data: 'processed' },
    }),
  }))
  .build();

With MPP

Combine with the MPP extension for multi-method payments (Tempo, Stripe, Lightning):

import { mpp, tempo } from '@lucid-agents/mpp';
import { catalog } from '@lucid-agents/catalog';

const agent = await createAgent({
  name: 'mpp-catalog',
  version: '1.0.0',
})
  .use(http())
  .use(mpp({
    config: {
      methods: [tempo.server({ currency: '0x...', recipient: '0x...' })],
      currency: 'usd',
    },
  }))
  .use(catalog({
    file: './products.yaml',
    keyPrefix: 'store/',
    handlerFactory: (item) => async ({ input }) => ({
      output: { product: item.key, price: item.price },
    }),
  }))
  .build();

Both x402 and MPP can coexist on the same agent alongside catalog.

Runtime access

After building, the catalog items are available on the runtime for introspection:

const agent = await createAgent({ ... })
  .use(catalog({ file: './products.yaml' }))
  .build();

// Access parsed catalog items
const items = (agent as any).catalog.items;
console.log(`Loaded ${items.length} products`);

for (const item of items) {
  console.log(`${item.key}: ${item.name} — $${item.price ?? 'free'}`);
}

API reference

parseCatalogYaml(content)

Parses a YAML string into validated CatalogItem[]. Accepts either a top-level array or { products: [...] } wrapper.

import { parseCatalogYaml } from '@lucid-agents/catalog';

const items = parseCatalogYaml(yamlString);

Throws if any item fails validation against CatalogItemSchema.

parseCatalogCsv(content)

Parses a CSV string into validated CatalogItem[]. Requires a key column. Columns prefixed with meta_ are collected into metadata.

import { parseCatalogCsv } from '@lucid-agents/catalog';

const items = parseCatalogCsv(csvString);

generateEntrypoints(items, options?)

Converts CatalogItem[] into EntrypointDef[] ready for registration:

import { generateEntrypoints } from '@lucid-agents/catalog';

const entrypoints = generateEntrypoints(items, {
  keyPrefix: 'api/',
  network: 'eip155:8453',
  handlerFactory: (item) => async ({ input }) => ({
    output: { result: item.name },
  }),
});

CatalogItemSchema

Zod schema for validating catalog items. Useful for custom parsing pipelines:

import { CatalogItemSchema } from '@lucid-agents/catalog';

const result = CatalogItemSchema.safeParse({
  key: 'test',
  name: 'Test Item',
  price: '0.01',
});

Network support

Network identifiers use CAIP-2 format:

NetworkCAIP-2 ID
Baseeip155:8453
Base Sepoliaeip155:84532
Ethereumeip155:1
Solana Mainnetsolana:mainnet
Solana Devnetsolana:devnet

Set a default via the network option, or override per item in your catalog file. Item-level network always takes precedence over the global default.

Exports

// Extension
export { catalog } from '@lucid-agents/catalog';

// Parsers
export { parseCatalogYaml, parseCatalogCsv } from '@lucid-agents/catalog';

// Entrypoint generation
export { generateEntrypoints } from '@lucid-agents/catalog';

// Schema & types
export {
  CatalogItemSchema,
  type CatalogItem,
  type CatalogConfig,
  type CatalogExtensionOptions,
} from '@lucid-agents/catalog';

On this page