@kvasar/google-stitch 0.1.8 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openclaw-google-stitch",
3
3
  "name": "Google Stitch MCP",
4
- "version": "0.1.8",
4
+ "version": "0.1.11",
5
5
  "description": "Integrates Google Stitch MCP services into OpenClaw",
6
6
  "skills": ["skills"],
7
7
  "configSchema": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kvasar/google-stitch",
3
- "version": "0.1.8",
3
+ "version": "0.1.11",
4
4
  "description": "OpenClaw plugin for Google Stitch UI generation, screen design, variants, and design systems",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
package/skills/SKILL.md CHANGED
@@ -56,7 +56,7 @@ If the user wants to start a new design workspace:
56
56
 
57
57
  - `create_project`
58
58
 
59
- Examples:
59
+ Example prompts:
60
60
 
61
61
  - create a new project
62
62
  - start a new app design
@@ -65,8 +65,22 @@ Examples:
65
65
  If the user wants existing project info:
66
66
 
67
67
  - `get_project`
68
+
69
+ Example prompts:
70
+
71
+ - Get the details of project 4044680601076201931
72
+ - Show me the project info
73
+ - What’s in my project?
74
+
75
+
68
76
  - `list_projects`
69
77
 
78
+ Example prompts:
79
+
80
+ - List all my Stitch projects
81
+ - Show me my projects
82
+ - List shared projects
83
+
70
84
  ---
71
85
 
72
86
  ### Screen generation
@@ -75,7 +89,7 @@ For new screens from prompts:
75
89
 
76
90
  - `generate_screen_from_text`
77
91
 
78
- Examples:
92
+ Example prompts:
79
93
 
80
94
  - create login page
81
95
  - generate dashboard
@@ -86,14 +100,40 @@ Always extract:
86
100
 
87
101
  - projectId
88
102
  - prompt
89
- - deviceType
90
- - preferred model
103
+ - deviceType (optional)
104
+ - modelId (optional)
91
105
 
92
- Preferred model:
106
+ ## Supported device types:
93
107
 
94
- - `GEMINI_3_1_PRO`
108
+ - MOBILE
109
+ - DESKTOP
110
+ - TABLET
111
+ - AGNOSTIC
95
112
 
96
- ---
113
+ ### Preferred default:
114
+
115
+ - DESKTOP
116
+
117
+ #### Supported models:
118
+
119
+ - GEMINI_3_PRO
120
+ - GEMINI_3_FLASH
121
+ - GEMINI_3_1_PRO
122
+
123
+ #### Preferred default:
124
+
125
+ - GEMINI_3_1_PRO
126
+
127
+ Always structure the tool call as:
128
+
129
+ {
130
+ "projectId": "project-id",
131
+ "prompt": "Generate a modern SaaS dashboard with KPI cards and sidebar",
132
+ "deviceType": "DESKTOP",
133
+ "modelId": "GEMINI_3_1_PRO"
134
+ }
135
+
136
+ Never call the tool with only a string prompt.
97
137
 
98
138
  ### Screen editing
99
139
 
@@ -101,7 +141,7 @@ For modifying existing screens:
101
141
 
102
142
  - `edit_screens`
103
143
 
104
- Examples:
144
+ Example prompts:
105
145
 
106
146
  - make button blue
107
147
  - add navbar
@@ -135,17 +175,44 @@ Recommended defaults:
135
175
 
136
176
  For brand consistency and theming:
137
177
 
178
+
179
+
138
180
  - `create_design_system`
181
+
182
+ Example prompts:
183
+
184
+ - Create a dark mode design system with Inter font and round corners
185
+ - Set up a design system with blue as the primary color
186
+ - Create a brand identity with Geist font and minimal roundness
187
+
139
188
  - `update_design_system`
189
+
190
+ Example prompts:
191
+
192
+ - Update the design system to use dark mode
193
+ - Change the font to Geist in our design system
194
+ - Update the roundness to fully rounded
195
+
196
+
140
197
  - `list_design_systems`
198
+
199
+ Example prompts:
200
+
201
+ - List all design systems in project 12345
202
+ - Show me the available design systems
203
+ - What design systems do I have?
204
+
205
+
141
206
  - `apply_design_system`
