diff --git a/packages/sdk/README.md b/packages/sdk/README.md index 06bc3b3..0fd1ca9 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -11,7 +11,9 @@ import { stitch } from "@google/stitch-sdk"; // STITCH_API_KEY must be set in the environment const project = await stitch.createProject("My App"); -const screen = await project.generate("A login page with email and password fields"); +const screen = await project.generate( + "A login page with email and password fields", +); const html = await screen.getHtml(); const imageUrl = await screen.getImage(); ``` @@ -79,11 +81,11 @@ for (const variant of variants) { `variantOptions` fields: -| Field | Type | Default | Description | -|---|---|---|---| -| `variantCount` | `number` | 3 | Number of variants (1–5) | -| `creativeRange` | `string` | `"EXPLORE"` | `"REFINE"`, `"EXPLORE"`, or `"REIMAGINE"` | -| `aspects` | `string[]` | all | `"LAYOUT"`, `"COLOR_SCHEME"`, `"IMAGES"`, `"TEXT_FONT"`, `"TEXT_CONTENT"` | +| Field | Type | Default | Description | +| --------------- | ---------- | ----------- | ------------------------------------------------------------------------- | +| `variantCount` | `number` | 3 | Number of variants (1–5) | +| `creativeRange` | `string` | `"EXPLORE"` | `"REFINE"`, `"EXPLORE"`, or `"REIMAGINE"` | +| `aspects` | `string[]` | all | `"LAYOUT"`, `"COLOR_SCHEME"`, `"IMAGES"`, `"TEXT_FONT"`, `"TEXT_CONTENT"` | ## Tool Client (Agent Usage) @@ -116,26 +118,26 @@ The client auto-connects on the first `callTool` or `listTools` call. No explici The root class. Manages projects. -| Method | Parameters | Returns | Description | -|---|---|---|---| -| `createProject(title)` | `title: string` | `Promise` | Create a new project | -| `projects()` | — | `Promise` | List all accessible projects | -| `project(id)` | `id: string` | `Project` | Reference a project by ID (no API call) | +| Method | Parameters | Returns | Description | +| ---------------------- | --------------- | -------------------- | --------------------------------------- | +| `createProject(title)` | `title: string` | `Promise` | Create a new project | +| `projects()` | — | `Promise` | List all accessible projects | +| `project(id)` | `id: string` | `Project` | Reference a project by ID (no API call) | ### `Project` A Stitch project containing screens. -| Property | Type | Description | -|---|---|---| -| `id` | `string` | Alias for `projectId` | +| Property | Type | Description | +| ----------- | -------- | --------------------------------------- | +| `id` | `string` | Alias for `projectId` | | `projectId` | `string` | Bare project ID (no `projects/` prefix) | -| Method | Parameters | Returns | Description | -|---|---|---|---| -| `generate(prompt, deviceType?)` | `prompt: string`, `deviceType?: DeviceType` | `Promise` | Generate a screen from a text prompt | -| `screens()` | — | `Promise` | List all screens in the project | -| `getScreen(screenId)` | `screenId: string` | `Promise` | Retrieve a specific screen by ID | +| Method | Parameters | Returns | Description | +| ------------------------------- | ------------------------------------------- | ------------------- | ------------------------------------ | +| `generate(prompt, deviceType?)` | `prompt: string`, `deviceType?: DeviceType` | `Promise` | Generate a screen from a text prompt | +| `screens()` | — | `Promise` | List all screens in the project | +| `getScreen(screenId)` | `screenId: string` | `Promise` | Retrieve a specific screen by ID | `DeviceType`: `"MOBILE"` \| `"DESKTOP"` \| `"TABLET"` \| `"AGNOSTIC"` @@ -143,18 +145,18 @@ A Stitch project containing screens. A generated UI screen. Provides access to HTML and screenshots. -| Property | Type | Description | -|---|---|---| -| `id` | `string` | Alias for `screenId` | -| `screenId` | `string` | Bare screen ID | -| `projectId` | `string` | Parent project ID | +| Property | Type | Description | +| ----------- | -------- | -------------------- | +| `id` | `string` | Alias for `screenId` | +| `screenId` | `string` | Bare screen ID | +| `projectId` | `string` | Parent project ID | -| Method | Parameters | Returns | Description | -|---|---|---|---| -| `edit(prompt, deviceType?, modelId?)` | `prompt: string` | `Promise` | Edit the screen with a text prompt | -| `variants(prompt, variantOptions, deviceType?, modelId?)` | `prompt: string`, `variantOptions: object` | `Promise` | Generate design variants | -| `getHtml()` | — | `Promise` | Get the screen's HTML download URL | -| `getImage()` | — | `Promise` | Get the screen's screenshot download URL | +| Method | Parameters | Returns | Description | +| --------------------------------------------------------- | ------------------------------------------ | ------------------- | ---------------------------------------- | +| `edit(prompt, deviceType?, modelId?)` | `prompt: string` | `Promise` | Edit the screen with a text prompt | +| `variants(prompt, variantOptions, deviceType?, modelId?)` | `prompt: string`, `variantOptions: object` | `Promise` | Generate design variants | +| `getHtml()` | — | `Promise` | Get the screen's HTML download URL | +| `getImage()` | — | `Promise` | Get the screen's screenshot download URL | `getHtml()` and `getImage()` use cached data from the generation response when available. If the screen was loaded from `screens()` or `getScreen()`, they call the `get_screen` API automatically. @@ -170,12 +172,12 @@ const result = await client.callTool("tool_name", { arg: "value" }); await client.close(); ``` -| Method | Parameters | Returns | Description | -|---|---|---|---| -| `callTool(name, args)` | `name: string`, `args: Record` | `Promise` | Call an MCP tool | -| `listTools()` | — | `Promise<{ tools }>` | List available tools | -| `connect()` | — | `Promise` | Explicitly connect (auto-called by `callTool`) | -| `close()` | — | `Promise` | Close the connection | +| Method | Parameters | Returns | Description | +| ------------------------- | ------------------------------------------- | -------------------- | ---------------------------------------------- | +| `callTool(name, args)` | `name: string`, `args: Record` | `Promise` | Call an MCP tool | +| `listTools()` | — | `Promise<{ tools }>` | List available tools | +| `connect()` | — | `Promise` | Explicitly connect (auto-called by `callTool`) | +| `close()` | — | `Promise` | Close the connection | ### `StitchProxy` @@ -212,10 +214,10 @@ import { stitch } from "@google/stitch-sdk"; const tool = stitch.toolMap.get("generate_screen_from_text"); if (tool) { // Pre-parsed params — no JSON Schema parsing needed - const required = tool.params.filter(p => p.required); - const optional = tool.params.filter(p => !p.required); - console.log(required.map(p => p.name)); // ["projectId", "prompt"] - console.log(optional.map(p => p.name)); // ["deviceType", "modelId"] + const required = tool.params.filter((p) => p.required); + const optional = tool.params.filter((p) => !p.required); + console.log(required.map((p) => p.name)); // ["projectId", "prompt"] + console.log(optional.map((p) => p.name)); // ["deviceType", "modelId"] } // Iterate all tools @@ -234,12 +236,12 @@ The raw `inputSchema` (`ToolInputSchema`) is also available on each entry. Stand ### Environment Variables -| Variable | Required | Description | -|---|---|---| -| `STITCH_API_KEY` | Yes (or use OAuth) | API key for authentication | -| `STITCH_ACCESS_TOKEN` | No | OAuth access token (alternative to API key) | -| `GOOGLE_CLOUD_PROJECT` | With OAuth | Google Cloud project ID | -| `STITCH_HOST` | No | Override the MCP server URL | +| Variable | Required | Description | +| ---------------------- | ------------------ | ------------------------------------------- | +| `STITCH_API_KEY` | Yes (or use OAuth) | API key for authentication | +| `STITCH_ACCESS_TOKEN` | No | OAuth access token (alternative to API key) | +| `GOOGLE_CLOUD_PROJECT` | With OAuth | Google Cloud project ID | +| `STITCH_HOST` | No | Override the MCP server URL | ### Explicit Configuration @@ -256,13 +258,13 @@ const sdk = new Stitch(client); const projects = await sdk.projects(); ``` -| Option | Type | Default | Description | -|---|---|---|---| -| `apiKey` | `string` | `STITCH_API_KEY` | API key | -| `accessToken` | `string` | `STITCH_ACCESS_TOKEN` | OAuth token | -| `projectId` | `string` | `GOOGLE_CLOUD_PROJECT` | Cloud project ID | -| `baseUrl` | `string` | `https://stitch.googleapis.com/mcp` | MCP server URL | -| `timeout` | `number` | `300000` | Request timeout (ms) | +| Option | Type | Default | Description | +| ------------- | -------- | ----------------------------------- | -------------------- | +| `apiKey` | `string` | `STITCH_API_KEY` | API key | +| `accessToken` | `string` | `STITCH_ACCESS_TOKEN` | OAuth token | +| `projectId` | `string` | `GOOGLE_CLOUD_PROJECT` | Cloud project ID | +| `baseUrl` | `string` | `https://stitch.googleapis.com/mcp` | MCP server URL | +| `timeout` | `number` | `300000` | Request timeout (ms) | Authentication requires either `apiKey` or both `accessToken` and `projectId`. @@ -278,8 +280,8 @@ try { await project.screens(); } catch (error) { if (error instanceof StitchError) { - console.error(error.code); // "UNKNOWN_ERROR" - console.error(error.message); // Human-readable description + console.error(error.code); // "UNKNOWN_ERROR" + console.error(error.message); // Human-readable description console.error(error.recoverable); // false } } @@ -289,6 +291,10 @@ Error codes: `AUTH_FAILED`, `NOT_FOUND`, `PERMISSION_DENIED`, `RATE_LIMITED`, `N --- +## Examples + +- [Batch Design Generation](./examples/batch-generation/README.md) - Generate multiple designs in parallel from a JSON list of prompts. + ## Disclaimer This is not an officially supported Google product. This project is not @@ -297,4 +303,4 @@ Program](https://bughunters.google.com/open-source-security). ## License -Apache 2.0 — see [LICENSE](LICENSE) for details. \ No newline at end of file +Apache 2.0 — see [LICENSE](LICENSE) for details. diff --git a/packages/sdk/examples/batch-generation/README.md b/packages/sdk/examples/batch-generation/README.md new file mode 100644 index 0000000..893c8fd --- /dev/null +++ b/packages/sdk/examples/batch-generation/README.md @@ -0,0 +1,24 @@ +# Batch Design Generation + +This script demonstrates how to read a list of prompts from a JSON file and generate Stitch UI screens in parallel using `Promise.allSettled`. + +This is a Tier 1 (Script) example because it is deterministic and requires no agentic decision-making. + +## Prerequisites + +1. Set your `STITCH_API_KEY` environment variable. +2. Build the SDK first (from the root directory): + ```bash + npm run build + ``` + +## Running the Example + +Run this script directly from this directory: + +```bash +cd packages/sdk/examples/batch-generation +STITCH_API_KEY=your_key bun index.ts +``` + +The script will read prompts from `prompts.json`, create a single project, generate all designs in parallel, and output a summary report with the generated screen IDs and image URLs. diff --git a/packages/sdk/examples/batch-generation/index.ts b/packages/sdk/examples/batch-generation/index.ts new file mode 100644 index 0000000..f83713a --- /dev/null +++ b/packages/sdk/examples/batch-generation/index.ts @@ -0,0 +1,60 @@ +import { stitch } from "@google/stitch-sdk"; +import fs from "fs/promises"; +import path from "path"; +import "../_require-key.js"; + +console.log("Reading prompts from prompts.json..."); +const promptsPath = path.join(process.cwd(), "prompts.json"); +let promptsFile; +try { + promptsFile = await fs.readFile(promptsPath, "utf-8"); +} catch (error) { + console.error( + "Could not read prompts.json. Ensure you run this from the batch-generation directory.", + ); + process.exit(1); +} + +const prompts: string[] = JSON.parse(promptsFile); +console.log( + `Found ${prompts.length} prompts. Generating designs in parallel...`, +); + +const project = await stitch.createProject("Batch Generation Example"); +console.log(`Created project ${project.id}`); + +const results = await Promise.allSettled( + prompts.map(async (prompt, index) => { + console.log( + `[${index + 1}/${prompts.length}] Starting: "${prompt.slice(0, 30)}..."`, + ); + const screen = await project.generate(prompt); + const htmlUrl = await screen.getHtml(); + const imageUrl = await screen.getImage(); + return { prompt, screenId: screen.id, htmlUrl, imageUrl }; + }), +); + +console.log("\n=== Generation Results ==="); +const successful = results + .filter((r) => r.status === "fulfilled") + .map((r) => (r as PromiseFulfilledResult).value); +const failed = results + .filter((r) => r.status === "rejected") + .map((r) => (r as PromiseRejectedResult).reason); + +console.log(`✅ Successfully generated: ${successful.length}`); +console.log(`❌ Failed: ${failed.length}`); + +for (const success of successful) { + console.log(`\n- Prompt: "${success.prompt}"`); + console.log(` Screen ID: ${success.screenId}`); + console.log(` Image URL: ${success.imageUrl}`); +} + +if (failed.length > 0) { + console.log("\nErrors:"); + for (const error of failed) { + console.error(" ", error); + } +} diff --git a/packages/sdk/examples/batch-generation/prompts.json b/packages/sdk/examples/batch-generation/prompts.json new file mode 100644 index 0000000..be0a4a7 --- /dev/null +++ b/packages/sdk/examples/batch-generation/prompts.json @@ -0,0 +1,5 @@ +[ + "A modern landing page for a SaaS startup with a hero section and pricing table", + "A user profile settings dashboard with form fields for email, password, and notifications", + "A mobile app home screen for a fitness tracker showing daily steps and heart rate" +]