@pinepaper.studio/mcp-server 1.6.16 → 1.6.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.
- package/README.de.md +1 -1
- package/README.es.md +1 -1
- package/README.fr.md +1 -1
- package/README.hi.md +1 -1
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +142 -4
- package/README.pt-BR.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/browser/puppeteer-controller.d.ts +61 -1
- package/dist/browser/puppeteer-controller.d.ts.map +1 -1
- package/dist/browser/puppeteer-controller.js +168 -6
- package/dist/browser/puppeteer-controller.js.map +1 -1
- package/dist/build-info.json +2 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +75 -10
- package/dist/index.js.map +1 -1
- package/dist/ontology/ontology-validator.d.ts.map +1 -1
- package/dist/ontology/ontology-validator.js +9 -5
- package/dist/ontology/ontology-validator.js.map +1 -1
- package/dist/ontology/upstream-vocabulary.d.ts +2 -2
- package/dist/ontology/upstream-vocabulary.js +2 -2
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +198 -47
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/engine-lists.d.ts +20 -0
- package/dist/tools/engine-lists.d.ts.map +1 -0
- package/dist/tools/engine-lists.js +86 -0
- package/dist/tools/engine-lists.js.map +1 -0
- package/dist/tools/engine-methods.d.ts.map +1 -1
- package/dist/tools/engine-methods.js +8 -0
- package/dist/tools/engine-methods.js.map +1 -1
- package/dist/tools/engine-surface.d.ts +1 -1
- package/dist/tools/engine-surface.d.ts.map +1 -1
- package/dist/tools/engine-surface.js +20 -4
- package/dist/tools/engine-surface.js.map +1 -1
- package/dist/tools/handlers/beats.d.ts +64 -0
- package/dist/tools/handlers/beats.d.ts.map +1 -0
- package/dist/tools/handlers/beats.js +83 -0
- package/dist/tools/handlers/beats.js.map +1 -0
- package/dist/tools/handlers/brand-intake.d.ts +95 -0
- package/dist/tools/handlers/brand-intake.d.ts.map +1 -0
- package/dist/tools/handlers/brand-intake.js +242 -0
- package/dist/tools/handlers/brand-intake.js.map +1 -0
- package/dist/tools/handlers.d.ts +23 -11
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +265 -14
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/minimal-descriptions.d.ts.map +1 -1
- package/dist/tools/minimal-descriptions.js +5 -3
- package/dist/tools/minimal-descriptions.js.map +1 -1
- package/dist/tools/shape-params.d.ts +2 -2
- package/dist/tools/shape-params.js +2 -2
- package/dist/tools/stick-vocabulary.d.ts +1 -1
- package/dist/tools/stick-vocabulary.js +1 -1
- package/dist/tools/toolkits.d.ts.map +1 -1
- package/dist/tools/toolkits.js +2 -0
- package/dist/tools/toolkits.js.map +1 -1
- package/dist/types/code-generator.d.ts +63 -2
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +533 -49
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +2199 -640
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +186 -50
- package/dist/types/schemas.js.map +1 -1
- package/dist/utils/a11y.d.ts +2 -0
- package/dist/utils/a11y.d.ts.map +1 -1
- package/dist/utils/a11y.js +1 -1
- package/dist/utils/a11y.js.map +1 -1
- package/dist/utils/beats.d.ts +58 -0
- package/dist/utils/beats.d.ts.map +1 -0
- package/dist/utils/beats.js +110 -0
- package/dist/utils/beats.js.map +1 -0
- package/dist/utils/mcp-log.d.ts +16 -0
- package/dist/utils/mcp-log.d.ts.map +1 -0
- package/dist/utils/mcp-log.js +10 -0
- package/dist/utils/mcp-log.js.map +1 -0
- package/dist/utils/progress.d.ts +28 -0
- package/dist/utils/progress.d.ts.map +1 -0
- package/dist/utils/progress.js +24 -0
- package/dist/utils/progress.js.map +1 -0
- package/dist/utils/svg-size.d.ts +22 -0
- package/dist/utils/svg-size.d.ts.map +1 -0
- package/dist/utils/svg-size.js +43 -0
- package/dist/utils/svg-size.js.map +1 -0
- package/dist/vendor/design/design-style.js +1 -1
- package/manifest.json +9 -1
- package/package.json +7 -4
package/dist/types/schemas.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* 3. TypeScript type inference
|
|
9
9
|
*/
|
|
10
10
|
import { z } from 'zod';
|
|
11
|
+
import { ENGINE_EASING_NAMES, DESIGN_MEDIA, DESIGN_MEDIA_APPLY } from '../tools/engine-lists.js';
|
|
11
12
|
// =============================================================================
|
|
12
13
|
// COMMON SCHEMAS
|
|
13
14
|
// =============================================================================
|
|
@@ -70,14 +71,77 @@ export const BlendModeSchema = z.enum([
|
|
|
70
71
|
'difference',
|
|
71
72
|
'exclusion',
|
|
72
73
|
]).describe('Blend mode for compositing with background');
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Every easing name the engine's table accepts: GENERATED from FxTool's
|
|
76
|
+
* EASING_NAMES (scripts/sync-engine-lists.mjs, checked in prepublishOnly).
|
|
77
|
+
* Keyframes, masks, relations and the camera all resolve a name through that
|
|
78
|
+
* one table, so every easing enum here is this list.
|
|
79
|
+
*/
|
|
80
|
+
export const KEYFRAME_EASINGS = ENGINE_EASING_NAMES;
|
|
81
|
+
/** One line per easing, for tool descriptions and pinepaper_get_available_easings. */
|
|
82
|
+
export const EASING_DESCRIPTIONS = {
|
|
83
|
+
linear: 'Constant speed',
|
|
84
|
+
easeIn: 'Slow start, fast end',
|
|
85
|
+
easeOut: 'Fast start, slow end',
|
|
86
|
+
easeInOut: 'Slow start and end',
|
|
87
|
+
easeInCubic: 'Slow start, stronger than easeIn',
|
|
88
|
+
easeOutCubic: 'Slow end, stronger than easeOut',
|
|
89
|
+
easeInOutCubic: 'Slow start and end, stronger than easeInOut',
|
|
90
|
+
bounce: 'Bounces to rest at the end (same as easeOutBounce)',
|
|
91
|
+
easeOutBounce: 'Bounces to rest at the end',
|
|
92
|
+
easeInBounce: 'Bounces at the start, then moves',
|
|
93
|
+
elastic: 'Overshoots and oscillates at the end (same as easeOutElastic)',
|
|
94
|
+
easeOutElastic: 'Overshoots and oscillates at the end',
|
|
95
|
+
easeInElastic: 'Oscillates at the start, then moves',
|
|
96
|
+
easeOutBack: 'Overshoots the target slightly, then settles',
|
|
97
|
+
easeInBack: 'Pulls back first, then moves',
|
|
98
|
+
easeInOutBack: 'Pulls back first and overshoots at the end',
|
|
99
|
+
spring: 'Damped spring, ζ≈1: settles with no overshoot',
|
|
100
|
+
springSnappy: 'Damped spring, ζ≈0.69: quick, one ~5% overshoot',
|
|
101
|
+
springPlayful: 'Damped spring, ζ≈0.45: ~21% overshoot, visible wobbles',
|
|
102
|
+
easeInQuad: 'Quadratic slow start (same as easeIn)',
|
|
103
|
+
easeOutQuad: 'Quadratic slow end (same as easeOut)',
|
|
104
|
+
easeInOutQuad: 'Quadratic slow start and end (same as easeInOut)',
|
|
105
|
+
easeInSine: 'Gentle slow start (sine)',
|
|
106
|
+
easeOutSine: 'Gentle slow end (sine)',
|
|
107
|
+
easeInOutSine: 'Gentle slow start and end (sine)',
|
|
108
|
+
easeInQuart: 'Slow start, stronger than cubic (t⁴)',
|
|
109
|
+
easeOutQuart: 'Slow end, stronger than cubic',
|
|
110
|
+
easeInOutQuart: 'Slow start and end, stronger than cubic',
|
|
111
|
+
easeInQuint: 'Very slow start (t⁵)',
|
|
112
|
+
easeOutQuint: 'Very slow end',
|
|
113
|
+
easeInOutQuint: 'Very slow start and end',
|
|
114
|
+
easeInExpo: 'Near-still start, then fast (the standard expo curve)',
|
|
115
|
+
easeOutExpo: 'Fast, then a long near-still end (the standard expo curve)',
|
|
116
|
+
easeInOutExpo: 'Near-still at both ends, fast in the middle',
|
|
117
|
+
easeInCirc: 'Slow start, sharp finish (circular)',
|
|
118
|
+
easeOutCirc: 'Sharp start, slow finish (circular)',
|
|
119
|
+
easeInOutCirc: 'Circular slow start and end',
|
|
120
|
+
};
|
|
121
|
+
export const EasingSchema = z.enum(KEYFRAME_EASINGS).describe('Easing function for animation');
|
|
122
|
+
/**
|
|
123
|
+
* A keyframe's easing: a name from the table, or a custom cubic Bézier
|
|
124
|
+
* [x1, y1, x2, y2]. The interpolator runs the array natively, the validator
|
|
125
|
+
* accepts it, and every export path handles it (SMIL clamps y into keySplines;
|
|
126
|
+
* CSS and Lottie take the raw curve). Keyframe tracks only — keyframe_animate,
|
|
127
|
+
* camera_animate and inline keyframes — not masks or relations (fxtool-d7).
|
|
128
|
+
*/
|
|
129
|
+
export const BezierEasingSchema = z.tuple([z.number(), z.number(), z.number(), z.number()])
|
|
130
|
+
.refine(([x1, , x2]) => x1 >= 0 && x1 <= 1 && x2 >= 0 && x2 <= 1, {
|
|
131
|
+
message: 'cubic-bezier x1 and x2 must be within 0..1 (y1 and y2 may exceed it for overshoot)',
|
|
132
|
+
})
|
|
133
|
+
.describe('Custom cubic-bezier [x1, y1, x2, y2]');
|
|
134
|
+
export const KeyframeEasingSchema = z.union([EasingSchema, BezierEasingSchema]);
|
|
135
|
+
/** JSON-Schema form of KeyframeEasingSchema, for tool definitions. */
|
|
136
|
+
export const KEYFRAME_EASING_JSON_SCHEMA = {
|
|
137
|
+
oneOf: [
|
|
138
|
+
{ type: 'string', enum: [...KEYFRAME_EASINGS] },
|
|
139
|
+
{
|
|
140
|
+
type: 'array', items: { type: 'number' }, minItems: 4, maxItems: 4,
|
|
141
|
+
description: 'or a custom cubic-bezier [x1, y1, x2, y2] — x in 0..1, y may exceed 0..1 for overshoot',
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
};
|
|
81
145
|
// =============================================================================
|
|
82
146
|
// ITEM TYPES
|
|
83
147
|
// =============================================================================
|
|
@@ -251,7 +315,7 @@ export const SimpleAnimationTypeSchema = z.enum([
|
|
|
251
315
|
export const KeyframeSchema = z.object({
|
|
252
316
|
time: z.number().describe('Time in seconds for this keyframe'),
|
|
253
317
|
properties: z.record(z.unknown()).describe('Property values at this keyframe'),
|
|
254
|
-
easing:
|
|
318
|
+
easing: KeyframeEasingSchema.optional().default('linear'),
|
|
255
319
|
});
|
|
256
320
|
// =============================================================================
|
|
257
321
|
// RELATION TYPES
|
|
@@ -1149,6 +1213,8 @@ export const ModifyItemInputSchema = z.object({
|
|
|
1149
1213
|
itemId: z.string().describe('Registry ID of the item'),
|
|
1150
1214
|
properties: z.record(z.unknown()).describe('Properties to update'),
|
|
1151
1215
|
data: ItemDataFlagsSchema.optional(),
|
|
1216
|
+
atTime: z.number().min(0).optional().describe('AUTO-KEY: write these properties as keyframes at this time (seconds) instead of a static edit — eased from the previous value, other keys kept.'),
|
|
1217
|
+
easing: z.string().optional().describe('atTime: easing of the segment arriving at the new key (default linear).'),
|
|
1152
1218
|
});
|
|
1153
1219
|
// Delete Item
|
|
1154
1220
|
export const DeleteItemInputSchema = z.object({
|
|
@@ -1367,13 +1433,16 @@ export const SetBackgroundColorInputSchema = z.object({
|
|
|
1367
1433
|
color: SolidColorSchema.describe('Background color to set (solid colors only)'),
|
|
1368
1434
|
});
|
|
1369
1435
|
export const SetCanvasSizeInputSchema = z.object({
|
|
1370
|
-
//
|
|
1371
|
-
//
|
|
1372
|
-
//
|
|
1436
|
+
// 16384: the engine's own safe dimension for Chrome / Firefox
|
|
1437
|
+
// (ExportEngine.CANVAS_LIMITS; its hard limit is 32767 per side and 2^28
|
|
1438
|
+
// pixels), and what DOOH / projection canvases need (round 12: 8220 and
|
|
1439
|
+
// 12000 px wide rendered fine while this refused them at 8192, which is
|
|
1440
|
+
// Safari's safe size). Above 8192 the result warns that Safari / iOS may
|
|
1441
|
+
// not manage it.
|
|
1373
1442
|
// The refusal NAMES the route: a 728x90 banner refused with a bare "too
|
|
1374
1443
|
// small" left a caller to export the preset instead (round 8 BB, 1.48).
|
|
1375
|
-
width: z.number().min(100, "a canvas side under 100 px is not supported. For small assets (a 728x90 or 320x50 banner, a 28 px emote) design at 2-4x — e.g. 1456x180 — and export with agent_export region {x:0, y:0, width, height, outputWidth:728, outputHeight:90}. For VIDEO at a small size, design at 2x and export with agent_export scale: 0.5.").max(
|
|
1376
|
-
height: z.number().min(100, "a canvas side under 100 px is not supported. For small assets (a 728x90 or 320x50 banner, a 28 px emote) design at 2-4x — e.g. 1456x180 — and export with agent_export region {x:0, y:0, width, height, outputWidth:728, outputHeight:90}. For VIDEO at a small size, design at 2x and export with agent_export scale: 0.5.").max(
|
|
1444
|
+
width: z.number().min(100, "a canvas side under 100 px is not supported. For small assets (a 728x90 or 320x50 banner, a 28 px emote) design at 2-4x — e.g. 1456x180 — and export with agent_export region {x:0, y:0, width, height, outputWidth:728, outputHeight:90}. For VIDEO at a small size, design at 2x and export with agent_export scale: 0.5.").max(16384).describe('Canvas width (100-16384; above 8192 Safari / iOS may fail)'),
|
|
1445
|
+
height: z.number().min(100, "a canvas side under 100 px is not supported. For small assets (a 728x90 or 320x50 banner, a 28 px emote) design at 2-4x — e.g. 1456x180 — and export with agent_export region {x:0, y:0, width, height, outputWidth:728, outputHeight:90}. For VIDEO at a small size, design at 2x and export with agent_export scale: 0.5.").max(16384).describe('Canvas height (100-16384; above 8192 Safari / iOS may fail)'),
|
|
1377
1446
|
preset: z.string().optional().describe('Optional preset name'),
|
|
1378
1447
|
});
|
|
1379
1448
|
// Export
|
|
@@ -1816,9 +1885,7 @@ export const MaskPresetSchema = z.enum([
|
|
|
1816
1885
|
export const MaskTypeSchema = z.enum([
|
|
1817
1886
|
'rectangle', 'circle', 'ellipse', 'star', 'triangle', 'hexagon', 'heart', 'rounded',
|
|
1818
1887
|
]).describe('Mask shape type');
|
|
1819
|
-
export const MaskEasingSchema = z.enum(
|
|
1820
|
-
'linear', 'easeIn', 'easeOut', 'easeInOut', 'bounce', 'elastic',
|
|
1821
|
-
]).describe('Mask animation easing function');
|
|
1888
|
+
export const MaskEasingSchema = z.enum(KEYFRAME_EASINGS).describe('Mask animation easing function');
|
|
1822
1889
|
export const MaskKeyframePropertiesSchema = z.object({
|
|
1823
1890
|
x: z.number().optional(),
|
|
1824
1891
|
y: z.number().optional(),
|
|
@@ -1997,8 +2064,8 @@ export const AgentBatchOperationSchema = z.object({
|
|
|
1997
2064
|
generatorParams: z.record(z.unknown()).optional().describe('Generator parameters'),
|
|
1998
2065
|
generatorRegion: GeneratorRegionSchema.optional().describe('Optional sub-region {x, y, width, height} for execute_generator'),
|
|
1999
2066
|
// Set canvas size fields
|
|
2000
|
-
width: z.number().min(100).max(
|
|
2001
|
-
height: z.number().min(100).max(
|
|
2067
|
+
width: z.number().min(100).max(16384).optional().describe('Canvas width for set_canvas_size (100-16384)'),
|
|
2068
|
+
height: z.number().min(100).max(16384).optional().describe('Canvas height for set_canvas_size (100-16384)'),
|
|
2002
2069
|
preset: z.string().optional().describe('Canvas preset for set_canvas_size (e.g. instagram, youtube)'),
|
|
2003
2070
|
// Keyframe animate fields
|
|
2004
2071
|
keyframes: z.array(z.object({
|
|
@@ -2187,6 +2254,13 @@ export const AgentExportInputSchema = z.object({
|
|
|
2187
2254
|
loop: z.union([z.boolean(), z.number().int().min(0).max(1000)]).optional().describe('gif / apng: true = loop forever, false / 0 / 1 = play once, n = play n times.'),
|
|
2188
2255
|
maxBytes: z.number().int().positive().optional().describe('gif only: a size budget in bytes (email wants <= 1 MB). Over it, the GIF is re-encoded smaller — frame size scaled from the overshoot — at most twice; the result reports each attempt and whether the budget was met.'),
|
|
2189
2256
|
broadcast: z.boolean().optional().describe('mp4 only: broadcast-safe — BT.709, limited range (samples 16-235), tagged bt709, constant bitrate with an 8 Mbps floor at 720p and up (4 below). result.video reports what the encoder did.'),
|
|
2257
|
+
frames: z.number().int().min(1).max(36_000).optional().describe('Animated formats: export exactly this many frames; it takes the place of duration (duration x fps frames is rounded UP for mp4 / webm / gif). For frame-exact segments to assemble.'),
|
|
2258
|
+
// FxTool #55 (motion-course G2): N sub-frames per frame, averaged across
|
|
2259
|
+
// the shutter, so fast motion smears the way a camera's does.
|
|
2260
|
+
motionBlur: z.object({
|
|
2261
|
+
subframes: z.number().int().min(1).max(32).optional().describe('Renders per frame (default 8; 1 = no blur). Export time scales with it.'),
|
|
2262
|
+
shutter: z.number().min(0).max(1).optional().describe('Fraction of the frame interval the shutter is open (default 0.5, a 180° shutter).'),
|
|
2263
|
+
}).optional().describe('mp4 / webm: real motion blur. Each frame is rendered subframes times across the shutter and averaged, so fast movement smears instead of strobing. Costs subframes× the render time: run estimateOnly first, which reports renders and renderCost.'),
|
|
2190
2264
|
deterministic: z.boolean().optional().describe('mp4 / webm: byte-identical files for the same scene, so an export can be checksummed or cached by hash. The container\'s creation / modification timestamps are pinned; nothing about the pixels changes.'),
|
|
2191
2265
|
broadcastHeadroom: z.number().int().min(0).max(40).optional().describe('mp4 with broadcast only (default 12 with broadcast): luma codes kept clear at both ends of 16-235, so encoder ringing stays legal. 12 measured clean on hard edges; 4 cuts out-of-range samples ~100x, 12 ~1000x; never to zero, so a legaliser pass is still required (result fidelity names it).'),
|
|
2192
2266
|
bitrate: z.number().int().min(100_000).max(200_000_000).optional().describe('mp4 / webm: target bits per second, replacing the quality-derived one. The browser encoder treats it as a CEILING: simple content comes out lower, and result.video.achievedBitrate says what it was.'),
|
|
@@ -2196,8 +2270,8 @@ export const AgentExportInputSchema = z.object({
|
|
|
2196
2270
|
region: z.object({
|
|
2197
2271
|
x: z.number(), y: z.number(),
|
|
2198
2272
|
width: z.number().positive(), height: z.number().positive(),
|
|
2199
|
-
outputWidth: z.number().int().positive().max(
|
|
2200
|
-
outputHeight: z.number().int().positive().max(
|
|
2273
|
+
outputWidth: z.number().int().positive().max(16384).optional(),
|
|
2274
|
+
outputHeight: z.number().int().positive().max(16384).optional(),
|
|
2201
2275
|
excludeForeign: z.boolean().optional().describe('Default true: an item that reaches into the region but is centred outside it (a neighbouring card\'s overflow) is left out of this image; result.excludedItems names them. false renders everything that touches the region.'),
|
|
2202
2276
|
}).optional().describe('png / jpg / webp only: export just this canvas region (canvas coordinates, top-left x/y) — carousel slices, crops. Output is the region\'s size unless outputWidth/outputHeight say otherwise; a different aspect is covered, not stretched.'),
|
|
2203
2277
|
}).describe('Smart export options')
|
|
@@ -2211,7 +2285,10 @@ export const AgentExportInputSchema = z.object({
|
|
|
2211
2285
|
if (val.broadcastHeadroom !== undefined && !val.broadcast) {
|
|
2212
2286
|
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['broadcastHeadroom'], message: 'broadcastHeadroom applies only with broadcast: true (mp4).' });
|
|
2213
2287
|
}
|
|
2214
|
-
|
|
2288
|
+
if (val.frames !== undefined && !['mp4', 'webm', 'gif', 'apng'].includes(String(val.format))) {
|
|
2289
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['frames'], message: 'frames applies to animated formats: mp4, webm, gif, apng.' });
|
|
2290
|
+
}
|
|
2291
|
+
for (const k of ['bitrate', 'minBitrate', 'bitrateMode', 'deterministic', 'motionBlur']) {
|
|
2215
2292
|
if (val[k] !== undefined && val.format !== 'mp4' && val.format !== 'webm') {
|
|
2216
2293
|
ctx.addIssue({ code: z.ZodIssueCode.custom, path: [k], message: `${k} is a video setting: mp4 or webm. A gif's size is set by maxBytes and scale.` });
|
|
2217
2294
|
}
|
|
@@ -2256,14 +2333,14 @@ export const AgentExportInputSchema = z.object({
|
|
|
2256
2333
|
// Same shape as the wav refusal below: a knob that cannot apply is said
|
|
2257
2334
|
// once rather than accepted and dropped. png/svg/pdf carry no encode
|
|
2258
2335
|
// target, so scaling them here would promise a resize that never happens.
|
|
2259
|
-
if (val.fps !== undefined && !['mp4', 'webm', 'gif', 'auto'].includes(String(val.format))) {
|
|
2336
|
+
if (val.fps !== undefined && !['mp4', 'webm', 'gif', 'apng', 'auto'].includes(String(val.format))) {
|
|
2260
2337
|
ctx.addIssue({
|
|
2261
2338
|
code: z.ZodIssueCode.custom,
|
|
2262
2339
|
path: ['fps'],
|
|
2263
|
-
message: `fps applies to
|
|
2340
|
+
message: `fps applies to animated formats (mp4/webm/gif/apng), not "${val.format}" — a still has no frame rate.`,
|
|
2264
2341
|
});
|
|
2265
2342
|
}
|
|
2266
|
-
if (val.scale !== undefined && !['mp4', 'webm', 'gif', 'auto'].includes(String(val.format))) {
|
|
2343
|
+
if (val.scale !== undefined && !['mp4', 'webm', 'gif', 'apng', 'auto'].includes(String(val.format))) {
|
|
2267
2344
|
ctx.addIssue({
|
|
2268
2345
|
code: z.ZodIssueCode.custom,
|
|
2269
2346
|
path: ['scale'],
|
|
@@ -2694,6 +2771,8 @@ export const ImportImageInputSchema = z.object({
|
|
|
2694
2771
|
position: PositionSchema.optional().describe('Position on canvas (defaults to center)'),
|
|
2695
2772
|
maxWidth: z.number().optional().describe('Maximum width constraint in pixels'),
|
|
2696
2773
|
maxHeight: z.number().optional().describe('Maximum height constraint in pixels'),
|
|
2774
|
+
maxEdge: z.union([z.number().int().positive(), z.literal('native')]).optional().describe("The longest edge the studio STORES the image at, in px; 'native' keeps every pixel. Default: the studio's cap (it follows the canvas on desktop)."),
|
|
2775
|
+
nativeSize: z.boolean().optional().describe('Place the image at its source footprint even if it was stored smaller.'),
|
|
2697
2776
|
mask: ImageMaskSchema.optional().describe('Optional mask shape to apply to the image'),
|
|
2698
2777
|
smoothing: z.enum(['off', 'low', 'medium', 'high']).optional().describe("Resampling when the image is scaled. 'off' = nearest-neighbour, hard pixel edges (pixel art). Default 'low'."),
|
|
2699
2778
|
}).describe('Import image input');
|
|
@@ -2989,8 +3068,10 @@ export const TransformInputSchema = z.object({
|
|
|
2989
3068
|
// count is already the thing the consolidation backlog complains about, and
|
|
2990
3069
|
// `pinepaper_media` / `pinepaper_transform` set the precedent.
|
|
2991
3070
|
export const BrandKitInputSchema = z.object({
|
|
2992
|
-
/** 'plan' reports what WOULD change without touching the scene. */
|
|
2993
|
-
action: z.enum(['plan', 'apply']),
|
|
3071
|
+
/** 'plan' reports what WOULD change without touching the scene. 'from_url' reads a site into a proposed kit. */
|
|
3072
|
+
action: z.enum(['plan', 'apply', 'from_url']),
|
|
3073
|
+
url: z.string().url().optional().describe("from_url: the product's site (http/https). Read in an isolated browser context: the studio is not navigated."),
|
|
3074
|
+
screenshots: z.number().int().min(1).max(6).optional().describe('from_url: screenshots taken down the page (default 3).'),
|
|
2994
3075
|
// Spelled out rather than a loose record. A first version typed this as
|
|
2995
3076
|
// `z.record(...)` described as a "role → colour map", which is NOT the shape
|
|
2996
3077
|
// the engine takes — every call was rejected with "name is required;
|
|
@@ -3011,9 +3092,11 @@ export const BrandKitInputSchema = z.object({
|
|
|
3011
3092
|
heading: z.string().optional(),
|
|
3012
3093
|
body: z.string().optional(),
|
|
3013
3094
|
}).optional(),
|
|
3014
|
-
}),
|
|
3095
|
+
}).optional(),
|
|
3015
3096
|
selectionOnly: z.boolean().optional(),
|
|
3016
|
-
})
|
|
3097
|
+
})
|
|
3098
|
+
.refine((v) => v.action === 'from_url' || !!v.kit, { message: 'plan and apply require kit (from_url proposes one)', path: ['kit'] })
|
|
3099
|
+
.refine((v) => v.action !== 'from_url' || (!!v.url && /^https?:\/\//i.test(v.url)), { message: 'from_url requires an http(s) url', path: ['url'] });
|
|
3017
3100
|
export const ComponentInputSchema = z.object({
|
|
3018
3101
|
action: z.enum(['define', 'list', 'instantiate', 'set_override', 'sync', 'update_from_instance', 'detach']),
|
|
3019
3102
|
itemIds: z.array(z.string()).optional(),
|
|
@@ -3153,6 +3236,13 @@ export const AccessibilityCheckInputSchema = z.object({
|
|
|
3153
3236
|
duration: z.number().positive().max(120).optional().describe('flash: seconds of the timeline to sample (default 5).'),
|
|
3154
3237
|
fps: z.number().int().min(10).max(60).optional().describe('flash: samples per second (default 20; a flash rate is only seen below half of it).'),
|
|
3155
3238
|
});
|
|
3239
|
+
// QUERY MUTATIONS (plan C5a): a property's keyframe series.
|
|
3240
|
+
export const QueryMutationsInputSchema = z.object({
|
|
3241
|
+
itemId: z.string().min(1),
|
|
3242
|
+
property: z.string().min(1).optional().describe('One property; all animated properties when omitted.'),
|
|
3243
|
+
range: z.tuple([z.number().min(0), z.number().min(0)]).optional().describe('[from, to] seconds.'),
|
|
3244
|
+
fps: z.number().positive().max(240).optional().describe('For the frame numbers (default 30).'),
|
|
3245
|
+
});
|
|
3156
3246
|
// RENDER BATCH (data-driven creative): one scene, many rows, one export per
|
|
3157
3247
|
// row. The handler runs each row through modify_item / template_params and
|
|
3158
3248
|
// then agent_export, so every per-tool check applies to every row.
|
|
@@ -3443,11 +3533,34 @@ export const ValidateSceneInputSchema = z.object({
|
|
|
3443
3533
|
});
|
|
3444
3534
|
// Deterministic headless frame capture (S3) — evaluate the scene at each time via
|
|
3445
3535
|
// sceneAt(t) with Math.random seeded once around the whole sequence, then snapshot.
|
|
3536
|
+
/**
|
|
3537
|
+
* A contact sheet: many frames tiled into ONE small image, saved to a file,
|
|
3538
|
+
* so an agent can look at its own film (G3 critique loop). Built on the same
|
|
3539
|
+
* deterministic capture; the times come from `times`, or from one of the
|
|
3540
|
+
* modes below.
|
|
3541
|
+
*/
|
|
3542
|
+
export const CaptureSheetSchema = z.object({
|
|
3543
|
+
every: z.number().positive().optional().describe('One frame every N seconds from 0 to duration (needs duration).'),
|
|
3544
|
+
duration: z.number().positive().optional().describe('Scene length in seconds, for every and loopSeam.'),
|
|
3545
|
+
strip: z.object({
|
|
3546
|
+
at: z.number().min(0).describe('Centre time in seconds.'),
|
|
3547
|
+
count: z.number().int().min(2).max(24).default(8),
|
|
3548
|
+
step: z.number().positive().default(1 / 30).describe('Seconds between frames (default one frame at 30 fps).'),
|
|
3549
|
+
}).optional().describe('Consecutive frames around a moment: to check a transition or a stutter.'),
|
|
3550
|
+
loopSeam: z.boolean().optional().describe('The last frame next to the first (needs duration): does the loop join without a jump?'),
|
|
3551
|
+
tileWidth: z.number().int().min(120).max(1080).default(360).describe('Width of each tile in px. 360 is phone width: read every word at this size.'),
|
|
3552
|
+
columns: z.number().int().min(1).max(12).optional().describe('Tiles per row (default: as square as the count allows, at most 6).'),
|
|
3553
|
+
format: z.enum(['jpeg', 'png']).default('jpeg'),
|
|
3554
|
+
labels: z.boolean().default(true).describe('Stamp each tile with its time.'),
|
|
3555
|
+
});
|
|
3446
3556
|
export const CaptureFramesInputSchema = z.object({
|
|
3447
|
-
times: z.array(z.number()).min(1).describe('Capture times in seconds, rendered in order.'),
|
|
3557
|
+
times: z.array(z.number()).min(1).optional().describe('Capture times in seconds, rendered in order. Required unless sheet gives every, strip or loopSeam.'),
|
|
3448
3558
|
seed: z.number().optional().describe('Seed Math.random once around the whole sequence so random generators/particles are reproducible (default 0).'),
|
|
3449
3559
|
includeDataUrls: z.boolean().optional().describe('Include each frame as a PNG data URL (large — token-heavy). Default false → returns a cheap per-frame hash + byte size only.'),
|
|
3450
|
-
|
|
3560
|
+
sheet: CaptureSheetSchema.optional().describe('Return ONE tiled image (saved to a file) instead of per-frame hashes, plus a critique rubric.'),
|
|
3561
|
+
})
|
|
3562
|
+
.refine((v) => !!v.times || !!(v.sheet && (v.sheet.every || v.sheet.strip || v.sheet.loopSeam)), { message: 'give times, or sheet with every, strip or loopSeam', path: ['times'] })
|
|
3563
|
+
.refine((v) => !v.sheet || !(v.sheet.every || v.sheet.loopSeam) || !!v.sheet.duration || !!v.times, { message: 'sheet.every and sheet.loopSeam need sheet.duration (the scene length in seconds)', path: ['sheet', 'duration'] });
|
|
3451
3564
|
// Ontology→scene compiler (S12-E3). Compiles a pp:-namespaced JSON-LD design graph
|
|
3452
3565
|
// (typed nodes + structural edges like pp:onTopOf) into a real scene. Compilation
|
|
3453
3566
|
// runs server-side (deterministic, testable); only the resulting create/addRelation
|
|
@@ -3474,20 +3587,27 @@ export const LintSceneInputSchema = z.object({
|
|
|
3474
3587
|
// Media (video/audio) via window.PinePaperAgent. Agent-facing surface is URL-based
|
|
3475
3588
|
// (the agent can't hand over a File). Uploaded media are first-class canvas items.
|
|
3476
3589
|
export const MediaInputSchema = z.object({
|
|
3477
|
-
action: z.enum(['upload_video', 'upload_audio', 'list', 'remove', 'set_playback_rate', 'set_clip', 'split', 'add_transition', 'set_time_remap', 'speed_ramp', 'match_cut', 'apply_track_matte', 'stop_live_matte'])
|
|
3590
|
+
action: z.enum(['upload_video', 'upload_audio', 'list', 'remove', 'set_playback_rate', 'set_clip', 'split', 'add_transition', 'set_time_remap', 'speed_ramp', 'match_cut', 'apply_track_matte', 'stop_live_matte', 'analyze_reference', 'remake_from_reference'])
|
|
3478
3591
|
.describe("'upload_video' / 'upload_audio' (from a URL) · 'list' media · 'remove' by id · 'set_playback_rate' · 'set_clip' (re-trim; with timeOffset also moves the clip) · 'split' (cut a clip in two at canvas time `at`; the first half keeps its id) · 'add_transition' (crossfade or dip between fromItemId, which ends at the cut, and toItemId, which starts there) · 'set_time_remap' (canvas-time→source-time curve: ramps, freezes, reverse) · 'speed_ramp' ({duration, speed} segments — the human way to say a remap) · 'match_cut' (cut between two shots aligning the SUBJECT via on-device detection) · 'apply_track_matte' (an item's alpha driven by another item's luma/alpha — type-filled-with-footage; live:true keeps it tracking as the matte animates) · 'stop_live_matte'"),
|
|
3479
3592
|
// Not .url(): a bare absolute path is not a URL, and 48f3a2a made the server
|
|
3480
3593
|
// read local files — so `/Users/…/broll1.mp4` was advertised and then rejected
|
|
3481
3594
|
// by the schema before anything could read it. resolveMediaSource validates
|
|
3482
3595
|
// what it is given and names the problem.
|
|
3483
3596
|
url: z.string().min(1).optional().describe('Required for upload_video / upload_audio: an http(s) URL, a data: URL, or a local file path (absolute, relative to the server, or file://).'),
|
|
3484
|
-
id: z.string().optional().describe('Media id — required for remove / set_playback_rate / set_clip / split.'),
|
|
3597
|
+
id: z.string().optional().describe('Media id — required for remove / set_playback_rate / set_clip / split / analyze_reference.'),
|
|
3598
|
+
// G4 reference intake (FxTool #52): measure a reference video's FORMAT and
|
|
3599
|
+
// build on it. Nothing of its footage, words or sound is copied.
|
|
3600
|
+
texts: z.array(z.string()).optional().describe('remake_from_reference: your words, one per shot of the reference (a short list is reused across shots).'),
|
|
3601
|
+
duration: z.number().positive().optional().describe('remake_from_reference: total length in seconds (default the reference\'s own).'),
|
|
3602
|
+
clear: z.boolean().optional().describe('remake_from_reference: empty the canvas first (media survives).'),
|
|
3603
|
+
thumbnails: z.boolean().optional().describe('analyze_reference: include a small image per shot (default false: they are inline images and token-heavy).'),
|
|
3485
3604
|
rate: z.number().min(0.25).max(4).optional().describe('Playback rate 0.25–4 — required for set_playback_rate.'),
|
|
3486
3605
|
inPoint: z.number().min(0).optional().describe('Clip in-point in media-time seconds — required for set_clip.'),
|
|
3487
3606
|
outPoint: z.number().min(0).optional().describe('Clip out-point in media-time seconds — required for set_clip.'),
|
|
3488
3607
|
// upload_video placement
|
|
3489
3608
|
position: PositionSchema.optional().describe('Canvas position — upload_video.'),
|
|
3490
|
-
scale: z.number().positive().optional().describe('
|
|
3609
|
+
scale: z.number().positive().optional().describe("upload_video: a multiplier on the FIT the studio applies (fit: 'contain' by default), not on the video's own pixels — 0.7 of a contain-fit 1920 clip on a 1080 canvas is not 0.7 x 1920. Pass fit: 'none' for scale to be of the source pixels."),
|
|
3610
|
+
fit: z.enum(['contain', 'cover', 'none']).optional().describe("upload_video: how the clip is sized before scale — 'contain' (default; whole clip inside the canvas), 'cover' (fills it, cropped), 'none' (source pixels). The result gives the scale and bounds that landed."),
|
|
3491
3611
|
timeOffset: z.number().optional().describe('Start offset on the timeline in seconds — upload_video / upload_audio; set_clip: move the clip to start here (canvas seconds).'),
|
|
3492
3612
|
clipInPoint: z.number().optional().describe('Trim in-point in seconds — upload_video.'),
|
|
3493
3613
|
clipOutPoint: z.number().optional().describe('Trim out-point in seconds — upload_video.'),
|
|
@@ -3530,6 +3650,8 @@ export const MediaInputSchema = z.object({
|
|
|
3530
3650
|
atPlayhead: z.boolean().optional().describe('upload_video / upload_audio: start the clip at the current playback time (instead of timeOffset).'),
|
|
3531
3651
|
})
|
|
3532
3652
|
.refine((v) => !(v.action === 'upload_video' || v.action === 'upload_audio') || !!v.url, { message: 'upload requires url', path: ['url'] })
|
|
3653
|
+
.refine((v) => v.action !== 'analyze_reference' || !!v.id, { message: 'analyze_reference requires id: the uploaded reference clip (upload_video it first)', path: ['id'] })
|
|
3654
|
+
.refine((v) => v.action !== 'remake_from_reference' || (!!v.texts && v.texts.length > 0), { message: 'remake_from_reference requires texts: your own words, one per shot', path: ['texts'] })
|
|
3533
3655
|
.refine((v) => !(v.action === 'remove' || v.action === 'set_playback_rate' || v.action === 'set_clip' || v.action === 'split') || !!v.id, { message: 'this action requires id', path: ['id'] })
|
|
3534
3656
|
.refine((v) => v.action !== 'split' || (v.at !== undefined && v.at >= 0), { message: 'split requires at (canvas seconds) — where to cut', path: ['at'] })
|
|
3535
3657
|
.refine((v) => v.action !== 'add_transition' || (!!v.fromItemId && !!v.toItemId), { message: 'add_transition requires fromItemId (the clip ending at the cut) and toItemId (the clip starting at it)', path: ['fromItemId'] })
|
|
@@ -3615,17 +3737,20 @@ export const TextStyleInputSchema = z.object({
|
|
|
3615
3737
|
* `list_media` before promising a medium in prose.
|
|
3616
3738
|
*/
|
|
3617
3739
|
export const DesignMediumInputSchema = z.object({
|
|
3618
|
-
action: z.enum(['list_media', 'resolve', 'list_stitches', 'apply_thread', 'apply_hatch', 'list_flow_fields', 'list_hatch_options'])
|
|
3619
|
-
.describe("'list_media' (
|
|
3620
|
-
medium: z.string().optional().describe(
|
|
3621
|
-
|
|
3740
|
+
action: z.enum(['list_media', 'resolve', 'list_stitches', 'apply_thread', 'apply_hatch', 'list_flow_fields', 'list_hatch_options', 'apply'])
|
|
3741
|
+
.describe("'apply' (render an item in ANY medium the engine can make: medium + options — watercolor, oil, cutPaper, thread, hatch) · 'list_media' (every medium with fidelity + limitation) · 'resolve' (can this medium be made here, and how honestly) · 'list_stitches' · 'apply_thread' (render an item in thread) · 'apply_hatch' (rule an item with hatching — value through line density) · 'list_flow_fields' · 'list_hatch_options'"),
|
|
3742
|
+
medium: z.string().optional().describe(`resolve / apply: medium key — ${DESIGN_MEDIA.join(', ')}. apply works for ${Object.keys(DESIGN_MEDIA_APPLY).join(', ')}; the others are refused by the engine with the reason (encaustic is absent here; charcoal and ink are brushes, not fills).`),
|
|
3743
|
+
options: z.record(z.string(), z.unknown()).optional().describe('apply: the medium\'s own options. watercolor: color, brush, water 0..1, opacity, edge 0..1 (the dark rim), angle, bleed, seed, keepSource. oil: color, brush, length, density, toneRange, relief, angle, seed, keepSource. cutPaper: color, facet, jitter, shadow, shadowColor, shadowOpacity, shadowOffset [dx, dy], seed, keepSource. thread / hatch: as apply_thread / apply_hatch.'),
|
|
3744
|
+
itemId: z.string().optional().describe('apply / apply_thread / apply_hatch: a closed path, compound path (holes kept), or a group of them (applied per path). The result takes the source\'s place in the stacking order and the source is hidden; applying again replaces it. One undo step.'),
|
|
3622
3745
|
stitch: z.enum(THREAD_STITCHES).optional()
|
|
3623
3746
|
.describe("Which stitch, when the medium is thread. Default longAndShort. These six are THE_STITCH_OPS the engine publishes — call 'list_stitches' for each one's description and its own parameters. An unknown name is refused by the engine rather than quietly stitched as a default fill."),
|
|
3624
3747
|
field: z.object({
|
|
3625
3748
|
kind: z.enum(['radial', 'spine', 'constant']),
|
|
3626
3749
|
cx: z.number().optional(), cy: z.number().optional(),
|
|
3627
3750
|
angle: z.number().optional(),
|
|
3628
|
-
|
|
3751
|
+
// [x, y] is converted to {x, y} here: the engine's own spine reader only
|
|
3752
|
+
// takes {x, y} and crashed on an array pair when called directly.
|
|
3753
|
+
spine: z.array(PositionSchema).optional().describe('Points along the midrib, each {x, y} or [x, y]'),
|
|
3629
3754
|
across: z.boolean().optional(),
|
|
3630
3755
|
}).optional().describe("apply_thread: the direction field — the thing that makes it needlepainting rather than hatching. Default radial from the shape centre. 'spine' runs stitches along a midrib or feather shaft; 'constant' is flat hatch."),
|
|
3631
3756
|
stitchLen: z.number().positive().optional().describe('apply_thread: nominal stitch length px (default 18).'),
|
|
@@ -3669,7 +3794,9 @@ export const DesignMediumInputSchema = z.object({
|
|
|
3669
3794
|
})
|
|
3670
3795
|
.refine((v) => v.action !== 'apply_thread' || !!v.itemId, { message: 'apply_thread requires itemId', path: ['itemId'] })
|
|
3671
3796
|
.refine((v) => v.action !== 'resolve' || !!v.medium, { message: 'resolve requires medium', path: ['medium'] })
|
|
3672
|
-
.refine((v) => v.action !== 'apply_hatch' || !!v.itemId, { message: 'apply_hatch requires itemId', path: ['itemId'] })
|
|
3797
|
+
.refine((v) => v.action !== 'apply_hatch' || !!v.itemId, { message: 'apply_hatch requires itemId', path: ['itemId'] })
|
|
3798
|
+
.refine((v) => v.action !== 'apply' || (!!v.itemId && !!v.medium), { message: 'apply requires itemId and medium', path: ['medium'] })
|
|
3799
|
+
.refine((v) => v.action !== 'apply' || !v.medium || DESIGN_MEDIA.includes(v.medium), { message: `unknown medium; the engine knows ${DESIGN_MEDIA.join(', ')}`, path: ['medium'] });
|
|
3673
3800
|
export const TextEffectInputSchema = z.object({
|
|
3674
3801
|
action: z.enum(['apply', 'list'])
|
|
3675
3802
|
.describe("'apply' (explode a text item into animated characters) · 'list' (the 37 effects with key/label/definition, for a picker)"),
|
|
@@ -3791,7 +3918,7 @@ export const World3DInputSchema = z.object({
|
|
|
3791
3918
|
.describe("'create' (a preset world) · 'describe' (every parameter with type/range — CALL THIS before configure) · 'configure' (live deep-merge patch, schema-validated) · actor ops · 'set_camera' (follow|fixed|orbit) · object ops · 'remove_world' · MESH AUTHORING: 'extrude_path' / 'lathe_path' turn a canvas path into real geometry, 'list_meshes', 'remove_mesh' · 'add_light' / 'set_light' / 'remove_light' / 'list_lights' (at most 8 point lights) · 'add_material' / 'set_material' / 'remove_material' / 'list_materials'"),
|
|
3792
3919
|
spec: z.union([z.string(), z.record(z.string(), z.unknown())]).optional().describe("create: a preset id ('forest', 'snowMountain', …) or a full world spec object."),
|
|
3793
3920
|
character: z.union([z.boolean(), z.record(z.string(), z.unknown())]).optional().describe('create: include the walkable character (true/config).'),
|
|
3794
|
-
patch: z.record(z.string(), z.unknown()).optional().describe('configure: partial world spec, deep-merged and validated — a wrong key errors naming the right one.'),
|
|
3921
|
+
patch: z.record(z.string(), z.unknown()).optional().describe('configure: partial world spec, deep-merged and validated — a wrong key errors naming the right one. Colours (env.zenith, env.horizon, env.fogColor, env.sunColor, env.ambient, env.lowColor … env.propColorB) take hex ("#03050c") or [r, g, b] in 0..1.'),
|
|
3795
3922
|
actorId: z.string().optional().describe('Actor id — add_actor (optional, generated if omitted) / remove_actor / set_actor_pose / set_camera target.'),
|
|
3796
3923
|
x: z.number().optional().describe('World-space x — add_actor / add_object.'),
|
|
3797
3924
|
z: z.number().optional().describe('World-space z — add_actor / add_object.'),
|
|
@@ -3806,8 +3933,12 @@ export const World3DInputSchema = z.object({
|
|
|
3806
3933
|
pathId: z.string().optional().describe("extrude_path / lathe_path: the canvas path to turn into geometry. Extrude sweeps it along depth; lathe revolves its profile around the Y axis. The path is unchanged — the mesh is new."),
|
|
3807
3934
|
mesh: z.object({
|
|
3808
3935
|
id: z.string().optional().describe('Mesh id (generated if omitted).'),
|
|
3809
|
-
depth: z.number().optional().describe('extrude_path: how far to sweep, in
|
|
3810
|
-
unitsPerPixel: z.number().optional().describe('Canvas pixels → world units. The whole scale relationship between the drawing and the world.'),
|
|
3936
|
+
depth: z.number().optional().describe('extrude_path: how far to sweep, in WORLD units (default 1). Not pixels: the outline is canvas px × unitsPerPixel.'),
|
|
3937
|
+
unitsPerPixel: z.number().optional().describe('Canvas pixels → world units (default 0.01, so a 200 px outline is 2 world units). The whole scale relationship between the drawing and the world.'),
|
|
3938
|
+
// FxTool #49 (7c50c838). Declared here, or z.object() strips them before
|
|
3939
|
+
// the emitter's spread can pass them on (the 5.64 colour bug, again).
|
|
3940
|
+
upright: z.boolean().optional().describe('extrude_path: stand the drawing up (canvas up = world up, extruded along Z) — a sign, a letter, a cut-out. Default false: flat, a floor plan in XZ extruded along Y.'),
|
|
3941
|
+
placement: z.enum(['centered', 'canvas']).optional().describe("extrude_path: 'centered' (default) centres the mesh on x/z with its BASE at y, so it stands on the ground at the point you give. 'canvas' keeps the outline's canvas position (× unitsPerPixel), the older behaviour."),
|
|
3811
3942
|
caps: z.boolean().optional().describe('extrude_path: close the two ends. A capped extrude is a solid; an uncapped one is a ribbon.'),
|
|
3812
3943
|
segments: z.number().optional().describe('extrude_path: segments along the sweep · lathe_path: segments around the revolution.'),
|
|
3813
3944
|
arc: z.number().optional().describe('lathe_path: how far round to revolve, in degrees. 360 is a full solid of revolution; less leaves it open.'),
|
|
@@ -3834,7 +3965,7 @@ export const World3DInputSchema = z.object({
|
|
|
3834
3965
|
x: z.number().optional().describe('World-space x (default 0).'),
|
|
3835
3966
|
y: z.number().optional().describe('World-space y (default 3 — above the ground, not on it).'),
|
|
3836
3967
|
z: z.number().optional().describe('World-space z (default 0).'),
|
|
3837
|
-
color: z.array(z.number()).length(3).optional().describe('
|
|
3968
|
+
color: z.union([z.string(), z.array(z.number()).length(3)]).optional().describe('Hex ("#ffdca0") or linear RGB [r, g, b], each component ≥ 0 and may exceed 1 for a brighter light (default [1, 0.86, 0.62], a warm lamp).'),
|
|
3838
3969
|
intensity: z.number().optional().describe('Brightness (default 6).'),
|
|
3839
3970
|
range: z.number().optional().describe('How far the light reaches, in world units (default 20).'),
|
|
3840
3971
|
}).optional().describe('add_light / set_light: the light spec. At most 8 point lights exist at once — the shader array is fixed-size, so a ninth is refused by name rather than ignored.'),
|
|
@@ -3842,14 +3973,14 @@ export const World3DInputSchema = z.object({
|
|
|
3842
3973
|
// --- Materials ------------------------------------------------------------
|
|
3843
3974
|
material: z.object({
|
|
3844
3975
|
id: z.string().optional().describe('Material id (generated if omitted).'),
|
|
3845
|
-
color: z.array(z.number()).length(3).optional().describe('Base colour
|
|
3846
|
-
emissive: z.array(z.number()).length(3).optional().describe('Light the surface emits regardless of the scene (default [0, 0, 0]).'),
|
|
3976
|
+
color: z.union([z.string(), z.array(z.number()).length(3)]).optional().describe('Base colour: hex ("#e9e4d8") or linear RGB [r, g, b] in 0..1 (default [0.8, 0.8, 0.82]).'),
|
|
3977
|
+
emissive: z.union([z.string(), z.array(z.number()).length(3)]).optional().describe('Light the surface emits regardless of the scene: hex or [r, g, b] in 0..1 (default [0, 0, 0]).'),
|
|
3847
3978
|
metalness: z.number().min(0).max(1).optional().describe('0 dielectric → 1 metal.'),
|
|
3848
3979
|
roughness: z.number().min(0).max(1).optional().describe('0 mirror → 1 fully diffuse. Floored at 0.045: a true zero is a numerically unstable mirror.'),
|
|
3849
3980
|
emissiveIntensity: z.number().min(0).optional().describe('Scales `emissive` before the frame is built, so it reaches every path that draws emissive.'),
|
|
3850
3981
|
clearcoat: z.number().min(0).max(1).optional().describe('A lacquer layer over the base — car paint, varnished wood.'),
|
|
3851
3982
|
clearcoatRoughness: z.number().min(0).max(1).optional().describe("The coat's own roughness. NOT floored like the base, so a mirror-smooth lacquer stays expressible."),
|
|
3852
|
-
sheenColor: z.array(z.number()).length(3).optional().describe('Retroreflective sheen colour — cloth, velvet.'),
|
|
3983
|
+
sheenColor: z.union([z.string(), z.array(z.number()).length(3)]).optional().describe('Retroreflective sheen colour — cloth, velvet: hex or [r, g, b] in 0..1.'),
|
|
3853
3984
|
sheenRoughness: z.number().min(0).max(1).optional().describe('How broad the sheen lobe is.'),
|
|
3854
3985
|
}).optional().describe("add_material / set_material: a NAMED, SHARED surface referenced by many objects, so one edit restyles all of them. metalness and roughness reach the shader on the MESH path only. There are no aoMapIntensity / normalScale / envMapIntensity knobs: each scales a map that does not exist, and a knob that scales nothing is worse than a missing one."),
|
|
3855
3986
|
materialId: z.string().optional().describe('set_material / remove_material: the material id.'),
|
|
@@ -3860,8 +3991,8 @@ export const World3DInputSchema = z.object({
|
|
|
3860
3991
|
crossfade: z.number().optional().describe('set_mesh_clip: seconds to blend out of the clip that was playing. The blend happens in LOCAL pose space — a palette cannot be blended after the fact.'),
|
|
3861
3992
|
// --- Navigation, picking and the camera ----------------------------------
|
|
3862
3993
|
navTarget: z.enum(['2d', '3d']).optional().describe("set_nav_target: which layer the pointer drives — '2d' the Paper canvas, '3d' the world."),
|
|
3863
|
-
point: z.object({ x: z.number(), y: z.number(), z: z.number().optional() }).optional()
|
|
3864
|
-
.describe('
|
|
3994
|
+
point: z.object({ x: z.number(), y: z.number().optional(), z: z.number().optional() }).optional()
|
|
3995
|
+
.describe('The point to convert. ground_height: {x, z} in WORLD coordinates (the terrain height there; y is read as z if z is absent). canvas_to_ground: {x, y} in CANVAS pixels. world_to_canvas: {x, y, z?} in world coordinates.'),
|
|
3865
3996
|
origin: z.array(z.number()).length(3).optional().describe('raycast: ray origin in world space, [x, y, z].'),
|
|
3866
3997
|
direction: z.array(z.number()).length(3).optional().describe('raycast: ray direction in world space, [x, y, z]. Need not be normalised.'),
|
|
3867
3998
|
multiplier: z.number().optional().describe('dolly_camera: greater than 1 moves away, less than 1 moves closer.'),
|
|
@@ -3879,6 +4010,10 @@ export const World3DInputSchema = z.object({
|
|
|
3879
4010
|
.describe('set_mesh_instances: one entry per copy, six numbers each — x, y, z, scale, rotY (RADIANS), and a free variant a shader may read for per-instance colour or phase.'),
|
|
3880
4011
|
})
|
|
3881
4012
|
.refine((v) => v.action !== 'configure' || !!v.patch, { message: 'configure requires patch', path: ['patch'] })
|
|
4013
|
+
// ground_height reads a WORLD (x, z); y is accepted in z's place for older callers.
|
|
4014
|
+
.refine((v) => v.action !== 'ground_height' || (!!v.point && (v.point.z !== undefined || v.point.y !== undefined)), { message: 'ground_height requires point {x, z} (world coordinates)', path: ['point'] })
|
|
4015
|
+
.refine((v) => v.action !== 'canvas_to_ground' || (!!v.point && v.point.y !== undefined), { message: 'canvas_to_ground requires point {x, y} (canvas coordinates)', path: ['point'] })
|
|
4016
|
+
.refine((v) => v.action !== 'world_to_canvas' || (!!v.point && v.point.y !== undefined), { message: 'world_to_canvas requires point {x, y, z?} (world coordinates)', path: ['point'] })
|
|
3882
4017
|
.refine((v) => !['remove_actor', 'set_actor_pose'].includes(v.action) || !!v.actorId, { message: 'this action requires actorId', path: ['actorId'] })
|
|
3883
4018
|
.refine((v) => v.action !== 'set_actor_pose' || !!v.pose, { message: 'set_actor_pose requires pose', path: ['pose'] })
|
|
3884
4019
|
.refine((v) => v.action !== 'set_camera' || !!v.camera, { message: 'set_camera requires camera', path: ['camera'] })
|
|
@@ -4078,6 +4213,7 @@ export const SoundInputSchema = z.object({
|
|
|
4078
4213
|
from: z.number().positive().describe('Starting frequency in Hz.'),
|
|
4079
4214
|
tau: z.number().positive().max(5).describe('Glide time constant in seconds (max 5).'),
|
|
4080
4215
|
}).optional().describe("define_percussion / define_sfx: the pitch glide, which is what makes a kick a kick — the engine's own note is that the glide IS the drum. Omit it and a defined kick comes out a beep."),
|
|
4216
|
+
duration: z.number().positive().optional().describe('render_soundtrack: seconds to render (same as options.duration; this wins if both are given).'),
|
|
4081
4217
|
sampleRate: z.number().int().positive().optional().describe('render_soundtrack: samples per second (default 48000).'),
|
|
4082
4218
|
bitDepth: z.union([z.literal(16), z.literal(32)]).optional().describe('render_soundtrack: 16 (default) or 32-bit float.'),
|
|
4083
4219
|
text: z.string().optional().describe("from_text / play_from_text: a plain-language description, e.g. 'a soft warm bell on A4'. 'from_text' resolves it to a spec WITHOUT playing, so a caller can inspect or edit before committing."),
|
|
@@ -4119,7 +4255,7 @@ export const SoundInputSchema = z.object({
|
|
|
4119
4255
|
.refine((v) => !['define_percussion', 'define_sfx'].includes(v.action)
|
|
4120
4256
|
|| (v.partials?.length ?? 0) > 0 || (v.noise ?? 0) > 0, { message: 'this action needs a partial table or noise > 0 — with neither the sound is silent', path: ['noise'] })
|
|
4121
4257
|
// Bound by the scene, like every other timed export.
|
|
4122
|
-
.refine((v) => v.action !== 'render_soundtrack' || (v.options?.duration ?? 1) <= VIDEO_MAX_DURATION_S, { message: `render_soundtrack duration is capped at ${VIDEO_MAX_DURATION_S}s, the same ceiling as video`, path: ['options'] });
|
|
4258
|
+
.refine((v) => v.action !== 'render_soundtrack' || (v.duration ?? v.options?.duration ?? 1) <= VIDEO_MAX_DURATION_S, { message: `render_soundtrack duration is capped at ${VIDEO_MAX_DURATION_S}s, the same ceiling as video`, path: ['options'] });
|
|
4123
4259
|
/**
|
|
4124
4260
|
* pinepaper_motion — the generators' Animation knob, pointed at anything.
|
|
4125
4261
|
*
|