feat(catalog): YAML/CSV catalog-driven route generation#1617
Conversation
…eneration New extension package that generates entrypoint routes from YAML or CSV product catalog files, eliminating the need to manually define hundreds of routes for large product catalogs. Supports x402 and MPP pricing, custom handler factories, key prefixes, network overrides, and metadata. 39 tests passing across schema validation, YAML/CSV parsing, entrypoint generation, and extension integration.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
lucid-agents | 84d5d38 | Commit Preview URL Branch Preview URL |
Mar 21 2026, 08:29 PM |
Greptile SummaryThis PR introduces a new Key findings:
Confidence Score: 3/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant AgentBuilder
participant CatalogExtension
participant Parser
participant EntrypointRegistry
User->>AgentBuilder: createAgent().use(catalog({ file })).build()
AgentBuilder->>CatalogExtension: build(ctx)
alt YAML / YML file
CatalogExtension->>Parser: parseCatalogYaml(content)
Parser-->>CatalogExtension: CatalogItem[]
CatalogExtension-->>AgentBuilder: { catalog: { items: CatalogItem[] } }
else CSV file
CatalogExtension->>Parser: parseCatalogCsv(content) [async]
Note over CatalogExtension: pendingCsvParse set, catalogItems still []
CatalogExtension-->>AgentBuilder: { catalog: { items: [] } } ⚠️ always empty
end
AgentBuilder->>CatalogExtension: onBuild(runtime)
alt CSV pending
CatalogExtension->>Parser: await pendingCsvParse
Parser-->>CatalogExtension: CatalogItem[] (reassigns variable, not catalog.items)
end
CatalogExtension->>CatalogExtension: generateEntrypoints(catalogItems, options)
loop for each EntrypointDef
CatalogExtension->>EntrypointRegistry: runtime.entrypoints.add(ep)
end
EntrypointRegistry-->>User: entrypoints registered ✓
Prompt To Fix All With AIThis is a comment left during a code review.
Path: packages/catalog/src/extension.ts
Line: 37-41
Comment:
**CSV `catalog.items` always empty in runtime slice**
When a CSV file is used, `build()` returns `{ catalog: { items: catalogItems } }` while `catalogItems` is still `[]` (CSV parsing is deferred). Later in `onBuild`, `catalogItems` is **reassigned** to the parsed array — but this doesn't update the already-returned `catalog.items` reference, which still points to the original empty array.
```typescript
// build() captures a reference to the current (empty) array
return {
catalog: {
items: catalogItems, // [] at this point for CSV
},
};
```
Then in `onBuild`:
```typescript
catalogItems = await pendingCsvParse; // ← reassigns the variable, NOT the captured array
```
Any consumer that reads `runtime.catalog.items` after build will always see `[]` for CSV-based catalogs, even though entrypoints are correctly registered. The existing test suite confirms this gap — the CSV test only verifies that entrypoints are added, not that `catalog.items` is populated.
**Fix options:**
1. Mutate the existing array in `onBuild` instead of reassigning: `catalogItems.push(...(await pendingCsvParse))`, and initialise `catalogItems` above it.
2. Store the returned runtime object and update it in `onBuild`: `runtimeSlice.catalog!.items = catalogItems`.
3. Return the catalog runtime slice only from `onBuild` via a pattern that supports deferred population.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: packages/catalog/src/entrypoints.ts
Line: 15-18
Comment:
**Return type should be `EntrypointDef[]` instead of `any[]`**
`generateEntrypoints` returns `any[]`, losing all type-safety for callers. `@lucid-agents/types` is a declared dependency and exports `EntrypointDef` — the project style guide in `AGENTS.md` explicitly calls out "Avoid `any`, prefer explicit types or `unknown`".
```suggestion
import type { EntrypointDef } from '@lucid-agents/types';
export function generateEntrypoints(
items: CatalogItem[],
options?: GenerateOptions,
): EntrypointDef[] {
```
The `entrypoint` object built inside the map can also be typed as `EntrypointDef` rather than `Record<string, unknown>`.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: packages/catalog/src/extension.ts
Line: 11-15
Comment:
**Extension does not implement the `Extension<R>` interface**
The return type of `catalog()` is manually typed as `{ name, build, onBuild? }` using `any` for both the `build` context and the `onBuild` runtime parameter. `@lucid-agents/types` exports the `Extension<R>` interface that all extensions should conform to — `BuildContext` and `AgentRuntime` are the correct types for those parameters.
Using `any` means TypeScript won't catch shape mismatches and the builder's type inference will degrade for consumers.
```typescript
import type { Extension, BuildContext } from '@lucid-agents/types';
import type { AgentRuntime } from '@lucid-agents/types';
export function catalog(
options: CatalogExtensionOptions,
): Extension<{ catalog?: CatalogRuntime }> {
// ...
return {
name: 'catalog',
build(ctx: BuildContext): { catalog?: CatalogRuntime } { ... },
async onBuild(runtime: AgentRuntime): Promise<void> { ... },
};
}
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: packages/catalog/src/parser.ts
Line: 37-39
Comment:
**`parseCatalogCsv` is unnecessarily `async`**
The function imports and calls `csv-parse/sync` which is fully synchronous — there is no `await` inside the function body. Marking it `async` wraps the return value in a redundant `Promise`, forces the CSV branch in `extension.ts` to defer to `onBuild` (contributing to the `catalog.items` bug above), and adds overhead with no benefit.
Consider removing `async` and returning `CatalogItem[]` directly, then calling it synchronously in `build()` alongside the YAML path.
```suggestion
export function parseCatalogCsv(
content: string,
): CatalogItem[] {
```
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: "feat(catalog): add @..." |
| return { | ||
| catalog: { | ||
| items: catalogItems, | ||
| }, | ||
| }; |
There was a problem hiding this comment.
CSV
catalog.items always empty in runtime slice
When a CSV file is used, build() returns { catalog: { items: catalogItems } } while catalogItems is still [] (CSV parsing is deferred). Later in onBuild, catalogItems is reassigned to the parsed array — but this doesn't update the already-returned catalog.items reference, which still points to the original empty array.
// build() captures a reference to the current (empty) array
return {
catalog: {
items: catalogItems, // [] at this point for CSV
},
};Then in onBuild:
catalogItems = await pendingCsvParse; // ← reassigns the variable, NOT the captured arrayAny consumer that reads runtime.catalog.items after build will always see [] for CSV-based catalogs, even though entrypoints are correctly registered. The existing test suite confirms this gap — the CSV test only verifies that entrypoints are added, not that catalog.items is populated.
Fix options:
- Mutate the existing array in
onBuildinstead of reassigning:catalogItems.push(...(await pendingCsvParse)), and initialisecatalogItemsabove it. - Store the returned runtime object and update it in
onBuild:runtimeSlice.catalog!.items = catalogItems. - Return the catalog runtime slice only from
onBuildvia a pattern that supports deferred population.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/catalog/src/extension.ts
Line: 37-41
Comment:
**CSV `catalog.items` always empty in runtime slice**
When a CSV file is used, `build()` returns `{ catalog: { items: catalogItems } }` while `catalogItems` is still `[]` (CSV parsing is deferred). Later in `onBuild`, `catalogItems` is **reassigned** to the parsed array — but this doesn't update the already-returned `catalog.items` reference, which still points to the original empty array.
```typescript
// build() captures a reference to the current (empty) array
return {
catalog: {
items: catalogItems, // [] at this point for CSV
},
};
```
Then in `onBuild`:
```typescript
catalogItems = await pendingCsvParse; // ← reassigns the variable, NOT the captured array
```
Any consumer that reads `runtime.catalog.items` after build will always see `[]` for CSV-based catalogs, even though entrypoints are correctly registered. The existing test suite confirms this gap — the CSV test only verifies that entrypoints are added, not that `catalog.items` is populated.
**Fix options:**
1. Mutate the existing array in `onBuild` instead of reassigning: `catalogItems.push(...(await pendingCsvParse))`, and initialise `catalogItems` above it.
2. Store the returned runtime object and update it in `onBuild`: `runtimeSlice.catalog!.items = catalogItems`.
3. Return the catalog runtime slice only from `onBuild` via a pattern that supports deferred population.
How can I resolve this? If you propose a fix, please make it concise.| export function generateEntrypoints( | ||
| items: CatalogItem[], | ||
| options?: GenerateOptions, | ||
| ): any[] { |
There was a problem hiding this comment.
Return type should be
EntrypointDef[] instead of any[]
generateEntrypoints returns any[], losing all type-safety for callers. @lucid-agents/types is a declared dependency and exports EntrypointDef — the project style guide in AGENTS.md explicitly calls out "Avoid any, prefer explicit types or unknown".
| export function generateEntrypoints( | |
| items: CatalogItem[], | |
| options?: GenerateOptions, | |
| ): any[] { | |
| import type { EntrypointDef } from '@lucid-agents/types'; | |
| export function generateEntrypoints( | |
| items: CatalogItem[], | |
| options?: GenerateOptions, | |
| ): EntrypointDef[] { |
The entrypoint object built inside the map can also be typed as EntrypointDef rather than Record<string, unknown>.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/catalog/src/entrypoints.ts
Line: 15-18
Comment:
**Return type should be `EntrypointDef[]` instead of `any[]`**
`generateEntrypoints` returns `any[]`, losing all type-safety for callers. `@lucid-agents/types` is a declared dependency and exports `EntrypointDef` — the project style guide in `AGENTS.md` explicitly calls out "Avoid `any`, prefer explicit types or `unknown`".
```suggestion
import type { EntrypointDef } from '@lucid-agents/types';
export function generateEntrypoints(
items: CatalogItem[],
options?: GenerateOptions,
): EntrypointDef[] {
```
The `entrypoint` object built inside the map can also be typed as `EntrypointDef` rather than `Record<string, unknown>`.
How can I resolve this? If you propose a fix, please make it concise.| export function catalog(options: CatalogExtensionOptions): { | ||
| name: string; | ||
| build: (ctx: any) => { catalog?: CatalogRuntime }; | ||
| onBuild?: (runtime: any) => Promise<void>; | ||
| } { |
There was a problem hiding this comment.
Extension does not implement the
Extension<R> interface
The return type of catalog() is manually typed as { name, build, onBuild? } using any for both the build context and the onBuild runtime parameter. @lucid-agents/types exports the Extension<R> interface that all extensions should conform to — BuildContext and AgentRuntime are the correct types for those parameters.
Using any means TypeScript won't catch shape mismatches and the builder's type inference will degrade for consumers.
import type { Extension, BuildContext } from '@lucid-agents/types';
import type { AgentRuntime } from '@lucid-agents/types';
export function catalog(
options: CatalogExtensionOptions,
): Extension<{ catalog?: CatalogRuntime }> {
// ...
return {
name: 'catalog',
build(ctx: BuildContext): { catalog?: CatalogRuntime } { ... },
async onBuild(runtime: AgentRuntime): Promise<void> { ... },
};
}Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/catalog/src/extension.ts
Line: 11-15
Comment:
**Extension does not implement the `Extension<R>` interface**
The return type of `catalog()` is manually typed as `{ name, build, onBuild? }` using `any` for both the `build` context and the `onBuild` runtime parameter. `@lucid-agents/types` exports the `Extension<R>` interface that all extensions should conform to — `BuildContext` and `AgentRuntime` are the correct types for those parameters.
Using `any` means TypeScript won't catch shape mismatches and the builder's type inference will degrade for consumers.
```typescript
import type { Extension, BuildContext } from '@lucid-agents/types';
import type { AgentRuntime } from '@lucid-agents/types';
export function catalog(
options: CatalogExtensionOptions,
): Extension<{ catalog?: CatalogRuntime }> {
// ...
return {
name: 'catalog',
build(ctx: BuildContext): { catalog?: CatalogRuntime } { ... },
async onBuild(runtime: AgentRuntime): Promise<void> { ... },
};
}
```
How can I resolve this? If you propose a fix, please make it concise.| export async function parseCatalogCsv( | ||
| content: string, | ||
| ): Promise<CatalogItem[]> { |
There was a problem hiding this comment.
parseCatalogCsv is unnecessarily async
The function imports and calls csv-parse/sync which is fully synchronous — there is no await inside the function body. Marking it async wraps the return value in a redundant Promise, forces the CSV branch in extension.ts to defer to onBuild (contributing to the catalog.items bug above), and adds overhead with no benefit.
Consider removing async and returning CatalogItem[] directly, then calling it synchronously in build() alongside the YAML path.
| export async function parseCatalogCsv( | |
| content: string, | |
| ): Promise<CatalogItem[]> { | |
| export function parseCatalogCsv( | |
| content: string, | |
| ): CatalogItem[] { |
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/catalog/src/parser.ts
Line: 37-39
Comment:
**`parseCatalogCsv` is unnecessarily `async`**
The function imports and calls `csv-parse/sync` which is fully synchronous — there is no `await` inside the function body. Marking it `async` wraps the return value in a redundant `Promise`, forces the CSV branch in `extension.ts` to defer to `onBuild` (contributing to the `catalog.items` bug above), and adds overhead with no benefit.
Consider removing `async` and returning `CatalogItem[]` directly, then calling it synchronously in `build()` alongside the YAML path.
```suggestion
export function parseCatalogCsv(
content: string,
): CatalogItem[] {
```
How can I resolve this? If you propose a fix, please make it concise.- Make parseCatalogCsv synchronous (csv-parse/sync is already sync) - Fix CSV catalog.items being empty in runtime slice (no longer deferred) - Type extension with Extension<R> interface, BuildContext, AgentRuntime - Type generateEntrypoints return as EntrypointDef[] instead of any[] - Use CAIP-2 network format in tests (solana:devnet, eip155:84532)
- Comprehensive README with API reference, YAML/CSV format docs, integration examples (x402, MPP, handler factories, key prefixes) - Example: catalog-mpp-store.ts showing 10-product YAML catalog with MPP tempo payments and handler factory - Example: products.csv showing CSV format with meta_ columns - Add @lucid-agents/catalog to examples dependencies
- Add packages/catalog.mdx with full documentation: YAML/CSV format, configuration, handler factories, payment integration, API reference - Add catalog to sidebar navigation (Extensions section) - Add catalog card to packages index page - Update architecture diagram to include catalog extension
- Sort imports alphabetically (simple-import-sort/imports) - Replace `as any` cast with typed assertion for catalog runtime access
Run prettier on catalog-mpp-store.ts and products.yaml.
Summary
@lucid-agents/catalogpackage that generates entrypoint routes from YAML or CSV product catalog filesaddEntrypoint()calls)Usage
Also supports CSV with
meta_prefixed columns for metadata:What's included
types.tsCatalogItemSchema(Zod),CatalogItem, config typesparser.tsparseCatalogYaml()+parseCatalogCsv()entrypoints.tsgenerateEntrypoints()— maps items toEntrypointDef[]extension.tscatalog()extension factory withonBuildhookTest plan
bun testin packages/catalog)bun run build)🤖 Generated with Claude Code