@pinepaper.studio/mcp-server 1.6.13 → 1.6.15
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 +6 -1
- package/README.es.md +6 -1
- package/README.fr.md +6 -1
- package/README.hi.md +6 -1
- package/README.ja.md +6 -1
- package/README.ko.md +6 -1
- package/README.md +245 -6
- package/README.pt-BR.md +6 -1
- package/README.zh-CN.md +6 -1
- package/assets/brand/pinepaper-lockup-horizontal-white.svg +1 -0
- package/assets/brand/pinepaper-lockup-horizontal.svg +1 -0
- package/assets/edit-whenever.svg +1 -1
- package/dist/browser/puppeteer-controller.js +2 -2
- package/dist/browser/puppeteer-controller.js.map +1 -1
- package/dist/build-info.json +5 -0
- package/dist/design/design-systems.js +1 -1
- package/dist/design/design-systems.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/ontology/upstream-vocabulary.d.ts +23 -0
- package/dist/ontology/upstream-vocabulary.d.ts.map +1 -0
- package/dist/ontology/upstream-vocabulary.js +1172 -0
- package/dist/ontology/upstream-vocabulary.js.map +1 -0
- package/dist/ontology/vocabulary.d.ts.map +1 -1
- package/dist/ontology/vocabulary.js +19 -0
- package/dist/ontology/vocabulary.js.map +1 -1
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +264 -70
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/engine-methods.d.ts.map +1 -1
- package/dist/tools/engine-methods.js +24 -2
- 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 +37 -10
- package/dist/tools/engine-surface.js.map +1 -1
- package/dist/tools/handlers/camera.d.ts.map +1 -1
- package/dist/tools/handlers/camera.js +17 -2
- package/dist/tools/handlers/camera.js.map +1 -1
- package/dist/tools/handlers/font.d.ts.map +1 -1
- package/dist/tools/handlers/font.js +48 -3
- package/dist/tools/handlers/font.js.map +1 -1
- package/dist/tools/handlers/maps.d.ts.map +1 -1
- package/dist/tools/handlers/maps.js +16 -0
- package/dist/tools/handlers/maps.js.map +1 -1
- package/dist/tools/handlers.d.ts +11 -1
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +426 -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 +2 -0
- package/dist/tools/minimal-descriptions.js.map +1 -1
- package/dist/tools/shape-params.d.ts +13 -2
- package/dist/tools/shape-params.d.ts.map +1 -1
- package/dist/tools/shape-params.js +98 -3
- package/dist/tools/shape-params.js.map +1 -1
- 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 +27 -1
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +2506 -273
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +1483 -239
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +259 -26
- package/dist/types/schemas.js.map +1 -1
- package/dist/utils/a11y.d.ts +67 -0
- package/dist/utils/a11y.d.ts.map +1 -0
- package/dist/utils/a11y.js +117 -0
- package/dist/utils/a11y.js.map +1 -0
- package/dist/utils/ad-package.d.ts +44 -0
- package/dist/utils/ad-package.d.ts.map +1 -0
- package/dist/utils/ad-package.js +121 -0
- package/dist/utils/ad-package.js.map +1 -0
- package/dist/utils/scc.d.ts +41 -0
- package/dist/utils/scc.d.ts.map +1 -0
- package/dist/utils/scc.js +185 -0
- package/dist/utils/scc.js.map +1 -0
- package/dist/utils/zip.d.ts +7 -0
- package/dist/utils/zip.d.ts.map +1 -0
- package/dist/utils/zip.js +82 -0
- package/dist/utils/zip.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +31 -1
- package/dist/version.js.map +1 -1
- package/icon.png +0 -0
- package/icon.svg +1 -0
- package/manifest.json +11 -2
- package/package.json +8 -4
package/dist/types/schemas.js
CHANGED
|
@@ -1038,7 +1038,10 @@ export const BlastParamsSchema = z.object({
|
|
|
1038
1038
|
// =============================================================================
|
|
1039
1039
|
// FILTER TYPES
|
|
1040
1040
|
// =============================================================================
|
|
1041
|
-
|
|
1041
|
+
// 'saturate' is what the published JSON schema offered for releases, while
|
|
1042
|
+
// the engine registers 'saturation' and this enum rejected 'saturate' — a
|
|
1043
|
+
// caller who followed the docs got a validation error. Accepted and mapped.
|
|
1044
|
+
export const FilterTypeSchema = z.preprocess((v) => (v === 'saturate' ? 'saturation' : v), z.enum([
|
|
1042
1045
|
'grayscale',
|
|
1043
1046
|
'sepia',
|
|
1044
1047
|
'blur',
|
|
@@ -1053,7 +1056,7 @@ export const FilterTypeSchema = z.enum([
|
|
|
1053
1056
|
'sharpen',
|
|
1054
1057
|
'emboss',
|
|
1055
1058
|
'posterize',
|
|
1056
|
-
]).describe('Type of visual filter to apply');
|
|
1059
|
+
])).describe('Type of visual filter to apply');
|
|
1057
1060
|
// =============================================================================
|
|
1058
1061
|
// TOOL INPUT SCHEMAS
|
|
1059
1062
|
// =============================================================================
|
|
@@ -1087,6 +1090,14 @@ export const AddFilterInputSchema = z.object({
|
|
|
1087
1090
|
params: z.record(z.unknown()).optional().default({}),
|
|
1088
1091
|
});
|
|
1089
1092
|
// Create Item
|
|
1093
|
+
// Item data flags. Advertised on create_item and modify_item for releases while
|
|
1094
|
+
// z.object() stripped them, so {data: {selectable: false}} was accepted and
|
|
1095
|
+
// did nothing. The engine reads all three off item.data.
|
|
1096
|
+
export const ItemDataFlagsSchema = z.object({
|
|
1097
|
+
selectable: z.boolean().optional(),
|
|
1098
|
+
isDraggable: z.boolean().optional(),
|
|
1099
|
+
isDecorative: z.boolean().optional(),
|
|
1100
|
+
});
|
|
1090
1101
|
export const CreateItemInputSchema = z.object({
|
|
1091
1102
|
itemType: ItemTypeSchema,
|
|
1092
1103
|
position: PositionSchema.optional().default({ x: 400, y: 300 }),
|
|
@@ -1096,6 +1107,11 @@ export const CreateItemInputSchema = z.object({
|
|
|
1096
1107
|
animationIntensity: z.number().optional().describe('Loop animation amplitude (0.1 = ±10%, default 0.15). Drives pulse/wobble/bounce/breathe amplitude; honored by SVG/SMIL/widget export.'),
|
|
1097
1108
|
animationDelay: z.number().optional().describe('Loop animation start delay in seconds.'),
|
|
1098
1109
|
keyframes: z.array(KeyframeSchema).optional().describe('Required when animationType is "keyframe". Inline keyframe array attached at creation.'),
|
|
1110
|
+
data: ItemDataFlagsSchema.optional(),
|
|
1111
|
+
// Also accepted at the top level: it names which point `position` is, so it
|
|
1112
|
+
// is read beside position as often as inside properties — and at the top
|
|
1113
|
+
// level it was stripped without a word (round 7 retest, 1.63).
|
|
1114
|
+
anchor: z.string().optional().describe("Which point position names: 'center' (default), 'top-left', 'top-right', 'bottom-left', 'bottom-right'. Same as properties.anchor."),
|
|
1099
1115
|
});
|
|
1100
1116
|
// Light direction for 3D effects
|
|
1101
1117
|
export const LightDirectionSchema = z.enum([
|
|
@@ -1132,6 +1148,7 @@ export const CreateDiagonalStripesInputSchema = z.object({
|
|
|
1132
1148
|
export const ModifyItemInputSchema = z.object({
|
|
1133
1149
|
itemId: z.string().describe('Registry ID of the item'),
|
|
1134
1150
|
properties: z.record(z.unknown()).describe('Properties to update'),
|
|
1151
|
+
data: ItemDataFlagsSchema.optional(),
|
|
1135
1152
|
});
|
|
1136
1153
|
// Delete Item
|
|
1137
1154
|
export const DeleteItemInputSchema = z.object({
|
|
@@ -1254,15 +1271,43 @@ export const ImportMotionCaptureInputSchema = z.object({
|
|
|
1254
1271
|
//
|
|
1255
1272
|
// Region (added FxTool c81781c): when set, the generator draws into a clipped
|
|
1256
1273
|
// sub-group translated to (x,y) sized (width × height). Region runs imply
|
|
1257
|
-
// preserve (won't wipe the canvas)
|
|
1258
|
-
//
|
|
1274
|
+
// preserve (won't wipe the canvas). An instance is generator + box, or an
|
|
1275
|
+
// explicit region.id (FxTool 5adfcf1c): the same box or id replaces, another
|
|
1276
|
+
// box adds an instance. (Before 5adfcf1c a second box silently replaced the
|
|
1277
|
+
// first — "drawWaves in a roundedRect draws nothing".) Invalid/zero-size
|
|
1259
1278
|
// regions fall back to full-canvas FxTool-side.
|
|
1279
|
+
//
|
|
1280
|
+
// Shape (FxTool c68524a2): the clip may be a rounded rect, ellipse, circle,
|
|
1281
|
+
// polygon, star, or an existing path's own geometry ({itemId}, whose bounds
|
|
1282
|
+
// are the box when none is given).
|
|
1283
|
+
export const GENERATOR_REGION_SHAPES = ['rect', 'roundedRect', 'ellipse', 'circle', 'polygon', 'star'];
|
|
1260
1284
|
export const GeneratorRegionSchema = z.object({
|
|
1261
|
-
x: z.number().describe('Region top-left X in canvas coords'),
|
|
1262
|
-
y: z.number().describe('Region top-left Y in canvas coords'),
|
|
1263
|
-
width: z.number().positive().describe('Region width in pixels'),
|
|
1264
|
-
height: z.number().positive().describe('Region height in pixels'),
|
|
1265
|
-
|
|
1285
|
+
x: z.number().optional().describe('Region top-left X in canvas coords'),
|
|
1286
|
+
y: z.number().optional().describe('Region top-left Y in canvas coords'),
|
|
1287
|
+
width: z.number().positive().optional().describe('Region width in pixels'),
|
|
1288
|
+
height: z.number().positive().optional().describe('Region height in pixels'),
|
|
1289
|
+
shape: z.union([z.enum(GENERATOR_REGION_SHAPES), z.object({ itemId: z.string().min(1) })]).optional()
|
|
1290
|
+
.describe("Clip shape: 'rect' (default), 'roundedRect' (+radius), 'ellipse', 'circle', 'polygon' (+sides), 'star' (+points, innerRatio), or {itemId} — clip to that path's geometry (the box defaults to its bounds)."),
|
|
1291
|
+
radius: z.number().min(0).optional().describe("roundedRect corner radius (default 20% of the shorter side)."),
|
|
1292
|
+
sides: z.number().int().min(3).max(64).optional().describe('polygon sides (default 6).'),
|
|
1293
|
+
points: z.number().int().min(3).max(64).optional().describe('star points (default 5).'),
|
|
1294
|
+
innerRatio: z.number().min(0.05).max(0.95).optional().describe('star inner radius as a fraction of the outer (default 0.5).'),
|
|
1295
|
+
id: z.string().trim().min(1).max(64).optional().describe('Names this region instance. Without it an instance is the generator plus its box: the same box replaces, a different box adds another. With it, re-running under the same id replaces that instance wherever its box now is.'),
|
|
1296
|
+
}).superRefine((r, ctx) => {
|
|
1297
|
+
const box = [r.x, r.y, r.width, r.height].filter((v) => v !== undefined).length;
|
|
1298
|
+
const fromItem = typeof r.shape === 'object';
|
|
1299
|
+
if (box !== 4 && !(fromItem && box === 0)) {
|
|
1300
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: fromItem
|
|
1301
|
+
? 'give all of x, y, width, height, or none of them (the region is then the item\'s bounds).'
|
|
1302
|
+
: 'a region needs x, y, width and height — unless shape is {itemId}, which takes the item\'s bounds.' });
|
|
1303
|
+
}
|
|
1304
|
+
const own = { radius: 'roundedRect', sides: 'polygon', points: 'star', innerRatio: 'star' };
|
|
1305
|
+
for (const [k, shape] of Object.entries(own)) {
|
|
1306
|
+
if (r[k] !== undefined && r.shape !== shape) {
|
|
1307
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: [k], message: `${k} applies to shape '${shape}' only.` });
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}).describe('Optional sub-region the generator draws into, clipped to a shape.');
|
|
1266
1311
|
export const ExecuteGeneratorInputSchema = z.object({
|
|
1267
1312
|
generatorName: GeneratorNameSchema,
|
|
1268
1313
|
params: z.record(z.unknown()).optional().default({}),
|
|
@@ -1321,8 +1366,13 @@ export const SetBackgroundColorInputSchema = z.object({
|
|
|
1321
1366
|
color: SolidColorSchema.describe('Background color to set (solid colors only)'),
|
|
1322
1367
|
});
|
|
1323
1368
|
export const SetCanvasSizeInputSchema = z.object({
|
|
1324
|
-
|
|
1325
|
-
|
|
1369
|
+
// 8192, not 4096: the artboard tool takes any size and a 5400x1350 carousel
|
|
1370
|
+
// panorama exported exactly, while this refused it. 8192 stays well inside
|
|
1371
|
+
// the browser's per-side canvas limit.
|
|
1372
|
+
// The refusal NAMES the route: a 728x90 banner refused with a bare "too
|
|
1373
|
+
// small" left a caller to export the preset instead (round 8 BB, 1.48).
|
|
1374
|
+
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(8192).describe('Canvas width (100-8192)'),
|
|
1375
|
+
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(8192).describe('Canvas height (100-8192)'),
|
|
1326
1376
|
preset: z.string().optional().describe('Optional preset name'),
|
|
1327
1377
|
});
|
|
1328
1378
|
// Export
|
|
@@ -1432,7 +1482,10 @@ export const SceneItemSchema = z.object({
|
|
|
1432
1482
|
/** Temporary ID for referencing in relations (e.g., "sun", "earth") */
|
|
1433
1483
|
name: z.string().describe('Reference name for this item (used in relations)'),
|
|
1434
1484
|
/** Item type to create */
|
|
1435
|
-
|
|
1485
|
+
// The canonical list, not a subset. This declared ten types while the tool
|
|
1486
|
+
// hand-built four and turned the other six into a 30px circle; now that it
|
|
1487
|
+
// routes through app.create, every type the engine has works here.
|
|
1488
|
+
itemType: ItemTypeSchema,
|
|
1436
1489
|
/** Position on canvas */
|
|
1437
1490
|
position: PositionSchema.optional(),
|
|
1438
1491
|
/** Item-specific properties */
|
|
@@ -1444,7 +1497,11 @@ export const SceneRelationSchema = z.object({
|
|
|
1444
1497
|
/** Reference name of the target item */
|
|
1445
1498
|
target: z.string().describe('Name of the item to relate to'),
|
|
1446
1499
|
/** Type of relation */
|
|
1447
|
-
|
|
1500
|
+
// THE DOCS LISTED ~90 AND THIS ACCEPTED 8. A caller following the relation
|
|
1501
|
+
// catalogue — grows_from, say — was rejected by validation before the call
|
|
1502
|
+
// was made, with nothing to suggest the tool simply carried a smaller list
|
|
1503
|
+
// than the documentation it shares. The catalogue is the contract.
|
|
1504
|
+
type: RelationTypeSchema,
|
|
1448
1505
|
/** Relation-specific parameters */
|
|
1449
1506
|
params: z.record(z.unknown()).optional().describe('Relation parameters (radius, speed, distance, etc.)'),
|
|
1450
1507
|
});
|
|
@@ -1455,6 +1512,7 @@ export const SceneAnimationSchema = z.object({
|
|
|
1455
1512
|
type: z.enum(['bounce', 'breathe', 'fade', 'glow', 'jelly', 'path', 'pulse', 'rotate', 'scrollDown', 'scrollLeft', 'scrollRight', 'scrollUp', 'shake', 'slideLeftRight', 'slideUpDown', 'swing', 'typewriter', 'wobble']),
|
|
1456
1513
|
/** Animation speed (default: 1.0) */
|
|
1457
1514
|
speed: z.number().optional(),
|
|
1515
|
+
startTime: z.number().min(0).optional().describe('Seconds before this animation begins. These are ambient LOOPS, so this staggers when each one starts rather than scheduling a one-shot — which is what makes a scene read as choreographed instead of everything moving at once from frame zero.'),
|
|
1458
1516
|
/** Animation parameters */
|
|
1459
1517
|
params: z.record(z.unknown()).optional().describe('Animation-specific parameters'),
|
|
1460
1518
|
});
|
|
@@ -1822,12 +1880,31 @@ export const AgentPlatformSchema = z.enum([
|
|
|
1822
1880
|
'web',
|
|
1823
1881
|
'print-a4',
|
|
1824
1882
|
'print-letter',
|
|
1883
|
+
// Landscape print (round 8 DD, 8.18): the engine has a4-landscape and
|
|
1884
|
+
// letter-landscape at 300 dpi, but no platform reached them, so a
|
|
1885
|
+
// landscape print job had no preset carrying the print DPI.
|
|
1886
|
+
'print-a4-landscape',
|
|
1887
|
+
'print-letter-landscape',
|
|
1825
1888
|
]).describe('Target platform for export');
|
|
1826
1889
|
/**
|
|
1827
1890
|
* Export format types
|
|
1828
1891
|
*/
|
|
1829
1892
|
export const AgentExportFormatSchema = z.enum([
|
|
1830
1893
|
'svg', 'png', 'gif', 'mp4', 'webm', 'pdf',
|
|
1894
|
+
// STILLS UNDER A BYTE BUDGET. Ad specs cap an image at ~150 KB, and PNG could
|
|
1895
|
+
// not get there. Rendered as the png path (region included) and re-encoded in
|
|
1896
|
+
// the page at the quality tier's compression. jpg has no alpha — see the tool text.
|
|
1897
|
+
'jpg', 'webp',
|
|
1898
|
+
// ANIMATION WITH FULL ALPHA (8.12): stepped PNG frames. webm cannot carry
|
|
1899
|
+
// alpha with the studio's muxer, and gif's transparency is 1-bit.
|
|
1900
|
+
'apng',
|
|
1901
|
+
// ADS (8.21 / 8.24): the studio's standalone widget page, wrapped for a
|
|
1902
|
+
// display network (a zip with ad.size + clickTag, and a backup image) or as
|
|
1903
|
+
// an MRAID playable (one HTML file, CTA through mraid.open).
|
|
1904
|
+
'html5-ad', 'playable',
|
|
1905
|
+
// CAPTIONS: text items staged with bornAt / ttl, as a subtitle file —
|
|
1906
|
+
// scc is CEA-608 (broadcast, 29.97 drop-frame).
|
|
1907
|
+
'srt', 'vtt', 'scc',
|
|
1831
1908
|
// AUDIO-ONLY. The soundtrack on its own, with no frames rendered — so
|
|
1832
1909
|
// platform dimensions, framing and quality do not apply to it, and no
|
|
1833
1910
|
// platform preset resolves to it. It has to be asked for by name.
|
|
@@ -1836,9 +1913,10 @@ export const AgentExportFormatSchema = z.enum([
|
|
|
1836
1913
|
/**
|
|
1837
1914
|
* Screenshot policy for agent jobs
|
|
1838
1915
|
*/
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1916
|
+
// 'never' is what the published JSON schema offered for releases while this
|
|
1917
|
+
// enum rejected it, so a caller who followed the docs got a validation error.
|
|
1918
|
+
// Accepted and read as 'none'.
|
|
1919
|
+
export const AgentScreenshotPolicySchema = z.preprocess((v) => (v === 'never' ? 'none' : v), z.enum(['none', 'on_error', 'on_complete', 'on_request'])).describe('When to take screenshots during agent job');
|
|
1842
1920
|
/**
|
|
1843
1921
|
* Start job input schema
|
|
1844
1922
|
*/
|
|
@@ -1861,6 +1939,10 @@ export const AgentStartJobInputSchema = z.object({
|
|
|
1861
1939
|
*/
|
|
1862
1940
|
export const AgentEndJobInputSchema = z.object({
|
|
1863
1941
|
takeScreenshot: z.boolean().optional().default(true).describe('Take final screenshot'),
|
|
1942
|
+
// The JSON schema has always published this name, and z.object() stripped it,
|
|
1943
|
+
// so includeScreenshot:false still took a screenshot. It wins when given —
|
|
1944
|
+
// see generateAgentEndJob.
|
|
1945
|
+
includeScreenshot: z.boolean().optional(),
|
|
1864
1946
|
analyzeContent: z.boolean().optional().default(true).describe('Analyze content for export recommendations'),
|
|
1865
1947
|
}).describe('Options for ending an agent job');
|
|
1866
1948
|
/**
|
|
@@ -1914,8 +1996,8 @@ export const AgentBatchOperationSchema = z.object({
|
|
|
1914
1996
|
generatorParams: z.record(z.unknown()).optional().describe('Generator parameters'),
|
|
1915
1997
|
generatorRegion: GeneratorRegionSchema.optional().describe('Optional sub-region {x, y, width, height} for execute_generator'),
|
|
1916
1998
|
// Set canvas size fields
|
|
1917
|
-
width: z.number().min(100).max(
|
|
1918
|
-
height: z.number().min(100).max(
|
|
1999
|
+
width: z.number().min(100).max(8192).optional().describe('Canvas width for set_canvas_size (100-8192)'),
|
|
2000
|
+
height: z.number().min(100).max(8192).optional().describe('Canvas height for set_canvas_size (100-8192)'),
|
|
1919
2001
|
preset: z.string().optional().describe('Canvas preset for set_canvas_size (e.g. instagram, youtube)'),
|
|
1920
2002
|
// Keyframe animate fields
|
|
1921
2003
|
keyframes: z.array(z.object({
|
|
@@ -2061,12 +2143,104 @@ export const AgentExportInputSchema = z.object({
|
|
|
2061
2143
|
framing: z.enum(['canvas', 'camera']).optional().default('canvas').describe('Output framing: "canvas" (full canvas, default) or "camera" (camera_animates first-keyframe viewport — fails if no walkthrough exists). Camera animation still drives motion within the fixed output frame.'),
|
|
2062
2144
|
duration: z.number().min(0.5).max(VIDEO_MAX_DURATION_S).optional().default(5).describe(`Video duration in seconds for animated formats (mp4/webm/gif). Default 5. Max ${VIDEO_MAX_DURATION_S} for mp4/webm, max ${GIF_MAX_DURATION_S} for gif (GIF is not codec-bounded, so file size scales with frames × dimensions, and it cannot stream to the export store). Static formats (png/svg/pdf) ignore this. Past roughly a minute the export is held in the studio's export store and paged back to a file rather than returned inline; a studio without that store refuses by name and says so.`),
|
|
2063
2145
|
estimateOnly: z.boolean().optional().default(false).describe('Preflight only: return the estimated file size AND what this scene would lose to this format, rendering nothing. Use before a long or high-quality export to check the size first. Modeled for mp4/webm/gif; png/pdf/svg return confidence "none" because no dimension-based model exists for them; wav is reported as "exact", because uncompressed PCM size is arithmetic rather than a guess.'),
|
|
2064
|
-
|
|
2065
|
-
|
|
2146
|
+
// NTSC rates (round 9 FF, 1.75): CTV / OTT spots require 29.97 and it was
|
|
2147
|
+
// refused as "Expected integer", though the engine exports true 30000/1001.
|
|
2148
|
+
// A rational string ('30000/1001') or the NTSC decimals snap to the exact
|
|
2149
|
+
// rational — 29.97 is 30000/1001, not the float 29.97, whose drift over a
|
|
2150
|
+
// spot is a dropped frame.
|
|
2151
|
+
fps: z.preprocess((v) => {
|
|
2152
|
+
if (typeof v === 'string') {
|
|
2153
|
+
const m = /^(\d+)\/(\d+)$/.exec(v.trim());
|
|
2154
|
+
if (m && Number(m[2]) > 0)
|
|
2155
|
+
return Number(m[1]) / Number(m[2]);
|
|
2156
|
+
const n = Number(v);
|
|
2157
|
+
return Number.isFinite(n) ? v === '' ? v : n : v;
|
|
2158
|
+
}
|
|
2159
|
+
return v;
|
|
2160
|
+
}, z.number().min(1).max(120).transform((n) => {
|
|
2161
|
+
const ntsc = { '23.976': 24000 / 1001, '29.97': 30000 / 1001, '59.94': 60000 / 1001, '119.88': 120000 / 1001 };
|
|
2162
|
+
return ntsc[(Math.round(n * 1000) / 1000).toString()] ?? ntsc[(Math.round(n * 100) / 100).toString()] ?? n;
|
|
2163
|
+
})).optional().describe('Video only: frames per second — an integer, an NTSC rate (23.976, 29.97, 59.94; snapped to the exact 24000/1001 …), or a rational string like "30000/1001". Overrides whatever `quality` implies. The tiers set it — draft 15, standard 30, high 60 — so asking for higher quality DOUBLES the frame count and the render time unless you say otherwise. File size is bitrate x duration and does not move with fps, but the picture gets fewer bits per frame at a higher one.'),
|
|
2164
|
+
scale: z.number().min(0.1).max(1).optional().describe('Video only: render at this fraction of the platform preset\'s dimensions (0.1-1). The engine derives its encode target from RESOLUTION unless bitrate is given, so this is the size control; halving the frame roughly quarters the pixels and the file. It is also the preview knob: scale 0.5 with quality "draft" is the fast look-check before committing to a full render. Rounded to even dimensions, which H.264 requires.'),
|
|
2066
2165
|
sampleRate: z.number().int().positive().optional().describe('wav only: samples per second (default 48000). Ignored by every other format.'),
|
|
2067
2166
|
bitDepth: z.union([z.literal(16), z.literal(32)]).optional().describe('wav only: 16 (default) or 32-bit float. Ignored by every other format.'),
|
|
2167
|
+
pdf: z.object({
|
|
2168
|
+
paperFormat: z.string().optional().describe("Paper size: 'a4', 'a3', 'letter', … or 'custom' (the canvas's own size, the default)."),
|
|
2169
|
+
orientation: z.enum(['portrait', 'landscape']).optional().describe('Default: from the canvas aspect.'),
|
|
2170
|
+
bleed: z.number().min(0).max(20).optional().describe('Bleed in mm; > 0 includes the bleed area.'),
|
|
2171
|
+
trimMarks: z.boolean().optional().describe('Add crop / trim marks.'),
|
|
2172
|
+
searchableText: z.boolean().optional().describe('An invisible text layer over the artwork, so the PDF is selectable, searchable and screen-readable (default true where the studio supports it; lines no font in the PDF can carry are left out and counted). false omits it.'),
|
|
2173
|
+
dpi: z.number().int().min(72).max(600).optional().describe('Rasterisation DPI (default: the quality tier\'s).'),
|
|
2174
|
+
pages: z.union([
|
|
2175
|
+
z.literal('scenes'),
|
|
2176
|
+
z.array(z.union([z.string(), z.object({ sceneId: z.string(), width: z.number().positive(), height: z.number().positive() })])).min(1),
|
|
2177
|
+
]).optional().describe("Multi-page: 'scenes' = one page per saved scene in timeline order, or a list of scene ids — or {sceneId, width, height} to give a page its canvas size (scenes do not record one)."),
|
|
2178
|
+
}).optional().describe('pdf only: print options.'),
|
|
2179
|
+
ad: z.object({
|
|
2180
|
+
clickUrl: z.string().url().optional().describe('html5-ad: the clickTag value for testing (networks replace it at serve time). playable: the store / landing URL the CTA opens — required.'),
|
|
2181
|
+
ctaItemId: z.string().min(1).optional().describe('The item whose bounds are the click target. html5-ad: optional (default: the whole ad). playable: required — a playable is interactive, so only the CTA clicks through. The hotspot is where the item is at export time; it does not follow an animated CTA.'),
|
|
2182
|
+
maxBytes: z.number().int().positive().optional().describe('Size budget for the upload (the zip for html5-ad, the HTML for playable). html5-ad defaults to 150000: Google Ads display HTML5 is 150 KB; some uploads allow 600 KB — set it to your network\'s figure. Over budget is a fidelity warning, not a refusal.'),
|
|
2183
|
+
}).optional().describe('html5-ad / playable options.'),
|
|
2184
|
+
transparent: z.boolean().optional().describe('apng: keep the alpha channel (the default; false fills the background colour). webm: true writes a VP9 alpha channel where the studio supports it (result.video.alpha confirms it).'),
|
|
2185
|
+
alphaQuantizer: z.number().int().min(0).max(63).optional().describe('webm with transparent: quality of the alpha stream, 0 = lossless … 63 (the studio\'s default otherwise).'),
|
|
2186
|
+
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.'),
|
|
2187
|
+
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.'),
|
|
2188
|
+
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.'),
|
|
2189
|
+
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).'),
|
|
2190
|
+
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.'),
|
|
2191
|
+
minBitrate: z.number().int().min(100_000).max(200_000_000).optional().describe('mp4 / webm: a floor under the target. If the achieved bitrate misses it, fidelity warns and names the re-encode a delivery spec needs.'),
|
|
2192
|
+
bitrateMode: z.enum(['constant', 'variable']).optional().describe("mp4 / webm: 'constant' (the default with broadcast) or 'variable'."),
|
|
2193
|
+
time: z.number().min(0).optional().describe('Stills only (png / jpg / webp / svg / pdf): render the scene at this time in seconds. Without it a still is taken at wherever the playhead happens to be, so two identical runs can differ.'),
|
|
2194
|
+
region: z.object({
|
|
2195
|
+
x: z.number(), y: z.number(),
|
|
2196
|
+
width: z.number().positive(), height: z.number().positive(),
|
|
2197
|
+
outputWidth: z.number().int().positive().max(8192).optional(),
|
|
2198
|
+
outputHeight: z.number().int().positive().max(8192).optional(),
|
|
2199
|
+
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.'),
|
|
2200
|
+
}).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.'),
|
|
2068
2201
|
}).describe('Smart export options')
|
|
2069
2202
|
.superRefine((val, ctx) => {
|
|
2203
|
+
if (val.time !== undefined && ['mp4', 'webm', 'gif', 'apng', 'wav', 'srt', 'vtt', 'scc'].includes(String(val.format))) {
|
|
2204
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['time'], message: "time picks the moment a STILL is taken; a video or audio export runs from 0 for its duration. Drop time, or export png / jpg / webp / svg / pdf." });
|
|
2205
|
+
}
|
|
2206
|
+
if (val.broadcast && val.format !== 'mp4') {
|
|
2207
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['broadcast'], message: 'broadcast is an mp4 setting (BT.709 H.264). Export format: "mp4".' });
|
|
2208
|
+
}
|
|
2209
|
+
if (val.broadcastHeadroom !== undefined && !val.broadcast) {
|
|
2210
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['broadcastHeadroom'], message: 'broadcastHeadroom applies only with broadcast: true (mp4).' });
|
|
2211
|
+
}
|
|
2212
|
+
for (const k of ['bitrate', 'minBitrate', 'bitrateMode']) {
|
|
2213
|
+
if (val[k] !== undefined && val.format !== 'mp4' && val.format !== 'webm') {
|
|
2214
|
+
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.` });
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
if (val.bitrate !== undefined && val.minBitrate !== undefined && val.minBitrate > val.bitrate) {
|
|
2218
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['minBitrate'], message: 'minBitrate is above bitrate; the floor would override the target. Drop one, or lower minBitrate.' });
|
|
2219
|
+
}
|
|
2220
|
+
if (val.ad !== undefined && val.format !== 'html5-ad' && val.format !== 'playable') {
|
|
2221
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['ad'], message: 'ad options apply to format "html5-ad" or "playable".' });
|
|
2222
|
+
}
|
|
2223
|
+
if (val.format === 'playable' && (!val.ad?.ctaItemId || !val.ad?.clickUrl)) {
|
|
2224
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['ad'], message: 'a playable needs ad.ctaItemId (the install button — only it clicks through, so the rest stays playable) and ad.clickUrl (the store URL it opens).' });
|
|
2225
|
+
}
|
|
2226
|
+
if (val.transparent !== undefined && val.format !== 'apng' && val.format !== 'webm') {
|
|
2227
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['transparent'], message: 'transparent applies to apng and webm: png keeps alpha already, and mp4 (H.264) / gif cannot carry it. For animation with alpha, export "webm" or "apng".' });
|
|
2228
|
+
}
|
|
2229
|
+
if (val.alphaQuantizer !== undefined && !(val.format === 'webm' && val.transparent === true)) {
|
|
2230
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['alphaQuantizer'], message: 'alphaQuantizer applies to a transparent webm (format "webm", transparent: true).' });
|
|
2231
|
+
}
|
|
2232
|
+
if (val.loop !== undefined && val.format !== 'gif' && val.format !== 'apng') {
|
|
2233
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['loop'], message: "loop is a gif / apng setting. A video cannot carry a loop flag — for a seamless loop, key t = 0 and t = duration to the same state and export exactly that duration." });
|
|
2234
|
+
}
|
|
2235
|
+
if (val.maxBytes !== undefined && val.format !== 'gif') {
|
|
2236
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['maxBytes'], message: "maxBytes is a gif budget. For mp4 / webm use scale and quality; for stills use jpg / webp with a lower quality." });
|
|
2237
|
+
}
|
|
2238
|
+
if (val.pdf !== undefined && val.format !== 'pdf') {
|
|
2239
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['pdf'], message: "pdf options apply only to format: 'pdf'." });
|
|
2240
|
+
}
|
|
2241
|
+
if (val.region !== undefined && !['png', 'jpg', 'webp'].includes(String(val.format))) {
|
|
2242
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['region'], message: "region applies only to a still — format 'png', 'jpg' or 'webp'. It renders one image of that part of the canvas." });
|
|
2243
|
+
}
|
|
2070
2244
|
// wav carries no picture, so the visual knobs are not merely ignored — a
|
|
2071
2245
|
// caller who set them believes something about the output that is not
|
|
2072
2246
|
// true. Said once, rather than silently dropped.
|
|
@@ -2519,6 +2693,7 @@ export const ImportImageInputSchema = z.object({
|
|
|
2519
2693
|
maxWidth: z.number().optional().describe('Maximum width constraint in pixels'),
|
|
2520
2694
|
maxHeight: z.number().optional().describe('Maximum height constraint in pixels'),
|
|
2521
2695
|
mask: ImageMaskSchema.optional().describe('Optional mask shape to apply to the image'),
|
|
2696
|
+
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'."),
|
|
2522
2697
|
}).describe('Import image input');
|
|
2523
2698
|
// =============================================================================
|
|
2524
2699
|
// ONTOLOGY SCHEMAS
|
|
@@ -2970,6 +3145,25 @@ export const AudioBeatsInputSchema = z.object({
|
|
|
2970
3145
|
sensitivity: z.number().optional(),
|
|
2971
3146
|
minGap: z.number().optional(),
|
|
2972
3147
|
});
|
|
3148
|
+
// ACCESSIBILITY CHECK: text contrast (WCAG 1.4.3) and flashing (2.3.1).
|
|
3149
|
+
export const AccessibilityCheckInputSchema = z.object({
|
|
3150
|
+
checks: z.array(z.enum(['contrast', 'flash'])).min(1).optional().describe("Which checks (default both)."),
|
|
3151
|
+
duration: z.number().positive().max(120).optional().describe('flash: seconds of the timeline to sample (default 5).'),
|
|
3152
|
+
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).'),
|
|
3153
|
+
});
|
|
3154
|
+
// RENDER BATCH (data-driven creative): one scene, many rows, one export per
|
|
3155
|
+
// row. The handler runs each row through modify_item / template_params and
|
|
3156
|
+
// then agent_export, so every per-tool check applies to every row.
|
|
3157
|
+
export const RenderBatchRowSchema = z.object({
|
|
3158
|
+
id: z.string().min(1).max(64).optional().describe('A name for this row, echoed in its result.'),
|
|
3159
|
+
changes: z.record(z.string(), z.record(z.unknown())).optional().describe('itemId → properties, applied with modify_item (content, fillColor, fit, …).'),
|
|
3160
|
+
template: z.object({ templateId: z.string().min(1), params: z.record(z.unknown()) }).optional().describe('Apply a template\'s params (template_params apply).'),
|
|
3161
|
+
}).refine((r) => r.changes !== undefined || r.template !== undefined, { message: 'a row needs changes and / or template.' });
|
|
3162
|
+
export const RenderBatchInputSchema = z.object({
|
|
3163
|
+
rows: z.array(RenderBatchRowSchema).min(1).max(100).describe('One entry per creative (max 100).'),
|
|
3164
|
+
export: z.record(z.unknown()).describe('agent_export options used for every row (format, platform, quality, duration, ad, …).'),
|
|
3165
|
+
estimateOnly: z.boolean().optional().describe('Validate the rows and return the plan without rendering.'),
|
|
3166
|
+
});
|
|
2973
3167
|
export const TemplateParamsInputSchema = z.object({
|
|
2974
3168
|
action: z.enum(['get', 'apply']),
|
|
2975
3169
|
templateId: z.string(),
|
|
@@ -3000,8 +3194,13 @@ export const ImageFilterInputSchema = z.object({
|
|
|
3000
3194
|
preserveShading: z.number().min(0).max(1).optional().describe('recolor_palette: how much of the original luminance detail survives (default 1).'),
|
|
3001
3195
|
});
|
|
3002
3196
|
export const LassoInputSchema = z.object({
|
|
3003
|
-
action: z.enum(['activate', 'apply']),
|
|
3197
|
+
action: z.enum(['cut', 'activate', 'apply']),
|
|
3004
3198
|
itemId: z.string().optional(),
|
|
3199
|
+
points: z.array(z.union([
|
|
3200
|
+
z.tuple([z.number(), z.number()]),
|
|
3201
|
+
z.object({ x: z.number(), y: z.number() }),
|
|
3202
|
+
])).optional(),
|
|
3203
|
+
keepOriginal: z.boolean().optional(),
|
|
3005
3204
|
});
|
|
3006
3205
|
export const CutoutStyleInputSchema = z.object({
|
|
3007
3206
|
action: z.enum(['apply', 'list']),
|
|
@@ -3124,6 +3323,11 @@ export const ExportWidgetInputSchema = z.object({
|
|
|
3124
3323
|
export const ExportWidgetHtmlInputSchema = z.object({
|
|
3125
3324
|
title: z.string().optional(),
|
|
3126
3325
|
download: z.boolean().optional(),
|
|
3326
|
+
// Accessibility / localisation (round 8 EE, 8.20): the engine writes
|
|
3327
|
+
// <html lang="en"> with no dir and no text alternative for the canvas.
|
|
3328
|
+
lang: z.string().regex(/^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/).optional().describe('BCP 47 language tag for the page, e.g. "ar", "hi", "zh-Hans".'),
|
|
3329
|
+
dir: z.enum(['ltr', 'rtl', 'auto']).optional().describe('Text direction for the page.'),
|
|
3330
|
+
alt: z.string().max(500).optional().describe('Text alternative for the animation: the canvas gets role="img" and this as its aria-label, and the page a meta description.'),
|
|
3127
3331
|
});
|
|
3128
3332
|
// =============================================================================
|
|
3129
3333
|
// DATA VISUALIZATION (CHARTS)
|
|
@@ -3270,7 +3474,11 @@ export const LintSceneInputSchema = z.object({
|
|
|
3270
3474
|
export const MediaInputSchema = z.object({
|
|
3271
3475
|
action: z.enum(['upload_video', 'upload_audio', 'list', 'remove', 'set_playback_rate', 'set_clip', 'set_time_remap', 'speed_ramp', 'match_cut', 'apply_track_matte', 'stop_live_matte'])
|
|
3272
3476
|
.describe("'upload_video' / 'upload_audio' (from a URL) · 'list' media · 'remove' by id · 'set_playback_rate' · 'set_clip' (re-trim) · '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'"),
|
|
3273
|
-
|
|
3477
|
+
// Not .url(): a bare absolute path is not a URL, and 48f3a2a made the server
|
|
3478
|
+
// read local files — so `/Users/…/broll1.mp4` was advertised and then rejected
|
|
3479
|
+
// by the schema before anything could read it. resolveMediaSource validates
|
|
3480
|
+
// what it is given and names the problem.
|
|
3481
|
+
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://).'),
|
|
3274
3482
|
id: z.string().optional().describe('Media id — required for remove / set_playback_rate / set_clip.'),
|
|
3275
3483
|
rate: z.number().min(0.25).max(4).optional().describe('Playback rate 0.25–4 — required for set_playback_rate.'),
|
|
3276
3484
|
inPoint: z.number().min(0).optional().describe('Clip in-point in media-time seconds — required for set_clip.'),
|
|
@@ -3513,10 +3721,20 @@ export const GameInputSchema = z.object({
|
|
|
3513
3721
|
rows: z.number().int().positive(),
|
|
3514
3722
|
cellSize: z.number().positive().optional().describe('World units per cell (default 1).'),
|
|
3515
3723
|
origin: PositionSchema.optional().describe('World position of cell (0,0).'),
|
|
3516
|
-
blocked: z.array(z.number()).optional().describe('
|
|
3724
|
+
blocked: z.array(z.number()).optional().describe('A MASK, not a list of indices: one entry per cell, cols*rows long, row-major, non-zero meaning blocked. The engine reads blocked[y * cols + x].'),
|
|
3517
3725
|
obstacles: z.array(z.object({ x: z.number(), y: z.number(), width: z.number(), height: z.number() }))
|
|
3518
3726
|
.optional().describe('World-space rects to block — pass item bounds and the walls block the board.'),
|
|
3519
|
-
}).optional().describe('pathfind: the board.')
|
|
3727
|
+
}).optional().describe('pathfind: the board.')
|
|
3728
|
+
// A MASK AND AN INDEX LIST LOOK IDENTICAL TO A TYPE CHECKER.
|
|
3729
|
+
//
|
|
3730
|
+
// `blocked` is read as blocked[y * cols + x], so passing the INDICES of the
|
|
3731
|
+
// blocked cells — the other obvious reading — blocks cells 0..n-1 by their
|
|
3732
|
+
// values instead, and the answer comes back as a plausible "no path" with
|
|
3733
|
+
// nothing wrong on the face of it. The length tells the two apart for free.
|
|
3734
|
+
.refine((g) => !g?.blocked || g.blocked.length === g.cols * g.rows, {
|
|
3735
|
+
message: 'grid.blocked must have exactly cols*rows entries — one per cell, non-zero meaning blocked. A list of blocked cell INDICES is a different thing and produces a wrong board rather than an error.',
|
|
3736
|
+
path: ['blocked'],
|
|
3737
|
+
}),
|
|
3520
3738
|
start: PositionSchema.optional().describe('pathfind: start, WORLD coordinates.'),
|
|
3521
3739
|
goal: PositionSchema.optional().describe('pathfind: goal, WORLD coordinates.'),
|
|
3522
3740
|
diagonal: z.boolean().optional().describe('pathfind: 8-way movement (default true). Never cuts corners.'),
|
|
@@ -3582,6 +3800,13 @@ export const World3DInputSchema = z.object({
|
|
|
3582
3800
|
segments: z.number().optional().describe('extrude_path: segments along the sweep · lathe_path: segments around the revolution.'),
|
|
3583
3801
|
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.'),
|
|
3584
3802
|
flatness: z.number().optional().describe('Curve flattening tolerance when the path is sampled into rings — smaller is finer and heavier.'),
|
|
3803
|
+
// 5.64: these were read by the generator (65a1a21) but not declared here,
|
|
3804
|
+
// so z.object() stripped them before it ran — every mesh came out the
|
|
3805
|
+
// default grey. Declared, and tested through the handler this time.
|
|
3806
|
+
color: z.union([z.string(), z.array(z.number())]).optional().describe('Mesh colour: hex or [r,g,b] (0..255 or 0..1). Default light grey.'),
|
|
3807
|
+
metalness: z.number().min(0).max(1).optional(),
|
|
3808
|
+
roughness: z.number().min(0).max(1).optional(),
|
|
3809
|
+
uniforms: z.record(z.string(), z.unknown()).optional().describe('Raw shader uniforms (diffuse, metalness, roughness …) — win over color / metalness / roughness.'),
|
|
3585
3810
|
x: z.number().optional().describe('Placement in world space.'),
|
|
3586
3811
|
y: z.number().optional().describe('Placement in world space (omit to let it sit where the terrain puts it).'),
|
|
3587
3812
|
z: z.number().optional().describe('Placement in world space.'),
|
|
@@ -3815,9 +4040,9 @@ export const SoundInputSchema = z.object({
|
|
|
3815
4040
|
'list_instruments', 'list_percussion', 'list_sfx',
|
|
3816
4041
|
'play_tone', 'play_chord', 'chord_frequencies', 'play_percussion', 'play_sfx',
|
|
3817
4042
|
'play_spec', 'from_text', 'play_from_text',
|
|
3818
|
-
'create', 'timbre_from_path', 'set_placement', 'remove', 'stop_all',
|
|
4043
|
+
'create', 'sequence', 'timbre_from_path', 'set_placement', 'remove', 'stop_all',
|
|
3819
4044
|
'define_instrument', 'define_percussion', 'define_sfx', 'render_soundtrack',
|
|
3820
|
-
]).describe("Catalogues: 'list_instruments' · 'list_percussion' · 'list_sfx'. Play: 'play_tone' · 'play_chord' · 'play_percussion' · 'play_sfx' · 'play_spec' · 'play_from_text'. Read without playing: 'chord_frequencies' · 'from_text' (a plain-language description resolved to a spec) · 'timbre_from_path'. Canvas: 'create' (a sound drawn AS a waveform path) · 'set_placement' · 'remove' · 'stop_all'. Define: 'define_instrument' · 'define_percussion' · 'define_sfx' — the six built-in instruments are a starting set, not a claim that music contains six; anything missing can be defined here and is then first-class, listed by the list_* actions and playable by name. Render: 'render_soundtrack' — mix every placed sound to a WAV file offline, with no Web Audio and no playback."),
|
|
4045
|
+
]).describe("Catalogues: 'list_instruments' · 'list_percussion' · 'list_sfx'. Play: 'play_tone' · 'play_chord' · 'play_percussion' · 'play_sfx' · 'play_spec' · 'play_from_text'. Read without playing: 'chord_frequencies' · 'from_text' (a plain-language description resolved to a spec) · 'timbre_from_path'. Canvas: 'create' (a sound drawn AS a waveform path) · 'sequence' (MANY cues in ONE call — a 112-cue music bed was 112 calls) · 'set_placement' · 'remove' · 'stop_all'. Define: 'define_instrument' · 'define_percussion' · 'define_sfx' — the six built-in instruments are a starting set, not a claim that music contains six; anything missing can be defined here and is then first-class, listed by the list_* actions and playable by name. Render: 'render_soundtrack' — mix every placed sound to a WAV file offline, with no Web Audio and no playback."),
|
|
3821
4046
|
note: z.string().optional().describe("play_tone: scientific pitch, e.g. 'A4' or 'C#3'."),
|
|
3822
4047
|
root: z.string().optional().describe("play_chord / chord_frequencies: the root note, e.g. 'C4'."),
|
|
3823
4048
|
chord: z.string().optional().default('major').describe("play_chord / chord_frequencies: the chord kind, e.g. 'major', 'minor', 'maj7', 'dim'. Call the engine rather than guessing at exotic names."),
|
|
@@ -3849,6 +4074,14 @@ export const SoundInputSchema = z.object({
|
|
|
3849
4074
|
itemId: z.string().optional().describe('timbre_from_path: the path whose SHAPE becomes the harmonic content. set_placement / remove: the sound item.'),
|
|
3850
4075
|
samples: z.number().int().positive().optional().describe('timbre_from_path: how many points to sample along the path (default 256). More is a finer timbre and a slower read.'),
|
|
3851
4076
|
placement: z.record(z.string(), z.unknown()).optional().describe('set_placement: where and when the sound sits on the timeline.'),
|
|
4077
|
+
cues: z.array(z.object({
|
|
4078
|
+
t: z.number().min(0).describe('When this cue sounds, in seconds.'),
|
|
4079
|
+
spec: z.record(z.string(), z.unknown()).optional().describe('The synthesis spec, as create takes it.'),
|
|
4080
|
+
preset: z.string().optional().describe("A named instrument, percussion or sfx instead of a spec — whatever the list_* actions report."),
|
|
4081
|
+
note: z.string().optional().describe("Pitch for a preset instrument, e.g. 'A4'."),
|
|
4082
|
+
duration: z.number().positive().optional().describe('Seconds this cue lasts.'),
|
|
4083
|
+
visual: z.record(z.string(), z.unknown()).optional().describe('Per-cue waveform drawing options, as create takes them.'),
|
|
4084
|
+
})).optional().describe('sequence: every cue in one call. Each is created and placed at its own t, so a music bed is ONE call rather than one per cue — a pilot spent 112 calls on a bed this expresses in a single array.'),
|
|
3852
4085
|
visual: z.object({
|
|
3853
4086
|
width: z.number().optional().describe('Waveform path width in canvas units (default 220).'),
|
|
3854
4087
|
height: z.number().optional().describe('Waveform path height in canvas units (default 64).'),
|