142
207
 
143
- Examples:
208
+ Applies a design system to one or more screens, modifying their appearance to match the system’s tokens (colors, fonts, shapes).
209
+
210
+ Example prompts:
211
+
212
+ - Apply the blue design system to all screens in project 12345
213
+ - Restyle these screens with the brand identity
214
+ - Use design system abc to update my screens
144
215
 
145
- - create dark theme
146
- - use Inter font
147
- - rounded buttons
148
- - apply brand colors
149
216
 
150
217
  ---
151
218
 
@@ -1,6 +1,19 @@
1
1
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
2
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3
3
 
4
+ export type DeviceType =
5
+ | "DEVICE_TYPE_UNSPECIFIED"
6
+ | "MOBILE"
7
+ | "DESKTOP"
8
+ | "TABLET"
9
+ | "AGNOSTIC";
10
+
11
+ export type ModelId =
12
+ | "MODEL_ID_UNSPECIFIED"
13
+ | "GEMINI_3_PRO"
14
+ | "GEMINI_3_FLASH"
15
+ | "GEMINI_3_1_PRO";
16
+
4
17
  export class StitchMCPClient {
5
18
  private client: Client;
6
19
  private transport: StreamableHTTPClientTransport;
@@ -41,12 +54,22 @@ export class StitchMCPClient {
41
54
  });
42
55
  }
43
56
 
44
- async generateScreen(prompt: string) {
45
- await this.connect();
57
+ async generateScreen(params: {
58
+ projectId: string;
59
+ prompt: string;
60
+ deviceType?: DeviceType;
61
+ modelId?: ModelId;
62
+ }) {
63
+ await this.connect();
46
64
 
47
- return this.client.callTool({
48
- name: "generate_screen_from_text",
49
- arguments: { prompt },
50
- });
51
- }
65
+ return this.client.callTool({
66
+ name: "generate_screen_from_text",
67
+ arguments: {
68
+ projectId: params.projectId,
69
+ prompt: params.prompt,
70
+ deviceType: params.deviceType,
71
+ modelId: params.modelId,
72
+ },
73
+ });
74
+ }
52
75
  }
