@kvasar/google-stitch 0.1.16 → 0.1.18

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.16",
4
+ "version": "0.1.18",
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.16",
3
+ "version": "0.1.18",
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",
@@ -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
  };
@@ -1,9 +1,36 @@
1
1
  import { StitchMCPClient } from "../services/stitch-mcp-client.js";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
2
5
 
3
6
  interface ListScreensParams {
4
7
  projectId: string;
5
8
  }
6
9
 
10
+ type StitchFile = {
11
+ name?: string;
12
+ downloadUrl?: string;
13
+ mimeType?: string;
14
+ fileContentBase64?: string;
15
+ };
16
+
17
+ type Screen = {
18
+ name?: string;
19
+ id?: string;
20
+ title?: string;
21
+ prompt?: string;
22
+ screenshot?: StitchFile;
23
+ htmlCode?: StitchFile;
24
+ figmaExport?: StitchFile;
25
+ deviceType?: string;
26
+ width?: string;
27
+ height?: string;
28
+ groupId?: string;
29
+ groupName?: string;
30
+ generatedBy?: string;
31
+ isCreatedByClient?: boolean;
32
+ };
33
+
7
34
  export const listScreensTool = (client: StitchMCPClient) => ({
8
35
  name: "list_screens",
9
36
  description: "Lists all screens within a given Stitch project",
@@ -13,8 +40,95 @@ export const listScreensTool = (client: StitchMCPClient) => ({
13
40
  throw new Error("projectId is required");
14
41
  }
15
42
 
16
- return client.request("list_screens", {
17
- projectId: params.projectId,
18
- });
19
- },
43
+ const screens = (await client.request("list_screens", {
44
+ projectId: params.projectId
45
+ })) as unknown as Screen[];
46
+
47
+ const content: Array<{ type: string; [key: string]: any }> = [];
48
+
49
+ if (!screens?.length) {
50
+ return {
51
+ content: [
52
+ {
53
+ type: "text",
54
+ text: "No screens found in this project."
55
+ }
56
+ ]
57
+ };
58
+ }
59
+
60
+ for (const screen of screens) {
61
+ content.push({
62
+ type: "text",
63
+ text: `## ${screen.title || "Untitled screen"}
64
+ Prompt: ${screen.prompt || "-"}
65
+ Device: ${screen.deviceType || "-"}
66
+ Size: ${screen.width || "?"} × ${screen.height || "?"}`
67
+ });
68
+
69
+ // Prefer embedded image
70
+ if (screen.screenshot?.fileContentBase64) {
71
+ const tempFilePath = path.join(
72
+ os.tmpdir(),
73
+ `stitch-screen-${Date.now()}-${Math.random()
74
+ .toString(36)
75
+ .slice(2)}.png`
76
+ );
77
+
78
+ fs.writeFileSync(
79
+ tempFilePath,
80
+ Buffer.from(
81
+ screen.screenshot.fileContentBase64,
82
+ "base64"
83
+ )
84
+ );
85
+
86
+ content.push({
87
+ type: "image",
88
+ path: tempFilePath,
89
+ caption: screen.title || "Generated screen"
90
+ });
91
+
92
+ continue;
93
+ }
94
+
95
+ // Fallback URL
96
+ if (screen.screenshot?.downloadUrl) {
97
+ content.push({
98
+ type: "image",
99
+ url: screen.screenshot.downloadUrl,
100
+ caption: screen.title || "Generated screen"
101
+ });
102
+
103
+ continue;
104
+ }
105
+
106
+ // HTML fallback
107
+ if (screen.htmlCode?.fileContentBase64) {
108
+ const html = Buffer.from(
109
+ screen.htmlCode.fileContentBase64,
110
+ "base64"
111
+ ).toString("utf-8");
112
+
113
+ content.push({
114
+ type: "html",
115
+ html
116
+ });
117
+
118
+ continue;
119
+ }
120
+
121
+ if (screen.htmlCode?.downloadUrl) {
122
+ content.push({
123
+ type: "html",
124
+ html: `<iframe
125
+ src="${screen.htmlCode.downloadUrl}"
126
+ style="width:100%;height:600px;border:none;"
127
+ ></iframe>`
128
+ });
129
+ }
130
+ }
131
+
132
+ return { content };
133
+ }
20
134
  });
@@ -1,7 +0,0 @@
1
- export const getScreenTool = (client:any) => ({
2
- name: "get_screen",
3
- description: "get screen",
4
- async execute(_: string, params: any) {
5
- return await client.request?.("get_screen", params);
6
- },
7
- });