diff --git a/docs/content/docs/api-reference/cli.mdx b/docs/content/docs/api-reference/cli.mdx index fd7968ee1..3ed9c1074 100644 --- a/docs/content/docs/api-reference/cli.mdx +++ b/docs/content/docs/api-reference/cli.mdx @@ -103,7 +103,7 @@ Every framework overlay includes a `get_weather` example backed by Open-Meteo. A **OpenUI examples** -Interactive `openui create` lists starter templates, and option to create an app from listed [OpenUI examples](https://github.com/thesysdev/openui/blob/main/examples). `--example ` skips both menus. `--example` cannot be combined with `--template` or `--backend-framework`. Any other folder name in the catalog works the same way. +Interactive `openui create` lists starter templates and featured [OpenUI examples](https://github.com/thesysdev/openui/blob/main/examples). `--example ` skips both menus. `--example` cannot be combined with `--template` or `--backend-framework`. Any other folder name in the catalog works the same way. **Conversation storage** @@ -544,3 +544,7 @@ collection. `createLibrary`, `PromptOptions`, and the `Library` interface that `openui generate` read. + +The backend framework picker shows examples marked `featured: true` +in `examples/examples.json`, in catalog order. Select “More OpenUI Examples →” for the +complete catalog, or pass any catalog name through `--example`. diff --git a/examples/README.md b/examples/README.md index ec1ecadce..f514411b0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -17,6 +17,14 @@ Each example has one primary home. Complete workflows with a companion tutorial `miscellaneous` is intentionally flat. If several examples develop the same stable integration seam, promote that seam to a top-level category instead of adding nested miscellaneous taxonomies. +## CLI selection + +Set `"featured": true` on an entry in `examples.json` to show it in the CLI's +backend framework picker. It shows up to five featured examples in catalog +order beside the backend choices. “More examples…” opens the complete catalog, +including entries with omitted or false flags. Every example is also available +through `openui create --example `. + ## Catalog ### Agent frameworks diff --git a/examples/examples.json b/examples/examples.json index fbc631ca6..eac950c14 100644 --- a/examples/examples.json +++ b/examples/examples.json @@ -16,7 +16,8 @@ "title": "Mastra", "description": "A Mastra agent connected to OpenUI through AG-UI.", "path": "agent-frameworks/mastra", - "envKey": "THESYS_API_KEY" + "envKey": "THESYS_API_KEY", + "featured": true }, { "title": "Vercel AI SDK", @@ -82,13 +83,15 @@ "title": "Material UI", "description": "A broad Material UI component library for generated interfaces.", "path": "design-systems/material-ui", - "envKey": "THESYS_API_KEY" + "envKey": "THESYS_API_KEY", + "featured": true }, { - "title": "shadcn/ui", + "title": "ShadCN", "description": "A broad shadcn/ui component library for generated interfaces.", "path": "design-systems/shadcn", - "envKey": "THESYS_API_KEY" + "envKey": "THESYS_API_KEY", + "featured": true }, { "title": "Grok Build", @@ -97,10 +100,11 @@ "envKey": "XAI_API_KEY" }, { - "title": "Pi", + "title": "Pi Harness", "description": "A Pi coding-agent session on OpenUI Cloud.", "path": "harnesses/pi", - "envKey": "THESYS_API_KEY" + "envKey": "THESYS_API_KEY", + "featured": true }, { "title": "Autofix", diff --git a/packages/openui-cli/README.md b/packages/openui-cli/README.md index e2e4d6e2e..3df1ac1d6 100644 --- a/packages/openui-cli/README.md +++ b/packages/openui-cli/README.md @@ -149,7 +149,7 @@ Every framework overlay includes `get_weather` as its example app-owned function #### OpenUI examples -Interactive `openui create` offers to scaffold [OpenUI examples](https://github.com/thesysdev/openui/blob/main/examples). Pass `--example ` to skip the menus. `--example` cannot be combined with `--template` or `--backend-framework`. +Interactive `openui create` offers featured [OpenUI examples](https://github.com/thesysdev/openui/blob/main/examples). Pass `--example ` to skip the menus. `--example` cannot be combined with `--template` or `--backend-framework`. ```bash openui create --example shadcn @@ -377,3 +377,7 @@ openui create --no-telemetry ## License [MIT](https://github.com/thesysdev/openui/blob/main/LICENSE) + +The backend framework picker shows examples marked `featured: true` +in `examples/examples.json`, in catalog order. Select “More OpenUI Examples →” for the +complete catalog, or pass any catalog name through `--example`. diff --git a/packages/openui-cli/src/commands/create/lib/examples-catalog.ts b/packages/openui-cli/src/commands/create/lib/examples-catalog.ts index 5fc9e1e84..e5d51a407 100644 --- a/packages/openui-cli/src/commands/create/lib/examples-catalog.ts +++ b/packages/openui-cli/src/commands/create/lib/examples-catalog.ts @@ -15,6 +15,8 @@ export type ExampleProject = { envFile: ".env"; /** Primary env var to prompt for. Omit when the example needs several keys. */ envKey?: string; + /** Show this example in the curated interactive picker. */ + featured?: boolean; }; function catalogError(message: string): CreateError { @@ -27,6 +29,7 @@ function parseCatalogEntry(item: unknown): ExampleProject { description?: unknown; path?: unknown; envKey?: unknown; + featured?: unknown; }; if ( typeof entry.title !== "string" || @@ -37,6 +40,9 @@ function parseCatalogEntry(item: unknown): ExampleProject { `${EXAMPLES_CATALOG_PATH} has an example missing title, description, or path.`, ); } + if (entry.featured !== undefined && typeof entry.featured !== "boolean") { + throw catalogError(`${EXAMPLES_CATALOG_PATH} has an example with an invalid featured flag.`); + } const relative = entry.path.replace(/^\/+/, ""); const name = relative.split("/").filter(Boolean).at(-1); if (!name) { @@ -53,6 +59,7 @@ function parseCatalogEntry(item: unknown): ExampleProject { description: entry.description, path: relative.startsWith("examples/") ? relative : `examples/${relative}`, envFile: ".env", + featured: entry.featured === true, envKey: typeof entry.envKey === "string" ? entry.envKey : undefined, }; } @@ -75,6 +82,11 @@ export async function loadExamplesCatalog( return parseExamplesCatalog(content); } +/** Keep catalog order so curators control which five examples are shown. */ +export function featuredExamples(examples: ExampleProject[]): ExampleProject[] { + return examples.filter((example) => example.featured === true).slice(0, 5); +} + export function findExample(name: string, examples: ExampleProject[]): ExampleProject { const normalized = name.toLowerCase(); const match = examples.find((entry) => entry.name.toLowerCase() === normalized); diff --git a/packages/openui-cli/src/commands/create/lib/help.ts b/packages/openui-cli/src/commands/create/lib/help.ts index 0bf562fa1..be7124032 100644 --- a/packages/openui-cli/src/commands/create/lib/help.ts +++ b/packages/openui-cli/src/commands/create/lib/help.ts @@ -82,7 +82,8 @@ async function loadCreateHelpText(): Promise { sections.push(`OpenUI examples: Loaded at runtime from examples/examples.json in the OpenUI repo. - Pick "Scaffold from OpenUI Examples" in the interactive prompt, or pass + Pick a featured example beside the backend choices, choose "More examples…" + for the complete list, or pass --example with any catalog folder name.`); return `\n${sections.join("\n\n")}\n`; diff --git a/packages/openui-cli/src/commands/create/lib/resolve.ts b/packages/openui-cli/src/commands/create/lib/resolve.ts index 029648b01..c5d8a615b 100644 --- a/packages/openui-cli/src/commands/create/lib/resolve.ts +++ b/packages/openui-cli/src/commands/create/lib/resolve.ts @@ -3,6 +3,7 @@ import { resolveArgs } from "../../../lib/resolve-args"; import type { RetryAttemptInfo } from "../../../lib/retry"; import type { OverlayName, TemplateName } from "./create-types"; import { + featuredExamples, findExample, groupedExampleChoices, loadExamplesCatalog, @@ -111,6 +112,7 @@ export async function resolveProjectIdentity( const OPENUI_EXAMPLES_CHOICE = "openui-examples"; const GO_BACK_CHOICE = "__back__"; +const EXAMPLE_CHOICE_PREFIX = "example:"; export async function resolveCreateSelection(params: { backendFramework?: OverlayName; @@ -126,6 +128,7 @@ export async function resolveCreateSelection(params: { if (backendFramework) return { kind: "overlay", overlay: backendFramework }; if (!interactive) return { kind: "overlay", overlay: "langgraph" }; + const featured = featuredExamples(examples); const { select, Separator } = await import("@inquirer/prompts"); const prompt = async ( message: string, @@ -154,12 +157,21 @@ export async function resolveCreateSelection(params: { name: overlay.name, description: overlay.description, })); - if (examples.length > 0) { + if (featured.length > 0) { starterChoices.push(new Separator()); + starterChoices.push( + ...featured.map((example) => ({ + value: `${EXAMPLE_CHOICE_PREFIX}${example.name}`, + name: example.label, + description: example.description, + })), + ); + } + if (examples.length > 0) { starterChoices.push({ value: OPENUI_EXAMPLES_CHOICE, - name: "Scaffold from OpenUI Examples", - description: "Browse examples from the OpenUI repo", + name: "More OpenUI Examples →", + description: "Browse the complete OpenUI example catalog", }); } @@ -168,6 +180,12 @@ export async function resolveCreateSelection(params: { starterChoices, starterChoices.length, ); + if (selected.startsWith(EXAMPLE_CHOICE_PREFIX)) { + return { + kind: "example", + example: findExample(selected.slice(EXAMPLE_CHOICE_PREFIX.length), examples), + }; + } if (selected !== OPENUI_EXAMPLES_CHOICE) { return { kind: "overlay", overlay: selected as OverlayName }; }