@@ -1,7 +1,61 @@
1
- export const applyDesignSystemTool = (client:any) => ({
1
+ type SelectedScreenInstance = {
2
+ id: string;
3
+ sourceScreen: string;
4
+ };
5
+
6
+ type ApplyDesignSystemParams = {
7
+ projectId: string;
8
+ selectedScreenInstances: SelectedScreenInstance[];
9
+ assetId: string;
10
+ };
11
+
12
+ export const applyDesignSystemTool = (client: any) => ({
2
13
  name: "apply_design_system",
3
- description: "apply design system",
4
- async execute(_: string, params: any) {
5
- return await client.request?.("apply_design_system", params);
14
+ description:
15
+ "Applies a design system asset to one or more screens, updating colors, fonts, and shapes",
16
+
17
+ parameters: {
18
+ type: "object",
19
+ properties: {
20
+ projectId: {
21
+ type: "string",
22
+ description: "Required. Project ID without prefix."
23
+ },
24
+ selectedScreenInstances: {
25
+ type: "array",
26
+ description:
27
+ "Required. Screen instances to update from get_project.",
28
+ items: {
29
+ type: "object",
30
+ properties: {
31
+ id: {
32
+ type: "string",
33
+ description:
34
+ "Required. Screen instance ID (not source screen ID)."
35
+ },
36
+ sourceScreen: {
37
+ type: "string",
38
+ description:
39
+ "Required. Resource name format: projects/{project}/screens/{screen}"
40
+ }
41
+ },
42
+ required: ["id", "sourceScreen"]
43
+ }
44
+ },
45
+ assetId: {
46
+ type: "string",
47
+ description:
48
+ "Required. Design system asset ID without assets/ prefix."
49
+ }
50
+ },
51
+ required: [
52
+ "projectId",
53
+ "selectedScreenInstances",
54
+ "assetId"
55
+ ]
6
56
  },
7
- });
57
+
58
+ async execute(_: string, params: ApplyDesignSystemParams) {
59
+ return await client.request("apply_design_system", params);
60
+ }
61
+ });
@@ -1,7 +1,142 @@
1
- export const createDesignSystemTool = (client:any) => ({
1
+ type ColorMode =
2
+ | "COLOR_MODE_UNSPECIFIED"
3
+ | "LIGHT"
4
+ | "DARK";
5
+
6
+ type FontType =
7
+ | "FONT_UNSPECIFIED"
8
+ | "INTER"
9
+ | "DM_SANS"
10
+ | "GEIST";
11
+
12
+ type Roundness =
13
+ | "ROUNDNESS_UNSPECIFIED"
14
+ | "ROUND_FOUR"
15
+ | "ROUND_EIGHT"
16
+ | "ROUND_TWELVE"
17
+ | "ROUND_FULL";
18
+
19
+ type ColorVariant =
20
+ | "COLOR_VARIANT_UNSPECIFIED"
21
+ | "MONOCHROME"
22
+ | "NEUTRAL"
23
+ | "TONAL_SPOT"
24
+ | "VIBRANT"
25
+ | "EXPRESSIVE"
26
+ | "FIDELITY"
27
+ | "CONTENT"
28
+ | "RAINBOW"
29
+ | "FRUIT_SALAD";
30
+
31
+ type DesignTheme = {
32
+ colorMode: ColorMode;
33
+ headlineFont: FontType;
34
+ bodyFont: FontType;
35
+ labelFont?: FontType;
36
+ roundness: Roundness;
37
+ customColor: string;
38
+ colorVariant?: ColorVariant;
39
+ overridePrimaryColor?: string;
40
+ overrideSecondaryColor?: string;
41
+ overrideTertiaryColor?: string;
42
+ overrideNeutralColor?: string;
43
+ designMd?: string;
44
+ };
45
+
46
+ type DesignSystem = {
47
+ displayName: string;
48
+ theme: DesignTheme;
49
+ };
50
+
51
+ type CreateDesignSystemParams = {
52
+ designSystem: DesignSystem;
53
+ projectId?: string;
54
+ };
55
+
56
+ export const createDesignSystemTool = (client: any) => ({
2
57
  name: "create_design_system",
3
- description: "create design system",
4
- async execute(_: string, params: any) {
5
- return await client.request?.("create_design_system", params);
58
+ description:
59
+ "Creates a new design system with colors, typography, roundness, and theme tokens for a Stitch project",
60
+
61
+ parameters: {
62
+ type: "object",
63
+ properties: {
64
+ projectId: {
65
+ type: "string",
66
+ description:
67
+ "Optional. Project ID. If omitted, creates a global design asset."
68
+ },
69
+ designSystem: {
70
+ type: "object",
71
+ description: "Required. Design system definition.",
72
+ properties: {
73
+ displayName: {
74
+ type: "string",
75
+ description: "Required. Display name."
76
+ },
77
+ theme: {
78
+ type: "object",
79
+ properties: {
80
+ colorMode: {
81
+ type: "string",
82
+ enum: ["LIGHT", "DARK"]
83
+ },
84
+ headlineFont: {
85
+ type: "string",
86
+ enum: ["INTER", "DM_SANS", "GEIST"]
87
+ },
88
+ bodyFont: {
89
+ type: "string",
90
+ enum: ["INTER", "DM_SANS", "GEIST"]
91
+ },
92
+ labelFont: {
93
+ type: "string",
94
+ enum: ["INTER", "DM_SANS", "GEIST"]
95
+ },
96
+ roundness: {
97
+ type: "string",
98
+ enum: [
99
+ "ROUND_FOUR",
100
+ "ROUND_EIGHT",
101
+ "ROUND_TWELVE",
102
+ "ROUND_FULL"
103
+ ]
104
+ },
105
+ customColor: {
106
+ type: "string",
107
+ description: "Hex seed color"
108
+ },
109
+ colorVariant: {
110
+ type: "string",
111
+ enum: [
112
+ "MONOCHROME",
113
+ "NEUTRAL",
114
+ "TONAL_SPOT",
115
+ "VIBRANT",
116
+ "EXPRESSIVE"
117
+ ]
118
+ },
119
+ designMd: {
120
+ type: "string",
121
+ description: "Optional markdown design instructions"
122
+ }
123
+ },
124
+ required: [
125
+ "colorMode",
126
+ "headlineFont",
127
+ "bodyFont",
128
+ "roundness",
129
+ "customColor"
130
+ ]
131
+ }
132
+ },
133
+ required: ["displayName", "theme"]
134
+ }
135
+ },
136
+ required: ["designSystem"]
6
137
  },
7
- });
138
+
139
+ async execute(_: string, params: CreateDesignSystemParams) {
140
+ return await client.request("create_design_system", params);
141
+ }
142
+ });
@@ -1,28 +1,43 @@
1
- import { StitchMCPClient } from "../services/stitch-mcp-client.js";
1
+ import {
2
+ DeviceType,
3
+ ModelId,
4
+ StitchMCPClient
5
+ } from "../services/stitch-mcp-client.js";
2
6
  import fs from "node:fs";
3
7
  import path from "node:path";
4
8
  import os from "node:os";
5
9
 
6
- type StitchGenerateResponse = {
7
- html?: string;
8
- code?: string;
9
- jsx?: string;
10
- previewHtml?: string;
11
- result?: {
12
- html?: string;
13
- code?: string;
14
- jsx?: string;
15
- };
16
- imageUrl?: string;
17
- screenshotUrl?: string;
18
- previewUrl?: string;
19
- data?: {
20
- imageUrl?: string;
21
- screenshotUrl?: string;
22
- previewUrl?: string;
23
- base64Image?: string;
24
- rawBytes?: string;
10
+ type StitchResponse = {
11
+ screen?: {
12
+ name?: string;
13
+ title?: string;
14
+ prompt?: string;
15
+ screenshot?: {
16
+ url?: string;
17
+ bytes?: string;
18
+ };
19
+ htmlCode?: {
20
+ content?: string;
21
+ url?: string;
22
+ };
23
+ deviceType?: DeviceType;
24
+ width?: string;
25
+ height?: string;
25
26
  };
27
+ output_components?: Array<{
28
+ text?: string;
29
+ suggestion?: string;
30
+ design?: {
31
+ screenshot?: {
32
+ url?: string;
33
+ bytes?: string;
34
+ };
35
+ htmlCode?: {
36
+ content?: string;
37
+ url?: string;
38
+ };
39
+ };
40
+ }>;
26
41
  };
27
42
 
28
43
  export function generateScreenFromTextTool(client: StitchMCPClient) {
@@ -33,91 +48,100 @@ export function generateScreenFromTextTool(client: StitchMCPClient) {
33
48
  parameters: {
34
49
  type: "object",
35
50
  properties: {
51
+ projectId: {
52
+ type: "string",
53
+ description: "Required. Project ID without prefix."
54
+ },
36
55
  prompt: {
37
56
  type: "string",
38
57
  description: "Describe the UI screen to generate"
58
+ },
59
+ deviceType: {
60
+ type: "string",
61
+ enum: [
62
+ "DEVICE_TYPE_UNSPECIFIED",
63
+ "MOBILE",
64
+ "DESKTOP",
65
+ "TABLET",
66
+ "AGNOSTIC"
67
+ ]
68
+ },
69
+ modelId: {
70
+ type: "string",
71
+ enum: [
72
+ "MODEL_ID_UNSPECIFIED",
73
+ "GEMINI_3_PRO",
74
+ "GEMINI_3_FLASH",
75
+ "GEMINI_3_1_PRO"
76
+ ]
39
77
  }
40
78
  },
41
- required: ["prompt"]
79
+ required: ["projectId", "prompt"]
42
80
  },
43
81
 
44
- async execute(_id: string, params: { prompt: string }) {
45
- const result =
46
- (await client.generateScreen(
47
- params.prompt
48
- )) as StitchGenerateResponse;
82
+ async execute(
83
+ _id: string,
84
+ params: {
85
+ projectId: string;
86
+ prompt: string;
87
+ deviceType?: DeviceType;
88
+ modelId?: ModelId;
89
+ }
90
+ ) {
91
+ const result = (await client.generateScreen(params)) as StitchResponse;
92
+
93
+ const screen = result.screen;
94
+ const firstComponent = result.output_components?.[0];
49
95
 
50
- // Check for image URLs or base64 data
51
- const imageUrl = result.imageUrl || result.screenshotUrl || result.previewUrl ||
52
- result.data?.imageUrl || result.data?.screenshotUrl || result.data?.previewUrl;
53
- const base64Image = result.data?.base64Image;
54
- const rawBytes = result.data?.rawBytes;
96
+ const imageUrl =
97
+ screen?.screenshot?.url ||
98
+ firstComponent?.design?.screenshot?.url;
55
99
 
56
100
  if (imageUrl) {
57
- // If we have an image URL, return it as an image block
58
101
  return {
59
102
  content: [
60
103
  {
61
104
  type: "image",
62
105
  url: imageUrl,
63
- caption: "Generated by Google Stitch"
106
+ caption: screen?.title || "Generated by Google Stitch"
107
+ }
108
+ ]
109
+ };
110
+ }
111
+
112
+ const imageBytes =
113
+ screen?.screenshot?.bytes ||
114
+ firstComponent?.design?.screenshot?.bytes;
115
+
116
+ if (imageBytes) {
117
+ const tempFilePath = path.join(
118
+ os.tmpdir(),
119
+ `stitch-screen-${Date.now()}.png`
120
+ );
121
+
122
+ fs.writeFileSync(
123
+ tempFilePath,
124
+ Buffer.from(imageBytes, "base64")
125
+ );
126
+
127
+ return {
128
+ content: [
129
+ {
130
+ type: "image",
131
+ path: tempFilePath,
132
+ caption: screen?.title || "Generated by Google Stitch"
64
133
  }
65
134
  ]
66
135
  };
67
- } else if (base64Image || rawBytes) {
68
- // Decode and save base64 or raw bytes to a temporary file
69
- const tempDir = os.tmpdir();
70
- const tempFilePath = path.join(tempDir, `stitch-screen-${Date.now()}.png`);
71
-
72
- try {
73
- let imageData: Buffer;
74
- if (base64Image) {
75
- // Remove data URL prefix if present
76
- const cleanBase64 = base64Image.replace(/^data:image\/\w+;base64,/, '');
77
- imageData = Buffer.from(cleanBase64, 'base64');
78
- } else if (rawBytes) {
79
- // Assuming rawBytes is base64 encoded
80
- imageData = Buffer.from(rawBytes, 'base64');
81
- } else {
82
- throw new Error("No image data found");
83
- }
84
-
85
- fs.writeFileSync(tempFilePath, imageData);
86
-
87
- return {
88
- content: [
89
- {
90
- type: "image",
91
- path: tempFilePath,
92
- caption: "Generated by Google Stitch"
93
- }
94
- ]
95
- };
96
- } catch (error) {
97
- console.error("Failed to save image:", error);
98
- // Fall back to HTML if image processing fails
99
- }
100
136
  }
101
137
 
102
- // Fallback to HTML if no image data is available
103
138
  const html =
104
- result.html ??
105
- result.previewHtml ??
106
- result.result?.html ??
107
- (result.code
108
- ? `<pre style="white-space:pre-wrap;padding:16px">${escapeHtml(
109
- result.code
110
- )}</pre>`
111
- : result.jsx
112
- ? `<pre style="white-space:pre-wrap;padding:16px">${escapeHtml(
113
- result.jsx
114
- )}</pre>`
115
- : `<div style="padding:16px">
116
- <h3>Screen generated</h3>
117
- <pre>${escapeHtml(
118
- JSON.stringify(result, null, 2)
119
- )}</pre>
120
- </div>`);
139
+ screen?.htmlCode?.content ||
140
+ firstComponent?.design?.htmlCode?.content ||
141
+ `<div style="padding:16px">
142
+ <h3>${escapeHtml(screen?.title || "Generated screen")}</h3>
143
+ <p>${escapeHtml(firstComponent?.text || "")}</p>
144
+ </div>`;
121
145
 
122
146
  return {
123
147
  content: [