shipshots-mcp 0.2.2 → 0.2.5
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.
- package/dist/api-client.d.ts +8 -1
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +9 -3
- package/dist/api-client.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +18 -6
- package/dist/cli.js.map +1 -1
- package/dist/defaults.d.ts +8 -0
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +20 -13
- package/dist/defaults.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/semantic-position.d.ts.map +1 -1
- package/dist/semantic-position.js +1 -3
- package/dist/semantic-position.js.map +1 -1
- package/dist/tools/batch.d.ts.map +1 -1
- package/dist/tools/batch.js +15 -0
- package/dist/tools/batch.js.map +1 -1
- package/dist/tools/export.d.ts.map +1 -1
- package/dist/tools/export.js +78 -6
- package/dist/tools/export.js.map +1 -1
- package/dist/tools/layers.d.ts.map +1 -1
- package/dist/tools/layers.js +249 -8
- package/dist/tools/layers.js.map +1 -1
- package/dist/tools/layout.d.ts +4 -0
- package/dist/tools/layout.d.ts.map +1 -0
- package/dist/tools/layout.js +312 -0
- package/dist/tools/layout.js.map +1 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +122 -44
- package/dist/validation.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
package/dist/tools/export.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export function registerExportTools(server, api) {
|
|
3
|
-
server.tool("get_screen_preview", `Get a visual preview of a screen as an image.
|
|
3
|
+
server.tool("get_screen_preview", `Get a visual preview of a screen as an image. Requires a Pro plan.
|
|
4
4
|
|
|
5
5
|
WORKFLOW: position layers → get_screen_preview → inspect the image → adjust → preview again.
|
|
6
6
|
This is the recommended feedback loop for iterative layout design.
|
|
7
7
|
Rate limit: 60 previews per hour. Use validate_screen (free, no rate limit) for quick layout checks without rendering.`, {
|
|
8
8
|
project_id: z.string().describe("Project ID"),
|
|
9
9
|
screen_id: z.string().describe("Screen ID to preview"),
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
scale: z.number().min(0.25).max(1.0).optional().describe("Preview scale: 0.5 (default, 630×1368), 0.75 (945×2052), 1.0 (full 1260×2736). Higher = better detail but slower."),
|
|
11
|
+
}, async ({ project_id, screen_id, scale }) => {
|
|
12
12
|
const [result, project] = await Promise.all([
|
|
13
|
-
api.getScreenPreview(project_id, screen_id),
|
|
13
|
+
api.getScreenPreview(project_id, screen_id, scale),
|
|
14
14
|
api.getProject(project_id),
|
|
15
15
|
]);
|
|
16
16
|
// Build layer position summary from project data
|
|
@@ -30,9 +30,55 @@ Rate limit: 60 previews per hour. Use validate_screen (free, no rate limit) for
|
|
|
30
30
|
text: JSON.stringify({
|
|
31
31
|
width: result.width,
|
|
32
32
|
height: result.height,
|
|
33
|
-
scale: 0.5,
|
|
33
|
+
scale: scale ?? 0.5,
|
|
34
34
|
layers,
|
|
35
|
-
message: `Preview of screen "${screen_id}" (${result.width}×${result.height})`,
|
|
35
|
+
message: `Preview of screen "${screen_id}" (${result.width}×${result.height} at ${scale ?? 0.5}x)`,
|
|
36
|
+
}, null, 2),
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
server.tool("get_strip_preview", `Get a panoramic preview of connected screens rendered as a single wide image. Use this after connect_screens to see the cross-screen layout. Requires a Pro plan.
|
|
42
|
+
Rate limit: 60 previews per hour (shared with get_screen_preview).`, {
|
|
43
|
+
project_id: z.string().describe("Project ID"),
|
|
44
|
+
screen_ids: z.array(z.string()).min(2).describe("Ordered screen IDs to render as a strip (must be connected)"),
|
|
45
|
+
scale: z.number().min(0.25).max(1.0).optional().describe("Preview scale (default 0.5). Lower = faster, higher = more detail."),
|
|
46
|
+
}, async ({ project_id, screen_ids, scale }) => {
|
|
47
|
+
const [result, project] = await Promise.all([
|
|
48
|
+
api.getStripPreview(project_id, screen_ids, scale),
|
|
49
|
+
api.getProject(project_id),
|
|
50
|
+
]);
|
|
51
|
+
const offsets = [];
|
|
52
|
+
let cumX = 0;
|
|
53
|
+
for (const sid of screen_ids) {
|
|
54
|
+
offsets.push(cumX);
|
|
55
|
+
const s = project.screens.find((s) => s.id === sid);
|
|
56
|
+
cumX += s?.width ?? 0;
|
|
57
|
+
}
|
|
58
|
+
const layers = screen_ids.flatMap((sid, i) => {
|
|
59
|
+
const screen = project.screens.find((s) => s.id === sid);
|
|
60
|
+
if (!screen)
|
|
61
|
+
return [];
|
|
62
|
+
return screen.layers
|
|
63
|
+
.filter((l) => l.visible !== false)
|
|
64
|
+
.map((l) => ({ id: l.id, type: l.type, screen_id: sid, x: l.x + offsets[i], y: l.y, width: l.width, height: l.height }));
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
content: [
|
|
68
|
+
{
|
|
69
|
+
type: "image",
|
|
70
|
+
data: result.base64,
|
|
71
|
+
mimeType: result.mimeType,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: JSON.stringify({
|
|
76
|
+
width: result.width,
|
|
77
|
+
height: result.height,
|
|
78
|
+
scale: scale ?? 0.5,
|
|
79
|
+
screen_ids,
|
|
80
|
+
layers,
|
|
81
|
+
message: `Strip preview (${result.width}×${result.height} at ${scale ?? 0.5}x)`,
|
|
36
82
|
}, null, 2),
|
|
37
83
|
},
|
|
38
84
|
],
|
|
@@ -101,5 +147,31 @@ Rate limit: 60 previews per hour. Use validate_screen (free, no rate limit) for
|
|
|
101
147
|
],
|
|
102
148
|
};
|
|
103
149
|
});
|
|
150
|
+
server.tool("export_screenshot", `Export a screen as a full-resolution image. Returns a base64 JPEG at native canvas size (1260×2736 for standard screens). Requires a Pro plan.
|
|
151
|
+
Rate limit: shares the 60/hour limit with get_screen_preview.
|
|
152
|
+
|
|
153
|
+
For different export sizes (iPhone 6.5", iPad, etc.), use the web editor's export feature.`, {
|
|
154
|
+
project_id: z.string().describe("Project ID"),
|
|
155
|
+
screen_id: z.string().describe("Screen ID to export"),
|
|
156
|
+
}, async ({ project_id, screen_id }) => {
|
|
157
|
+
const result = await api.getScreenPreview(project_id, screen_id, 1.0);
|
|
158
|
+
return {
|
|
159
|
+
content: [
|
|
160
|
+
{
|
|
161
|
+
type: "image",
|
|
162
|
+
data: result.base64,
|
|
163
|
+
mimeType: result.mimeType,
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
type: "text",
|
|
167
|
+
text: JSON.stringify({
|
|
168
|
+
width: result.width,
|
|
169
|
+
height: result.height,
|
|
170
|
+
message: `Exported screen "${screen_id}" at full resolution (${result.width}×${result.height})`,
|
|
171
|
+
}, null, 2),
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
};
|
|
175
|
+
});
|
|
104
176
|
}
|
|
105
177
|
//# sourceMappingURL=export.js.map
|
package/dist/tools/export.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/tools/export.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,GAAc;IACnE,MAAM,CAAC,IAAI,CACT,oBAAoB,EACpB;;;;uHAImH,EACnH;QACE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC7C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/tools/export.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../src/tools/layers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../src/tools/layers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AA6BlD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,GAAG,IAAI,CAk7B1E"}
|
package/dist/tools/layers.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { createTextLayer, createDeviceLayer, createImageLayer, createShapeLayer, createBadgeLayer, createArrowLayer, autoPosition, } from "../defaults.js";
|
|
2
|
+
import { createTextLayer, createDeviceLayer, createImageLayer, createShapeLayer, createBadgeLayer, createArrowLayer, autoPosition, H_MARGIN, V_MARGIN, } from "../defaults.js";
|
|
3
3
|
import { getProjectAndScreen, unescapeNewlines } from "../helpers.js";
|
|
4
4
|
import { positionEnum, sizeEnum, tiltEnum, resolveSemanticPosition } from "../semantic-position.js";
|
|
5
5
|
/** Extract position/dimension info from a layer for inclusion in tool responses. */
|
|
@@ -56,7 +56,11 @@ Use fontWeight contrast (800 vs 400) within the same font family for hierarchy.
|
|
|
56
56
|
rotation: z.number().optional().describe("2D rotation in degrees (default: 0)"),
|
|
57
57
|
rotateX: z.number().optional().describe("3D tilt on X axis, -90 to 90 (default: 0). Creates perspective depth effect."),
|
|
58
58
|
rotateY: z.number().optional().describe("3D tilt on Y axis, -90 to 90 (default: 0). Creates perspective depth effect."),
|
|
59
|
-
|
|
59
|
+
blurEnabled: z.boolean().optional().describe("Enable Gaussian blur (default: false)"),
|
|
60
|
+
blurAmount: z.number().min(0).max(50).optional().describe("Blur radius in pixels, 0-50"),
|
|
61
|
+
backdropBlurEnabled: z.boolean().optional().describe("Enable frosted glass backdrop blur (default: false). Layer needs low opacity to see the effect."),
|
|
62
|
+
backdropBlurAmount: z.number().min(0).max(50).optional().describe("Backdrop blur radius in pixels, 0-50"),
|
|
63
|
+
}, async ({ project_id, screen_id, text, position, size, tilt, x, y, width, height, fontSize, fontFamily, fontWeight, color, textAlign, letterSpacing, lineHeight, fillType, gradientColor2, gradientDirection, strokeEnabled, strokeColor, strokeWidth, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, rotation, rotateX, rotateY, blurEnabled, blurAmount, backdropBlurEnabled, backdropBlurAmount }) => {
|
|
60
64
|
const result = await getProjectAndScreen(api, project_id, screen_id);
|
|
61
65
|
if ("error" in result) {
|
|
62
66
|
return { content: [{ type: "text", text: result.error }], isError: true };
|
|
@@ -93,6 +97,10 @@ Use fontWeight contrast (800 vs 400) within the same font family for hierarchy.
|
|
|
93
97
|
...(rotation !== undefined ? { rotation } : semantic.rotation !== undefined ? { rotation: semantic.rotation } : {}),
|
|
94
98
|
...(rotateX !== undefined ? { rotateX } : {}),
|
|
95
99
|
...(rotateY !== undefined ? { rotateY } : {}),
|
|
100
|
+
...(blurEnabled !== undefined ? { blurEnabled } : {}),
|
|
101
|
+
...(blurAmount !== undefined ? { blurAmount } : {}),
|
|
102
|
+
...(backdropBlurEnabled !== undefined ? { backdropBlurEnabled } : {}),
|
|
103
|
+
...(backdropBlurAmount !== undefined ? { backdropBlurAmount } : {}),
|
|
96
104
|
});
|
|
97
105
|
screen.layers.push(layer);
|
|
98
106
|
await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
|
|
@@ -136,7 +144,11 @@ QUICK PLACEMENT: Use position ("center", "top-left", "bottom-right", ...), size
|
|
|
136
144
|
shadowBlur: z.number().optional().describe("Shadow blur radius in pixels (default: 20)"),
|
|
137
145
|
shadowOffsetX: z.number().optional().describe("Shadow horizontal offset (default: 0)"),
|
|
138
146
|
shadowOffsetY: z.number().optional().describe("Shadow vertical offset (default: 10)"),
|
|
139
|
-
|
|
147
|
+
blurEnabled: z.boolean().optional().describe("Enable Gaussian blur (default: false)"),
|
|
148
|
+
blurAmount: z.number().min(0).max(50).optional().describe("Blur radius in pixels, 0-50"),
|
|
149
|
+
backdropBlurEnabled: z.boolean().optional().describe("Enable frosted glass backdrop blur (default: false). Layer needs low opacity to see the effect."),
|
|
150
|
+
backdropBlurAmount: z.number().min(0).max(50).optional().describe("Backdrop blur radius in pixels, 0-50"),
|
|
151
|
+
}, async ({ project_id, screen_id, deviceType, screenshotUrl, position, size, tilt, x, y, width, rotation, rotateX, rotateY, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, blurEnabled, blurAmount, backdropBlurEnabled, backdropBlurAmount }) => {
|
|
140
152
|
const result = await getProjectAndScreen(api, project_id, screen_id);
|
|
141
153
|
if ("error" in result) {
|
|
142
154
|
return { content: [{ type: "text", text: result.error }], isError: true };
|
|
@@ -160,6 +172,10 @@ QUICK PLACEMENT: Use position ("center", "top-left", "bottom-right", ...), size
|
|
|
160
172
|
...(shadowBlur !== undefined ? { shadowBlur } : {}),
|
|
161
173
|
...(shadowOffsetX !== undefined ? { shadowOffsetX } : {}),
|
|
162
174
|
...(shadowOffsetY !== undefined ? { shadowOffsetY } : {}),
|
|
175
|
+
...(blurEnabled !== undefined ? { blurEnabled } : {}),
|
|
176
|
+
...(blurAmount !== undefined ? { blurAmount } : {}),
|
|
177
|
+
...(backdropBlurEnabled !== undefined ? { backdropBlurEnabled } : {}),
|
|
178
|
+
...(backdropBlurAmount !== undefined ? { backdropBlurAmount } : {}),
|
|
163
179
|
});
|
|
164
180
|
screen.layers.push(layer);
|
|
165
181
|
await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
|
|
@@ -191,7 +207,11 @@ QUICK PLACEMENT: Use position ("center", "top-left", "bottom-right", ...), size
|
|
|
191
207
|
shadowBlur: z.number().optional().describe("Shadow blur radius in pixels (default: 20)"),
|
|
192
208
|
shadowOffsetX: z.number().optional().describe("Shadow horizontal offset (default: 0)"),
|
|
193
209
|
shadowOffsetY: z.number().optional().describe("Shadow vertical offset (default: 10)"),
|
|
194
|
-
|
|
210
|
+
blurEnabled: z.boolean().optional().describe("Enable Gaussian blur (default: false)"),
|
|
211
|
+
blurAmount: z.number().min(0).max(50).optional().describe("Blur radius in pixels, 0-50"),
|
|
212
|
+
backdropBlurEnabled: z.boolean().optional().describe("Enable frosted glass backdrop blur (default: false). Layer needs low opacity to see the effect."),
|
|
213
|
+
backdropBlurAmount: z.number().min(0).max(50).optional().describe("Backdrop blur radius in pixels, 0-50"),
|
|
214
|
+
}, async ({ project_id, screen_id, imageUrl, position, size, tilt, x, y, width, height, objectFit, borderRadius, rotation, rotateX, rotateY, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, blurEnabled, blurAmount, backdropBlurEnabled, backdropBlurAmount }) => {
|
|
195
215
|
const result = await getProjectAndScreen(api, project_id, screen_id);
|
|
196
216
|
if ("error" in result) {
|
|
197
217
|
return { content: [{ type: "text", text: result.error }], isError: true };
|
|
@@ -217,6 +237,10 @@ QUICK PLACEMENT: Use position ("center", "top-left", "bottom-right", ...), size
|
|
|
217
237
|
...(shadowBlur !== undefined ? { shadowBlur } : {}),
|
|
218
238
|
...(shadowOffsetX !== undefined ? { shadowOffsetX } : {}),
|
|
219
239
|
...(shadowOffsetY !== undefined ? { shadowOffsetY } : {}),
|
|
240
|
+
...(blurEnabled !== undefined ? { blurEnabled } : {}),
|
|
241
|
+
...(blurAmount !== undefined ? { blurAmount } : {}),
|
|
242
|
+
...(backdropBlurEnabled !== undefined ? { backdropBlurEnabled } : {}),
|
|
243
|
+
...(backdropBlurAmount !== undefined ? { backdropBlurAmount } : {}),
|
|
220
244
|
});
|
|
221
245
|
screen.layers.push(layer);
|
|
222
246
|
await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
|
|
@@ -228,7 +252,9 @@ QUICK PLACEMENT: Use position ("center", "top-left", "bottom-right", ...), size
|
|
|
228
252
|
|
|
229
253
|
QUICK PLACEMENT: Use position ("center", "top-left", "bottom-right", ...), size ("small", "medium", "large", "full-width"), and tilt ("slight-left", "dramatic-right", ...) for semantic positioning. Explicit x/y/width/height/rotation override these.
|
|
230
254
|
|
|
231
|
-
TIP: Use large semi-transparent gradient circles (opacity 0.3-0.5) as decorative orbs behind device frames for visual depth
|
|
255
|
+
TIP: Use large semi-transparent gradient circles (opacity 0.3-0.5) as decorative orbs behind device frames for visual depth.
|
|
256
|
+
|
|
257
|
+
DEPTH EFFECTS: Use gradientType "radial" for spotlight/glow orbs. Add blurEnabled + blurAmount for depth-of-field. Use backdropBlurEnabled + backdropBlurAmount on semi-transparent shapes for frosted glass panels.`, {
|
|
232
258
|
project_id: z.string().describe("Project ID"),
|
|
233
259
|
screen_id: z.string().describe("Screen ID"),
|
|
234
260
|
shapeType: z.enum(["rect", "circle", "rounded-rect"]).describe("Shape type"),
|
|
@@ -239,6 +265,7 @@ TIP: Use large semi-transparent gradient circles (opacity 0.3-0.5) as decorative
|
|
|
239
265
|
fillType: z.enum(["solid", "gradient", "image"]).optional().describe('Fill type (default: "solid")'),
|
|
240
266
|
gradientColor2: z.string().optional().describe('Second gradient color hex (default: "#000000")'),
|
|
241
267
|
gradientDirection: z.number().optional().describe("Gradient angle in degrees 0-360 (default: 135)"),
|
|
268
|
+
gradientType: z.enum(["linear", "radial"]).optional().describe('Gradient interpolation type. "radial" creates spotlight/glow effects centered in the shape. Default: "linear"'),
|
|
242
269
|
fillImageUrl: z.string().optional().describe("Image URL or local file path for image fill"),
|
|
243
270
|
fillImageSize: z.enum(["cover", "contain", "fill"]).optional().describe('Image fill size mode (default: "cover")'),
|
|
244
271
|
x: z.number().optional().describe("X position"),
|
|
@@ -257,7 +284,11 @@ TIP: Use large semi-transparent gradient circles (opacity 0.3-0.5) as decorative
|
|
|
257
284
|
shadowBlur: z.number().optional().describe("Shadow blur radius in pixels (default: 20)"),
|
|
258
285
|
shadowOffsetX: z.number().optional().describe("Shadow horizontal offset (default: 0)"),
|
|
259
286
|
shadowOffsetY: z.number().optional().describe("Shadow vertical offset (default: 10)"),
|
|
260
|
-
|
|
287
|
+
blurEnabled: z.boolean().optional().describe("Enable Gaussian blur (default: false)"),
|
|
288
|
+
blurAmount: z.number().min(0).max(50).optional().describe("Blur radius in pixels, 0-50"),
|
|
289
|
+
backdropBlurEnabled: z.boolean().optional().describe("Enable frosted glass backdrop blur (default: false). Layer needs low opacity to see the effect."),
|
|
290
|
+
backdropBlurAmount: z.number().min(0).max(50).optional().describe("Backdrop blur radius in pixels, 0-50. Blurs content behind this layer — great for frosted glass panels."),
|
|
291
|
+
}, async ({ project_id, screen_id, shapeType, position, size, tilt, fill, fillType, gradientColor2, gradientDirection, gradientType, fillImageUrl, fillImageSize, x, y, width, height, borderRadius, stroke, strokeWidth, rotation, rotateX, rotateY, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, blurEnabled, blurAmount, backdropBlurEnabled, backdropBlurAmount }) => {
|
|
261
292
|
const result = await getProjectAndScreen(api, project_id, screen_id);
|
|
262
293
|
if ("error" in result) {
|
|
263
294
|
return { content: [{ type: "text", text: result.error }], isError: true };
|
|
@@ -272,6 +303,7 @@ TIP: Use large semi-transparent gradient circles (opacity 0.3-0.5) as decorative
|
|
|
272
303
|
...(fillType !== undefined ? { fillType } : {}),
|
|
273
304
|
...(gradientColor2 !== undefined ? { gradientColor2 } : {}),
|
|
274
305
|
...(gradientDirection !== undefined ? { gradientDirection } : {}),
|
|
306
|
+
...(gradientType !== undefined ? { gradientType } : {}),
|
|
275
307
|
...(resolvedFillImageUrl !== undefined ? { fillImageUrl: resolvedFillImageUrl } : {}),
|
|
276
308
|
...(fillImageSize !== undefined ? { fillImageSize } : {}),
|
|
277
309
|
...(x !== undefined ? { x } : semantic.x !== undefined ? { x: semantic.x } : {}),
|
|
@@ -290,6 +322,10 @@ TIP: Use large semi-transparent gradient circles (opacity 0.3-0.5) as decorative
|
|
|
290
322
|
...(shadowBlur !== undefined ? { shadowBlur } : {}),
|
|
291
323
|
...(shadowOffsetX !== undefined ? { shadowOffsetX } : {}),
|
|
292
324
|
...(shadowOffsetY !== undefined ? { shadowOffsetY } : {}),
|
|
325
|
+
...(blurEnabled !== undefined ? { blurEnabled } : {}),
|
|
326
|
+
...(blurAmount !== undefined ? { blurAmount } : {}),
|
|
327
|
+
...(backdropBlurEnabled !== undefined ? { backdropBlurEnabled } : {}),
|
|
328
|
+
...(backdropBlurAmount !== undefined ? { backdropBlurAmount } : {}),
|
|
293
329
|
});
|
|
294
330
|
screen.layers.push(layer);
|
|
295
331
|
await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
|
|
@@ -336,7 +372,11 @@ QUICK PLACEMENT: Use position ("center", "top-left", "bottom-right", ...), size
|
|
|
336
372
|
rotation: z.number().optional().describe("2D rotation in degrees (default: 0)"),
|
|
337
373
|
rotateX: z.number().optional().describe("3D tilt on X axis, -90 to 90 (default: 0). Creates perspective depth effect."),
|
|
338
374
|
rotateY: z.number().optional().describe("3D tilt on Y axis, -90 to 90 (default: 0). Creates perspective depth effect."),
|
|
339
|
-
|
|
375
|
+
blurEnabled: z.boolean().optional().describe("Enable Gaussian blur (default: false)"),
|
|
376
|
+
blurAmount: z.number().min(0).max(50).optional().describe("Blur radius in pixels, 0-50"),
|
|
377
|
+
backdropBlurEnabled: z.boolean().optional().describe("Enable frosted glass backdrop blur (default: false). Layer needs low opacity to see the effect."),
|
|
378
|
+
backdropBlurAmount: z.number().min(0).max(50).optional().describe("Backdrop blur radius in pixels, 0-50"),
|
|
379
|
+
}, async ({ project_id, screen_id, badgeStyle, text, position, size, tilt, x, y, width, height, fontSize, fontFamily, fontWeight, color, backgroundColor, backgroundOpacity, borderRadius, iconType, iconColor, rating, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, rotation, rotateX, rotateY, blurEnabled, blurAmount, backdropBlurEnabled, backdropBlurAmount }) => {
|
|
340
380
|
const result = await getProjectAndScreen(api, project_id, screen_id);
|
|
341
381
|
if ("error" in result) {
|
|
342
382
|
return { content: [{ type: "text", text: result.error }], isError: true };
|
|
@@ -371,6 +411,10 @@ QUICK PLACEMENT: Use position ("center", "top-left", "bottom-right", ...), size
|
|
|
371
411
|
...(rotation !== undefined ? { rotation } : semantic.rotation !== undefined ? { rotation: semantic.rotation } : {}),
|
|
372
412
|
...(rotateX !== undefined ? { rotateX } : {}),
|
|
373
413
|
...(rotateY !== undefined ? { rotateY } : {}),
|
|
414
|
+
...(blurEnabled !== undefined ? { blurEnabled } : {}),
|
|
415
|
+
...(blurAmount !== undefined ? { blurAmount } : {}),
|
|
416
|
+
...(backdropBlurEnabled !== undefined ? { backdropBlurEnabled } : {}),
|
|
417
|
+
...(backdropBlurAmount !== undefined ? { backdropBlurAmount } : {}),
|
|
374
418
|
});
|
|
375
419
|
screen.layers.push(layer);
|
|
376
420
|
await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
|
|
@@ -408,7 +452,11 @@ POSITIONING TIP:
|
|
|
408
452
|
rotation: z.number().optional().describe("2D rotation in degrees (default: 0)"),
|
|
409
453
|
rotateX: z.number().optional().describe("3D tilt on X axis, -90 to 90 (default: 0). Creates perspective depth effect."),
|
|
410
454
|
rotateY: z.number().optional().describe("3D tilt on Y axis, -90 to 90 (default: 0). Creates perspective depth effect."),
|
|
411
|
-
|
|
455
|
+
blurEnabled: z.boolean().optional().describe("Enable Gaussian blur (default: false)"),
|
|
456
|
+
blurAmount: z.number().min(0).max(50).optional().describe("Blur radius in pixels, 0-50"),
|
|
457
|
+
backdropBlurEnabled: z.boolean().optional().describe("Enable frosted glass backdrop blur (default: false). Layer needs low opacity to see the effect."),
|
|
458
|
+
backdropBlurAmount: z.number().min(0).max(50).optional().describe("Backdrop blur radius in pixels, 0-50"),
|
|
459
|
+
}, async ({ project_id, screen_id, fromX, fromY, toX, toY, strokeColor, strokeWidth, headStyle, curvature, dashed, opacity, shadowEnabled, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, rotation, rotateX, rotateY, blurEnabled, blurAmount, backdropBlurEnabled, backdropBlurAmount }) => {
|
|
412
460
|
const result = await getProjectAndScreen(api, project_id, screen_id);
|
|
413
461
|
if ("error" in result) {
|
|
414
462
|
return { content: [{ type: "text", text: result.error }], isError: true };
|
|
@@ -434,6 +482,10 @@ POSITIONING TIP:
|
|
|
434
482
|
...(rotation !== undefined ? { rotation } : {}),
|
|
435
483
|
...(rotateX !== undefined ? { rotateX } : {}),
|
|
436
484
|
...(rotateY !== undefined ? { rotateY } : {}),
|
|
485
|
+
...(blurEnabled !== undefined ? { blurEnabled } : {}),
|
|
486
|
+
...(blurAmount !== undefined ? { blurAmount } : {}),
|
|
487
|
+
...(backdropBlurEnabled !== undefined ? { backdropBlurEnabled } : {}),
|
|
488
|
+
...(backdropBlurAmount !== undefined ? { backdropBlurAmount } : {}),
|
|
437
489
|
});
|
|
438
490
|
screen.layers.push(layer);
|
|
439
491
|
await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
|
|
@@ -500,6 +552,11 @@ QUICK PLACEMENT: Use position/size/tilt for quick repositioning. Explicit x/y/wi
|
|
|
500
552
|
shadowBlur: z.number().optional().describe("Shadow blur radius in pixels"),
|
|
501
553
|
shadowOffsetX: z.number().optional().describe("Shadow horizontal offset"),
|
|
502
554
|
shadowOffsetY: z.number().optional().describe("Shadow vertical offset"),
|
|
555
|
+
gradientType: z.enum(["linear", "radial"]).optional().describe('Gradient type for shape layers: "linear" or "radial"'),
|
|
556
|
+
blurEnabled: z.boolean().optional().describe("Enable Gaussian blur"),
|
|
557
|
+
blurAmount: z.number().min(0).max(50).optional().describe("Blur radius in pixels, 0-50"),
|
|
558
|
+
backdropBlurEnabled: z.boolean().optional().describe("Enable frosted glass backdrop blur"),
|
|
559
|
+
backdropBlurAmount: z.number().min(0).max(50).optional().describe("Backdrop blur radius in pixels, 0-50"),
|
|
503
560
|
animation_entrance_type: z.string().optional().describe('Override entrance animation type. Valid: "fade-rise", "slide-right", "zoom-pop", "fade", "bounce", "parallax", "drop-bounce", "flip", "typewriter", "blur-in", "wipe", "elastic". Set to "" to clear.'),
|
|
504
561
|
animation_entrance_duration: z.number().optional().describe("Override entrance duration in frames"),
|
|
505
562
|
animation_entrance_delay: z.number().optional().describe("Override entrance delay in frames"),
|
|
@@ -604,5 +661,189 @@ QUICK PLACEMENT: Use position/size/tilt for quick repositioning. Explicit x/y/wi
|
|
|
604
661
|
content: [{ type: "text", text: JSON.stringify({ message: "Layers reordered.", order: layer_ids }, null, 2) }],
|
|
605
662
|
};
|
|
606
663
|
});
|
|
664
|
+
// ── move_layer ──
|
|
665
|
+
server.tool("move_layer", `Move a layer's z-order position. Use "to-front"/"to-back" for extremes, or "in-front-of"/"behind" with a reference_layer_id for relative positioning. Simpler than reorder_layers when you just need to adjust one layer.`, {
|
|
666
|
+
project_id: z.string().describe("Project ID"),
|
|
667
|
+
screen_id: z.string().describe("Screen ID"),
|
|
668
|
+
layer_id: z.string().describe("Layer ID to move"),
|
|
669
|
+
placement: z.enum(["in-front-of", "behind", "to-front", "to-back"]).describe("Where to place the layer"),
|
|
670
|
+
reference_layer_id: z.string().optional().describe("Reference layer ID (required for 'in-front-of' and 'behind')"),
|
|
671
|
+
}, async ({ project_id, screen_id, layer_id, placement, reference_layer_id }) => {
|
|
672
|
+
const result = await getProjectAndScreen(api, project_id, screen_id);
|
|
673
|
+
if ("error" in result) {
|
|
674
|
+
return { content: [{ type: "text", text: result.error }], isError: true };
|
|
675
|
+
}
|
|
676
|
+
const { project, screenIndex } = result;
|
|
677
|
+
const screen = project.screens[screenIndex];
|
|
678
|
+
const sorted = [...screen.layers].sort((a, b) => a.zIndex - b.zIndex);
|
|
679
|
+
const targetIndex = sorted.findIndex((l) => l.id === layer_id);
|
|
680
|
+
if (targetIndex === -1) {
|
|
681
|
+
const ids = screen.layers.map((l) => l.id).join(", ");
|
|
682
|
+
return { content: [{ type: "text", text: `Error: Layer "${layer_id}" not found. Available: ${ids}` }], isError: true };
|
|
683
|
+
}
|
|
684
|
+
const [target] = sorted.splice(targetIndex, 1);
|
|
685
|
+
if (placement === "to-front") {
|
|
686
|
+
sorted.push(target);
|
|
687
|
+
}
|
|
688
|
+
else if (placement === "to-back") {
|
|
689
|
+
sorted.unshift(target);
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
if (!reference_layer_id) {
|
|
693
|
+
return { content: [{ type: "text", text: `Error: reference_layer_id is required for "${placement}".` }], isError: true };
|
|
694
|
+
}
|
|
695
|
+
const refIndex = sorted.findIndex((l) => l.id === reference_layer_id);
|
|
696
|
+
if (refIndex === -1) {
|
|
697
|
+
const ids = screen.layers.map((l) => l.id).join(", ");
|
|
698
|
+
return { content: [{ type: "text", text: `Error: Reference layer "${reference_layer_id}" not found. Available: ${ids}` }], isError: true };
|
|
699
|
+
}
|
|
700
|
+
const insertAt = placement === "in-front-of" ? refIndex + 1 : refIndex;
|
|
701
|
+
sorted.splice(insertAt, 0, target);
|
|
702
|
+
}
|
|
703
|
+
// Re-number all zIndex values
|
|
704
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
705
|
+
const layer = screen.layers.find((l) => l.id === sorted[i].id);
|
|
706
|
+
if (layer)
|
|
707
|
+
layer.zIndex = i + 1;
|
|
708
|
+
}
|
|
709
|
+
await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
|
|
710
|
+
const order = sorted.map((l) => l.id);
|
|
711
|
+
return {
|
|
712
|
+
content: [{ type: "text", text: JSON.stringify({ message: "Layer moved.", layer_id, placement, order }, null, 2) }],
|
|
713
|
+
};
|
|
714
|
+
});
|
|
715
|
+
// ── align_layer ──
|
|
716
|
+
server.tool("align_layer", `Align a layer relative to the canvas boundaries. Uses standard margins (${H_MARGIN}px horizontal, ${V_MARGIN}px vertical) for edge alignments.`, {
|
|
717
|
+
project_id: z.string().describe("Project ID"),
|
|
718
|
+
screen_id: z.string().describe("Screen ID"),
|
|
719
|
+
layer_id: z.string().describe("Layer ID to align"),
|
|
720
|
+
alignment: z.enum([
|
|
721
|
+
"center-horizontal", "center-vertical", "center",
|
|
722
|
+
"left", "right", "top", "bottom",
|
|
723
|
+
]).describe("Alignment relative to the canvas"),
|
|
724
|
+
}, async ({ project_id, screen_id, layer_id, alignment }) => {
|
|
725
|
+
const result = await getProjectAndScreen(api, project_id, screen_id);
|
|
726
|
+
if ("error" in result) {
|
|
727
|
+
return { content: [{ type: "text", text: result.error }], isError: true };
|
|
728
|
+
}
|
|
729
|
+
const { project, screenIndex } = result;
|
|
730
|
+
const screen = project.screens[screenIndex];
|
|
731
|
+
const layer = screen.layers.find((l) => l.id === layer_id);
|
|
732
|
+
if (!layer) {
|
|
733
|
+
const ids = screen.layers.map((l) => l.id).join(", ");
|
|
734
|
+
return { content: [{ type: "text", text: `Error: Layer "${layer_id}" not found. Available: ${ids}` }], isError: true };
|
|
735
|
+
}
|
|
736
|
+
if (alignment === "center-horizontal" || alignment === "center") {
|
|
737
|
+
layer.x = Math.round((screen.width - layer.width) / 2);
|
|
738
|
+
}
|
|
739
|
+
if (alignment === "center-vertical" || alignment === "center") {
|
|
740
|
+
layer.y = Math.round((screen.height - layer.height) / 2);
|
|
741
|
+
}
|
|
742
|
+
if (alignment === "left")
|
|
743
|
+
layer.x = H_MARGIN;
|
|
744
|
+
if (alignment === "right")
|
|
745
|
+
layer.x = screen.width - layer.width - H_MARGIN;
|
|
746
|
+
if (alignment === "top")
|
|
747
|
+
layer.y = V_MARGIN;
|
|
748
|
+
if (alignment === "bottom")
|
|
749
|
+
layer.y = screen.height - layer.height - V_MARGIN;
|
|
750
|
+
await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
|
|
751
|
+
return {
|
|
752
|
+
content: [{ type: "text", text: JSON.stringify({ message: `Layer aligned (${alignment}).`, ...layerPosition(layer) }, null, 2) }],
|
|
753
|
+
};
|
|
754
|
+
});
|
|
755
|
+
// ── align_layers ──
|
|
756
|
+
server.tool("align_layers", `Align multiple layers relative to each other, or distribute them evenly.`, {
|
|
757
|
+
project_id: z.string().describe("Project ID"),
|
|
758
|
+
screen_id: z.string().describe("Screen ID"),
|
|
759
|
+
layer_ids: z.array(z.string()).min(2).describe("Layer IDs to align"),
|
|
760
|
+
alignment: z.enum([
|
|
761
|
+
"left", "right", "top", "bottom",
|
|
762
|
+
"center-horizontal", "center-vertical",
|
|
763
|
+
"distribute-horizontal", "distribute-vertical",
|
|
764
|
+
]).describe("How to align the layers relative to each other"),
|
|
765
|
+
}, async ({ project_id, screen_id, layer_ids, alignment }) => {
|
|
766
|
+
const result = await getProjectAndScreen(api, project_id, screen_id);
|
|
767
|
+
if ("error" in result) {
|
|
768
|
+
return { content: [{ type: "text", text: result.error }], isError: true };
|
|
769
|
+
}
|
|
770
|
+
const { project, screenIndex } = result;
|
|
771
|
+
const screen = project.screens[screenIndex];
|
|
772
|
+
const layers = layer_ids.map((id) => screen.layers.find((l) => l.id === id)).filter(Boolean);
|
|
773
|
+
if (layers.length < 2) {
|
|
774
|
+
return { content: [{ type: "text", text: `Error: Need at least 2 valid layer IDs. Found ${layers.length}.` }], isError: true };
|
|
775
|
+
}
|
|
776
|
+
switch (alignment) {
|
|
777
|
+
case "left": {
|
|
778
|
+
const minX = Math.min(...layers.map((l) => l.x));
|
|
779
|
+
for (const l of layers)
|
|
780
|
+
l.x = minX;
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
case "right": {
|
|
784
|
+
const maxRight = Math.max(...layers.map((l) => l.x + l.width));
|
|
785
|
+
for (const l of layers)
|
|
786
|
+
l.x = maxRight - l.width;
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
case "top": {
|
|
790
|
+
const minY = Math.min(...layers.map((l) => l.y));
|
|
791
|
+
for (const l of layers)
|
|
792
|
+
l.y = minY;
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
case "bottom": {
|
|
796
|
+
const maxBottom = Math.max(...layers.map((l) => l.y + l.height));
|
|
797
|
+
for (const l of layers)
|
|
798
|
+
l.y = maxBottom - l.height;
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
case "center-horizontal": {
|
|
802
|
+
const avgCx = layers.reduce((s, l) => s + l.x + l.width / 2, 0) / layers.length;
|
|
803
|
+
for (const l of layers)
|
|
804
|
+
l.x = Math.round(avgCx - l.width / 2);
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
case "center-vertical": {
|
|
808
|
+
const avgCy = layers.reduce((s, l) => s + l.y + l.height / 2, 0) / layers.length;
|
|
809
|
+
for (const l of layers)
|
|
810
|
+
l.y = Math.round(avgCy - l.height / 2);
|
|
811
|
+
break;
|
|
812
|
+
}
|
|
813
|
+
case "distribute-horizontal": {
|
|
814
|
+
const sorted = [...layers].sort((a, b) => a.x - b.x);
|
|
815
|
+
const first = sorted[0];
|
|
816
|
+
const last = sorted[sorted.length - 1];
|
|
817
|
+
const totalSpan = (last.x + last.width) - first.x;
|
|
818
|
+
const totalLayerWidth = sorted.reduce((s, l) => s + l.width, 0);
|
|
819
|
+
const gap = (totalSpan - totalLayerWidth) / (sorted.length - 1);
|
|
820
|
+
let cx = first.x + first.width;
|
|
821
|
+
for (let i = 1; i < sorted.length - 1; i++) {
|
|
822
|
+
sorted[i].x = Math.round(cx + gap);
|
|
823
|
+
cx = sorted[i].x + sorted[i].width;
|
|
824
|
+
}
|
|
825
|
+
break;
|
|
826
|
+
}
|
|
827
|
+
case "distribute-vertical": {
|
|
828
|
+
const sorted = [...layers].sort((a, b) => a.y - b.y);
|
|
829
|
+
const first = sorted[0];
|
|
830
|
+
const last = sorted[sorted.length - 1];
|
|
831
|
+
const totalSpan = (last.y + last.height) - first.y;
|
|
832
|
+
const totalLayerHeight = sorted.reduce((s, l) => s + l.height, 0);
|
|
833
|
+
const gap = (totalSpan - totalLayerHeight) / (sorted.length - 1);
|
|
834
|
+
let cy = first.y + first.height;
|
|
835
|
+
for (let i = 1; i < sorted.length - 1; i++) {
|
|
836
|
+
sorted[i].y = Math.round(cy + gap);
|
|
837
|
+
cy = sorted[i].y + sorted[i].height;
|
|
838
|
+
}
|
|
839
|
+
break;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
await api.updateProject(project_id, { ...project, screens: project.screens, updatedAt: new Date().toISOString() });
|
|
843
|
+
const positions = layers.map((l) => ({ id: l.id, ...layerPosition(l) }));
|
|
844
|
+
return {
|
|
845
|
+
content: [{ type: "text", text: JSON.stringify({ message: `Layers aligned (${alignment}).`, layers: positions }, null, 2) }],
|
|
846
|
+
};
|
|
847
|
+
});
|
|
607
848
|
}
|
|
608
849
|
//# sourceMappingURL=layers.js.map
|