@kvasar/google-stitch 0.1.15 → 0.1.17

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.15",
4
+ "version": "0.1.17",
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.15",
3
+ "version": "0.1.17",
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
@@ -85,7 +85,7 @@ Example prompts:
85
85
 
86
86
  ### Screen generation
87
87
 
88
- For new screens from prompts:
88
+ For creating new screens from prompts:
89
89
 
90
90
  - `generate_screen_from_text`
91
91
 
@@ -96,12 +96,19 @@ Example prompts:
96
96
  - build landing page
97
97
  - design mobile onboarding
98
98
 
99
+ Use this tool when the user wants to create a new UI screen from a text description.
100
+
99
101
  Always extract:
100
102
 
101
103
  - projectId
102
104
  - prompt
103
- - deviceType (optional)
104
- - modelId (optional)
105
+
106
+ Important:
107
+ - Screen generation usually takes a few minutes to complete
108
+ - Do **not retry automatically** if a connection error or timeout occurs
109
+ - A connection error does **not necessarily mean the generation failed**
110
+ - After waiting a few minutes, use `get_screen` or `list_screens` to verify whether the screen was successfully created
111
+ - Avoid duplicate retries to prevent generating the same screen multiple times
105
112
 
106
113
  ## Supported device types:
107
114
 
@@ -7,37 +7,31 @@ import fs from "node:fs";
7
7
  import path from "node:path";
8
8
  import os from "node:os";
9
9
 
10
+ type StitchFile = {
11
+ name?: string;
12
+ downloadUrl?: string;
13
+ mimeType?: string;
14
+ fileContentBase64?: string;
15
+ };
16
+
17
+ type SessionOutputComponent = {
18
+ text?: string;
19
+ suggestion?: string;
20
+
21
+ };
22
+
10
23
  type StitchResponse = {
11
24
  screen?: {
12
25
  name?: string;
13
26
  title?: string;
14
27
  prompt?: string;
15
- screenshot?: {
16
- url?: string;
17
- bytes?: string;
18
- };
19
- htmlCode?: {
20
- content?: string;
21
- url?: string;
22
- };
28
+ screenshot?: StitchFile;
29
+ htmlCode?: StitchFile;
23
30
  deviceType?: DeviceType;
24
31
  width?: string;
25
32
  height?: string;
26
33
  };
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
- }>;
34
+ output_components?: SessionOutputComponent[];
41
35
  };
42
36
 
43
37
  export function generateScreenFromTextTool(client: StitchMCPClient) {
@@ -91,39 +85,28 @@ export function generateScreenFromTextTool(client: StitchMCPClient) {
91
85
  const result = (await client.generateScreen(params)) as StitchResponse;
92
86
 
93
87
  const screen = result.screen;
94
- const firstComponent = result.output_components?.[0];
95
88
 
96
- // Extract model's conversational text and suggestion
97
- const modelText = firstComponent?.text;
98
- const suggestion = firstComponent?.suggestion;
89
+ const modelText = result.output_components
90
+ ?.map(component => component.text)
91
+ .filter(Boolean)
92
+ .join("\n");
99
93
 
100
- // Try image URL response
101
- const imageUrl =
102
- screen?.screenshot?.url ||
103
- firstComponent?.design?.screenshot?.url;
94
+ const suggestionText = result.output_components
95
+ ?.map(component => component.suggestion)
96
+ .filter(Boolean)
97
+ .join("\n");
104
98
 
105
- if (imageUrl) {
106
- const content: Array<{ type: string; [key: string]: any }> = [];
107
- if (modelText) {
108
- content.push({ type: "text", text: modelText });
109
- }
99
+ const content: Array<{ type: string; [key: string]: any }> = [];
100
+
101
+ if (modelText) {
110
102
  content.push({
111
- type: "image",
112
- url: imageUrl,
113
- caption: screen?.title || "Generated by Google Stitch"
103
+ type: "text",
104
+ text: modelText
114
105
  });
115
- if (suggestion) {
116
- content.push({ type: "text", text: suggestion });
117
- }
118
- return { content };
119
106
  }
120
107
 
121
- // Try image bytes response
122
- const imageBytes =
123
- screen?.screenshot?.bytes ||
124
- firstComponent?.design?.screenshot?.bytes;
125
-
126
- if (imageBytes) {
108
+ // Prefer embedded image first
109
+ if (screen?.screenshot?.fileContentBase64) {
127
110
  const tempFilePath = path.join(
128
111
  os.tmpdir(),
129
112
  `stitch-screen-${Date.now()}.png`
@@ -131,43 +114,83 @@ export function generateScreenFromTextTool(client: StitchMCPClient) {
131
114
 
132
115
  fs.writeFileSync(
133
116
  tempFilePath,
134
- Buffer.from(imageBytes, "base64")
117
+ Buffer.from(
118
+ screen.screenshot.fileContentBase64,
119
+ "base64"
120
+ )
135
121
  );
136
122
 
137
- const content: Array<{ type: string; [key: string]: any }> = [];
138
- if (modelText) {
139
- content.push({ type: "text", text: modelText });
140
- }
141
123
  content.push({
142
124
  type: "image",
143
125
  path: tempFilePath,
144
- caption: screen?.title || "Generated by Google Stitch"
126
+ caption: screen.title || "Generated by Google Stitch"
145
127
  });
146
- if (suggestion) {
147
- content.push({ type: "text", text: suggestion });
128
+
129
+ if (suggestionText) {
130
+ content.push({
131
+ type: "text",
132
+ text: suggestionText
133
+ });
148
134
  }
135
+
149
136
  return { content };
150
137
  }
151
138
 
152
- // HTML fallback (either provided or minimal placeholder)
153
- let html =
154
- screen?.htmlCode?.content ||
155
- firstComponent?.design?.htmlCode?.content;
139
+ // Fallback to remote image URL
140
+ if (screen?.screenshot?.downloadUrl) {
141
+ content.push({
142
+ type: "image",
143
+ url: screen.screenshot.downloadUrl,
144
+ caption: screen.title || "Generated by Google Stitch"
145
+ });
146
+
147
+ if (suggestionText) {
148
+ content.push({
149
+ type: "text",
150
+ text: suggestionText
151
+ });
152
+ }
153
+
154
+ return { content };
155
+ }
156
+
157
+ // HTML fallback
158
+ let html: string | undefined;
159
+
160
+ if (screen?.htmlCode?.fileContentBase64) {
161
+ html = Buffer.from(
162
+ screen.htmlCode.fileContentBase64,
163
+ "base64"
164
+ ).toString("utf-8");
165
+ }
166
+
167
+ if (!html && screen?.htmlCode?.downloadUrl) {
168
+ html = `<iframe
169
+ src="${screen.htmlCode.downloadUrl}"
170
+ style="width:100%;height:600px;border:none;"
171
+ ></iframe>`;
172
+ }
156
173
 
157
174
  if (!html) {
158
175
  html = `<div style="padding:16px">
159
- <h3>${escapeHtml(screen?.title || "Generated screen")}</h3>
176
+ <h3>${escapeHtml(
177
+ screen?.title || "Generated screen"
178
+ )}</h3>
160
179
  </div>`;
161
180
  }
162
181
 
163
- const content: Array<{ type: string; [key: string]: any }> = [];
164
- if (modelText) {
165
- content.push({ type: "text", text: modelText });
166
- }
167
- content.push({ type: "html", html });
168
- if (suggestion) {
169
- content.push({ type: "text", text: suggestion });
182
+ content.push({
183
+ type: "html",
184
+ html
185
+ });
186
+
187
+ if (suggestionText) {
188
+ content.push({
189
+ type: "text",
190
+ text: suggestionText
191
+ });
170
192
  }
193
+
171
194
  return { content };
172
195
  }
173
196
  };