@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/tools/definitions.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/tools/definitions.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAoC/C,eAAO,MAAM,cAAc,6kbAyK+G,CAAC;AAW3I,eAAO,MAAM,eAAe,EAAE,IAAI,EAk0RjC,CAAC;AAEF;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAE5D;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,EAAE,CAEvC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,EAAE,CAO3D;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAE9D;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI,EAAE,CAQrE"}
|
|
@@ -11,7 +11,8 @@ import { COMPACT_DESCRIPTIONS } from './compact-descriptions.js';
|
|
|
11
11
|
import { MINIMAL_DESCRIPTIONS } from './minimal-descriptions.js';
|
|
12
12
|
import { STICK_GAITS, STICK_POSES, STICK_SEQUENCES, STICK_EXPRESSIONS, STICK_PROPS } from './stick-vocabulary.js';
|
|
13
13
|
import { GeneratorNameSchema, ItemTypeSchema } from '../types/schemas.js';
|
|
14
|
-
import {
|
|
14
|
+
import { DESIGN_MEDIA, DESIGN_MEDIA_APPLY } from './engine-lists.js';
|
|
15
|
+
import { EffectTypeSchema, SimpleAnimationTypeSchema, EasingSchema, KEYFRAME_EASINGS, EASING_DESCRIPTIONS, KEYFRAME_EASING_JSON_SCHEMA, GEOMETRY_OPERATIONS, THREAD_STITCHES, } from '../types/schemas.js';
|
|
15
16
|
/**
|
|
16
17
|
* Vocabulary lists derived from Zod schemas so tool descriptions stay in
|
|
17
18
|
* sync automatically. The description-schema-parity test
|
|
@@ -25,6 +26,7 @@ const SIMPLE_ANIMATION_TYPES_LIST = SimpleAnimationTypeSchema.options.join(', ')
|
|
|
25
26
|
// easeInCubic / easeOutCubic / easeOutBounce, which keyframe_animate rejects
|
|
26
27
|
// (round 8 DD, 1.68) — derived now, so it cannot say what the schema refuses.
|
|
27
28
|
const KEYFRAME_EASINGS_LIST = EasingSchema.options.join(', ');
|
|
29
|
+
const EASING_LINES = KEYFRAME_EASINGS.map((e) => `- ${e}: ${EASING_DESCRIPTIONS[e]}`).join('\n');
|
|
28
30
|
// =============================================================================
|
|
29
31
|
// AI AGENT GUIDE (exported for pinepaper_tool_guide)
|
|
30
32
|
// =============================================================================
|
|
@@ -261,12 +263,12 @@ COMMON PRESETS:
|
|
|
261
263
|
- hd-landscape: 1920x1080
|
|
262
264
|
- hd-portrait: 1080x1920
|
|
263
265
|
|
|
264
|
-
Max canvas size:
|
|
266
|
+
Max canvas size: 16384 per side (a 12000 px DOOH or projection canvas fits); above 8192 Safari / iOS may not render it, Chrome and Firefox do. For wedding invitations, event cards, or detailed designs, use at least 1080x1080 or larger.`,
|
|
265
267
|
inputSchema: {
|
|
266
268
|
type: 'object',
|
|
267
269
|
properties: {
|
|
268
|
-
width: { type: 'number', description: 'Canvas width in pixels (100-
|
|
269
|
-
height: { type: 'number', description: 'Canvas height in pixels (100-
|
|
270
|
+
width: { type: 'number', description: 'Canvas width in pixels (100-16384)' },
|
|
271
|
+
height: { type: 'number', description: 'Canvas height in pixels (100-16384)' },
|
|
270
272
|
preset: {
|
|
271
273
|
type: 'string',
|
|
272
274
|
description: 'Optional preset name',
|
|
@@ -732,6 +734,8 @@ Use the collage group's id for palette/collageStyle; use a child letter's id for
|
|
|
732
734
|
isDecorative: { type: 'boolean', description: 'Mark as decorative/non-interactive' },
|
|
733
735
|
},
|
|
734
736
|
},
|
|
737
|
+
atTime: { type: 'number', minimum: 0, description: 'AUTO-KEY: instead of a static edit, write these properties as keyframes at this time (seconds) — the same as a user dragging with auto-key on. Eased from the previous value; other keys and other properties\' curves are kept. "At 4 s make it orange" is { properties: { fillColor: "orange" }, atTime: 4 }.' },
|
|
738
|
+
easing: { type: 'string', description: 'atTime: easing of the segment arriving at the new key (default linear).' },
|
|
735
739
|
},
|
|
736
740
|
required: ['itemId', 'properties'],
|
|
737
741
|
},
|
|
@@ -1152,6 +1156,8 @@ EXAMPLES:
|
|
|
1152
1156
|
type: 'number',
|
|
1153
1157
|
description: 'Maximum height constraint in pixels',
|
|
1154
1158
|
},
|
|
1159
|
+
maxEdge: { anyOf: [{ type: 'integer', minimum: 1 }, { type: 'string', enum: ['native'] }], description: "The longest edge the studio STORES the image at (px); 'native' keeps every pixel. Default: the studio's cap, which follows the canvas on desktop. The result says when the image was stored smaller than its source." },
|
|
1160
|
+
nativeSize: { type: 'boolean', description: 'Place the image at its source footprint even if it was stored smaller.' },
|
|
1155
1161
|
mask: {
|
|
1156
1162
|
type: 'string',
|
|
1157
1163
|
enum: ['circle', 'rounded', 'hexagon', 'star'],
|
|
@@ -1729,6 +1735,49 @@ EXAMPLES:
|
|
|
1729
1735
|
required: ['action'],
|
|
1730
1736
|
},
|
|
1731
1737
|
},
|
|
1738
|
+
{
|
|
1739
|
+
name: 'pinepaper_beat_cuts',
|
|
1740
|
+
annotations: { title: 'Beat-Snapped Cuts', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1741
|
+
description: `Cut on the music: analyse a track's beats (or take beat times you already have), fit a beat grid, and return cut points on every beat, every bar, or every N beats — optionally splitting a video or audio clip at each one.
|
|
1742
|
+
|
|
1743
|
+
The song is the backbone of the edit: when the music is generated, cut the picture to its beats and bars and use the whole song. Do not trim the song to fit shots that were planned first.
|
|
1744
|
+
|
|
1745
|
+
- source: the music — an upload id, URL, data: URL or local file path. Or beats: [seconds] to skip the analysis.
|
|
1746
|
+
- every: 'beat' (default) | 'bar' | N beats. beatsPerBar (default 4). range: [from, to] seconds.
|
|
1747
|
+
- bpm: the tempo, if you know it (the grid's phase is still fitted to the music). Otherwise the studio's detection and its usual confusions (double, half, 3:4, 4:3 …) are scored against the music and the best is used; bpmHint: [min, max] keeps the choice in a range. The result lists the candidates, and says lowConfidence with a warning when the tempo is uncertain — pass bpm then.
|
|
1748
|
+
- clipId: split this clip at each cut, in order; cuts outside the clip are skipped and listed.
|
|
1749
|
+
|
|
1750
|
+
Returns cuts (seconds), the grid (bpm, phase, period, confidence) and, with clipId, the pieces' ids.`,
|
|
1751
|
+
inputSchema: {
|
|
1752
|
+
type: 'object',
|
|
1753
|
+
properties: {
|
|
1754
|
+
source: { type: 'string' },
|
|
1755
|
+
beats: { type: 'array', items: { type: 'number' } },
|
|
1756
|
+
bpm: { type: 'number' },
|
|
1757
|
+
bpmHint: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2, description: '[min, max] bpm to choose within.' },
|
|
1758
|
+
includeOnsets: { type: 'boolean', description: 'Also return the detected onsets (for diagnosing a tempo pick).' },
|
|
1759
|
+
every: { anyOf: [{ type: 'string', enum: ['beat', 'bar'] }, { type: 'integer', minimum: 1, maximum: 64 }] },
|
|
1760
|
+
beatsPerBar: { type: 'integer', minimum: 1, maximum: 16 },
|
|
1761
|
+
range: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2 },
|
|
1762
|
+
clipId: { type: 'string' },
|
|
1763
|
+
},
|
|
1764
|
+
},
|
|
1765
|
+
},
|
|
1766
|
+
{
|
|
1767
|
+
name: 'pinepaper_query_mutations',
|
|
1768
|
+
annotations: { title: 'Query Property Keyframes', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1769
|
+
description: `A property's values over time: the item's keyframe series ({t, frame, value, easing} per key), for one property or all, optionally within [from, to] seconds. The read side of modify_item { atTime } (auto-key): "what changed at 4 s?", "show the colour over time". It reads the item's keyframe track; it does not yet say WHO made each change or the value before it — that needs the studio's mutation log.`,
|
|
1770
|
+
inputSchema: {
|
|
1771
|
+
type: 'object',
|
|
1772
|
+
properties: {
|
|
1773
|
+
itemId: { type: 'string' },
|
|
1774
|
+
property: { type: 'string', description: 'One property; all animated properties when omitted.' },
|
|
1775
|
+
range: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2, description: '[from, to] seconds.' },
|
|
1776
|
+
fps: { type: 'number', description: 'For the frame numbers (default 30).' },
|
|
1777
|
+
},
|
|
1778
|
+
required: ['itemId'],
|
|
1779
|
+
},
|
|
1780
|
+
},
|
|
1732
1781
|
{
|
|
1733
1782
|
name: 'pinepaper_accessibility_check',
|
|
1734
1783
|
annotations: {
|
|
@@ -1750,7 +1799,7 @@ EXAMPLE: { checks: ["contrast", "flash"], duration: 6, fps: 20 }`,
|
|
|
1750
1799
|
type: 'object',
|
|
1751
1800
|
properties: {
|
|
1752
1801
|
checks: { type: 'array', items: { type: 'string', enum: ['contrast', 'flash'] }, description: 'Which checks (default both).' },
|
|
1753
|
-
duration: { type: 'number', exclusiveMinimum: 0, maximum: 120, description: '
|
|
1802
|
+
duration: { type: 'number', exclusiveMinimum: 0, maximum: 120, description: 'Seconds of the timeline to sample (default 5): flash samples it at fps; contrast at 0 s, inside each text lifetime, at keyframe times and every second.' },
|
|
1754
1803
|
fps: { type: 'integer', minimum: 10, maximum: 60, description: 'flash: samples per second (default 20).' },
|
|
1755
1804
|
},
|
|
1756
1805
|
},
|
|
@@ -1760,6 +1809,12 @@ EXAMPLE: { checks: ["contrast", "flash"], duration: 6, fps: 20 }`,
|
|
|
1760
1809
|
annotations: { title: 'Generate Image (cloud)', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
1761
1810
|
description: `Generate images with an AI model in the PinePaper cloud, and optionally place the first one on the canvas. CHARGED per output — price it first with pinepaper_generate_estimate (free).
|
|
1762
1811
|
|
|
1812
|
+
PLAN FIRST; GENERATE ONLY WHAT THE EDIT USES. Every output is paid in full whether or not it is used, so generate-then-trim is waste:
|
|
1813
|
+
- Plan the whole timeline (shots, lengths, roles) before the first paid call, and price the whole plan with pinepaper_generate_estimate.
|
|
1814
|
+
- Reuse before generating: an image you already generated is referenced by its ref "/scene-assets/<id>".
|
|
1815
|
+
- Video: choose each shot's length from the lengths the model allows (listModels; Veo takes 4, 6 or 8 s) and re-time the edit to them. Do not generate long and crop.
|
|
1816
|
+
- Music: the song is the backbone. Its length only loosely follows the request, so cut the picture to its beats and bars with pinepaper_beat_cuts and use the whole song, rather than trimming it to fit the picture.
|
|
1817
|
+
|
|
1763
1818
|
Needs a service-account key in PINEPAPER_API_KEY (pp_sa_…, render:create scope). It is an internal beta enabled per organisation: without it the result says "not enabled for your organisation", and nothing is charged. Every refusal says whether anything was charged.
|
|
1764
1819
|
|
|
1765
1820
|
- model or useCase: a model id from pinepaper_generate_estimate {listModels: true}, or a use case and the cloud picks its default (hero stills, plates, text in the image, character-consistent edits).
|
|
@@ -1802,7 +1857,7 @@ The result: assets [{assetId, ref, width, height}] — the sizes the files HAVE
|
|
|
1802
1857
|
{
|
|
1803
1858
|
name: 'pinepaper_generate_estimate',
|
|
1804
1859
|
annotations: { title: 'Estimate Generation (free)', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1805
|
-
description: `Free: price a pinepaper_generate call before making it, or list the models. { listModels: true } returns every model you may use with its price per output, use cases, licence notes and the defaults per use case; { model | useCase, input } returns the estimate for that request. Needs PINEPAPER_API_KEY, like pinepaper_generate.`,
|
|
1860
|
+
description: `Free: price a pinepaper_generate call before making it, or list the models. Estimate the WHOLE plan (every asset the edit needs, at the lengths it will use) before generating any of it, not call by call. { listModels: true } returns every model you may use with its price per output, use cases, licence notes and the defaults per use case; { model | useCase, input } returns the estimate for that request. Needs PINEPAPER_API_KEY, like pinepaper_generate.`,
|
|
1806
1861
|
inputSchema: {
|
|
1807
1862
|
type: 'object',
|
|
1808
1863
|
properties: {
|
|
@@ -2090,6 +2145,13 @@ By default returns a cheap per-frame hash + byte size (token-light), plus unique
|
|
|
2090
2145
|
|
|
2091
2146
|
Set includeDataUrls: true to also get each frame as a PNG data URL (large — only for a few frames).
|
|
2092
2147
|
|
|
2148
|
+
LOOK AT YOUR OWN FILM — sheet: one tiled image of many frames, saved to a file (filePath), plus a review rubric to score it against. Structure checks cannot see what a frame looks like; this can. Pick the frames with times (e.g. the music's beat times), or:
|
|
2149
|
+
- sheet.every + sheet.duration: one frame every N seconds.
|
|
2150
|
+
- sheet.strip {at, count?, step?}: consecutive frames around a moment (a transition, a stutter).
|
|
2151
|
+
- sheet.loopSeam + sheet.duration: the last frame beside the first.
|
|
2152
|
+
tileWidth defaults to 360 px, phone width: if a word cannot be read in its tile, it cannot be read on a phone. Then open the file, score review.criteria 1-10, and go through review.checklist.
|
|
2153
|
+
EXAMPLE: { sheet: { every: 1, duration: 15 } } → one ~15-tile JPEG.
|
|
2154
|
+
|
|
2093
2155
|
USE WHEN:
|
|
2094
2156
|
- Verifying an animation is deterministic / reproducible
|
|
2095
2157
|
- Checking whether a scene actually animates across a time range
|
|
@@ -2112,8 +2174,26 @@ EXAMPLE: { times: [0, 0.5, 1, 1.5, 2], seed: 42 }`,
|
|
|
2112
2174
|
type: 'boolean',
|
|
2113
2175
|
description: 'Include each frame as a PNG data URL (large). Default false → hashes only.',
|
|
2114
2176
|
},
|
|
2177
|
+
sheet: {
|
|
2178
|
+
type: 'object',
|
|
2179
|
+
description: 'Return ONE tiled image saved to a file, plus a critique rubric, instead of per-frame hashes. times is then optional.',
|
|
2180
|
+
properties: {
|
|
2181
|
+
every: { type: 'number', description: 'One frame every N seconds from 0 to duration.' },
|
|
2182
|
+
duration: { type: 'number', description: 'Scene length in seconds (for every and loopSeam).' },
|
|
2183
|
+
strip: {
|
|
2184
|
+
type: 'object',
|
|
2185
|
+
properties: { at: { type: 'number' }, count: { type: 'integer', minimum: 2, maximum: 24 }, step: { type: 'number' } },
|
|
2186
|
+
required: ['at'],
|
|
2187
|
+
description: 'Consecutive frames around a moment: count (default 8), step seconds apart (default 1/30).',
|
|
2188
|
+
},
|
|
2189
|
+
loopSeam: { type: 'boolean', description: 'The last frame beside the first.' },
|
|
2190
|
+
tileWidth: { type: 'integer', minimum: 120, maximum: 1080, description: 'Tile width in px (default 360, phone width).' },
|
|
2191
|
+
columns: { type: 'integer', minimum: 1, maximum: 12 },
|
|
2192
|
+
format: { type: 'string', enum: ['jpeg', 'png'], description: 'Default jpeg (small).' },
|
|
2193
|
+
labels: { type: 'boolean', description: 'Stamp each tile with its time (default true).' },
|
|
2194
|
+
},
|
|
2195
|
+
},
|
|
2115
2196
|
},
|
|
2116
|
-
required: ['times'],
|
|
2117
2197
|
},
|
|
2118
2198
|
},
|
|
2119
2199
|
{
|
|
@@ -2128,7 +2208,7 @@ EXAMPLE: { times: [0, 0.5, 1, 1.5, 2], seed: 42 }`,
|
|
|
2128
2208
|
description: `Bring VIDEO and AUDIO onto the canvas from a URL, and control playback. Uploaded video is a first-class canvas item (drag, scale, animate, export like any shape); audio rides the timeline.
|
|
2129
2209
|
|
|
2130
2210
|
ACTIONS:
|
|
2131
|
-
- upload_video: { url (required), position?, scale
|
|
2211
|
+
- upload_video: { url (required), position?, fit? ('contain' default | 'cover' | 'none' = source pixels), scale? (a multiplier on that fit — with fit 'none', on the source pixels), timeOffset? (timeline start, s) or atPlayhead?: true (start at the current playback time), clipInPoint?, clipOutPoint? (trim, s) } → { id, duration, width, height, name }
|
|
2132
2212
|
- upload_audio: { url (required), volume? (0–1, default 1), loop?, muted?, timeOffset? or atPlayhead? } → { id, registryId, duration, name }. Pass loop EXPLICITLY — loop:true for a music bed, loop:false for a voice-over or sting; when omitted, the studio's own default applies and differs between builds.
|
|
2133
2213
|
- list: → all media [{ id, kind, duration, … }]
|
|
2134
2214
|
- remove: { id } → removed boolean
|
|
@@ -2149,11 +2229,15 @@ VIDEO EDITING:
|
|
|
2149
2229
|
- apply_track_matte: { id, matteItemId, channel? (luma|alpha|red), invert?, strength?, hideMatte?, live? } — the matte item's pixels drive the target's alpha. The classic: a headline filled with footage. live: true keeps the cut tracking an ANIMATING matte (kinetic mask reveal); otherwise it bakes once, destructively (recorded in data.filterChain for round-trip).
|
|
2150
2230
|
- stop_live_matte: { id } — stop a live matte, keep the last cut.
|
|
2151
2231
|
|
|
2232
|
+
MAKE ONE LIKE THIS — a reference video's FORMAT, never its content:
|
|
2233
|
+
- analyze_reference: { id, thumbnails? } — the uploaded reference clip (upload_video it first) → cuts (to the frame), shots (length, colours), pacing, beat, and card: the same facts as plain text. Read the card before planning.
|
|
2234
|
+
- remake_from_reference: { texts, duration?, clear? } — builds a new piece on the last analysed reference: one card per shot, cut at its cut times, in each shot's colours, the headline popping on its beats. texts are YOUR words, one per shot. Nothing of the footage, its words or its sound is copied; the reference clip stays in the project muted. One undo step.
|
|
2235
|
+
|
|
2152
2236
|
EXAMPLE: { action: 'upload_video', url: 'https://…/clip.mp4', scale: 0.5, timeOffset: 1 }`,
|
|
2153
2237
|
inputSchema: {
|
|
2154
2238
|
type: 'object',
|
|
2155
2239
|
properties: {
|
|
2156
|
-
action: { type: 'string', 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'], description: "Media action" },
|
|
2240
|
+
action: { type: 'string', 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'], description: "Media action" },
|
|
2157
2241
|
transition: { type: 'object', properties: { type: { type: 'string', enum: ['crossfade', 'dip'] }, seconds: { type: 'number', exclusiveMinimum: 0, maximum: 10 }, color: { type: 'string' } }, description: "add_transition: { type ('crossfade' default | 'dip'), seconds, color (dip) }." },
|
|
2158
2242
|
atPlayhead: { type: 'boolean', description: 'upload_video / upload_audio: start at the current playback time (instead of timeOffset).' },
|
|
2159
2243
|
remapTrack: { type: 'array', items: { type: 'object', properties: { time: { type: 'number' }, value: { type: 'number' }, easing: { type: 'string' } }, required: ['time', 'value'] }, description: 'set_time_remap: canvas-seconds → source-seconds curve (≥2 points). null clears the remap.' },
|
|
@@ -2173,12 +2257,17 @@ EXAMPLE: { action: 'upload_video', url: 'https://…/clip.mp4', scale: 0.5, time
|
|
|
2173
2257
|
hideMatte: { type: 'boolean', description: 'apply_track_matte: hide the matte item after applying.' },
|
|
2174
2258
|
live: { type: 'boolean', description: 'apply_track_matte: keep re-cutting as the matte moves/animates — the kinetic mask reveal. Omitted = bake once, destructively.' },
|
|
2175
2259
|
url: { type: 'string', description: "Required for upload_video / upload_audio: an http(s) URL, a data: URL, or a PATH TO A LOCAL FILE. Remote URLs and local files are fetched/read by the server (the page may not fetch third-party hosts), so stock music and SFX links work; the HTTP status or content-type is named if one does not. Up to 48 MB." },
|
|
2176
|
-
id: { type: 'string', description: 'Media id — required for remove / set_playback_rate / set_clip.' },
|
|
2260
|
+
id: { type: 'string', description: 'Media id — required for remove / set_playback_rate / set_clip / analyze_reference.' },
|
|
2261
|
+
texts: { type: 'array', items: { type: 'string' }, description: 'remake_from_reference: your words, one per shot of the reference.' },
|
|
2262
|
+
duration: { type: 'number', description: 'remake_from_reference: total seconds (default the reference\'s own).' },
|
|
2263
|
+
clear: { type: 'boolean', description: 'remake_from_reference: empty the canvas first (media survives).' },
|
|
2264
|
+
thumbnails: { type: 'boolean', description: 'analyze_reference: include a small image per shot (default false; token-heavy).' },
|
|
2177
2265
|
rate: { type: 'number', description: 'Playback rate 0.25–4 — set_playback_rate.' },
|
|
2178
2266
|
inPoint: { type: 'number', description: 'Clip in-point (media-time s) — set_clip.' },
|
|
2179
2267
|
outPoint: { type: 'number', description: 'Clip out-point (media-time s, > inPoint) — set_clip.' },
|
|
2180
2268
|
position: { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } }, description: 'Canvas position — upload_video.' },
|
|
2181
|
-
scale: { type: 'number', description: '
|
|
2269
|
+
scale: { type: 'number', description: "upload_video: a multiplier on the fit (contain by default), not on the source pixels; with fit: 'none', of the source pixels." },
|
|
2270
|
+
fit: { type: 'string', enum: ['contain', 'cover', 'none'], description: "upload_video: size before scale — 'contain' (default), 'cover', or 'none' (source pixels)." },
|
|
2182
2271
|
timeOffset: { type: 'number', description: 'Timeline start offset (s) — upload_video / upload_audio; set_clip: move the clip to start here.' },
|
|
2183
2272
|
clipInPoint: { type: 'number', description: 'Trim in-point (s) — upload_video.' },
|
|
2184
2273
|
clipOutPoint: { type: 'number', description: 'Trim out-point (s) — upload_video.' },
|
|
@@ -2263,9 +2352,10 @@ READ THIS BEFORE PROMISING A MEDIUM. Naming a medium does not create its marks.
|
|
|
2263
2352
|
Call resolve before telling a user you will render in a medium. "yes, but as an impression" is the true answer for half of them, and a flat approximation misrepresents a medium rather than approximating it.
|
|
2264
2353
|
|
|
2265
2354
|
ACTIONS:
|
|
2355
|
+
- apply: { itemId, medium, options? } — render an item in ANY medium the engine can make: ${Object.keys(DESIGN_MEDIA_APPLY).join(', ')}. One engine entry point; a medium it cannot make (encaustic, absent here) or that is a brush rather than a fill (charcoal, ink) is refused with the reason: pass it on. watercolor options: color, brush, water 0..1, opacity, edge 0..1 (the dark rim), angle, bleed, seed. oil (stylised): color, brush, length, density, toneRange, relief, angle, seed. cutPaper (vector: survives SVG/PDF/video): color, facet, jitter, shadow, shadowColor, shadowOpacity, shadowOffset [dx, dy], seed. The result replaces the source in the stacking order and hides it; applying again replaces; one undo step.
|
|
2266
2356
|
- list_media: every medium with fidelity, definition and limitation. Read it rather than assuming a fixed set — the catalogue grows (hatch and watercolor are recent additions).
|
|
2267
2357
|
- resolve: { medium } → { ok, fidelity, reason, markMaker }. ok:false with a reason is a real answer — pass it on rather than trying anyway.
|
|
2268
|
-
- list_stitches: the
|
|
2358
|
+
- list_stitches: the thread stitches with their params.
|
|
2269
2359
|
- apply_thread: render an item in thread. Its own silhouette is the region and its own fill is the thread colour, so an existing drawing becomes stitched rather than needing to be redrawn. The source is HIDDEN, not destroyed.
|
|
2270
2360
|
- apply_hatch: rule an item with hatching. Same deal — silhouette is the region, source is hidden, re-hatching replaces.
|
|
2271
2361
|
- list_flow_fields: the 7 fields that bend a hatch line.
|
|
@@ -2295,11 +2385,12 @@ EXAMPLE — a stitched leaf:
|
|
|
2295
2385
|
inputSchema: {
|
|
2296
2386
|
type: 'object',
|
|
2297
2387
|
properties: {
|
|
2298
|
-
action: { type: 'string', enum: ['list_media', 'resolve', 'list_stitches', 'apply_thread', 'apply_hatch', 'list_flow_fields', 'list_hatch_options'], description: 'Medium operation' },
|
|
2299
|
-
medium: { type: 'string', description:
|
|
2300
|
-
|
|
2388
|
+
action: { type: 'string', enum: ['list_media', 'resolve', 'list_stitches', 'apply_thread', 'apply_hatch', 'list_flow_fields', 'list_hatch_options', 'apply'], description: 'Medium operation' },
|
|
2389
|
+
medium: { type: 'string', enum: [...DESIGN_MEDIA], description: `resolve / apply: the medium. apply works for ${Object.keys(DESIGN_MEDIA_APPLY).join(', ')}; the rest are refused by the engine with the reason. Call list_media for each one's fidelity and limitation.` },
|
|
2390
|
+
options: { type: 'object', description: 'apply: the medium\'s own options (see the description; color takes hex).' },
|
|
2391
|
+
itemId: { type: 'string', description: 'apply / apply_thread / apply_hatch: a closed path, compound path (holes kept), or a group of them (applied per path).' },
|
|
2301
2392
|
stitch: { type: 'string', enum: [...THREAD_STITCHES], description: "apply_thread: default longAndShort. These six are the stitches the engine publishes; call 'list_stitches' for each one's description and its own parameters. An unknown name is refused rather than quietly stitched as a default fill." },
|
|
2302
|
-
field: { type: 'object', description: "apply_thread: { kind: 'radial'|'spine'|'constant', cx, cy, angle, spine[], across } — default radial from the shape centre." },
|
|
2393
|
+
field: { type: 'object', description: "apply_thread: { kind: 'radial'|'spine'|'constant', cx, cy, angle, spine[], across } — default radial from the shape centre. spine is a list of points, each {x, y} or [x, y], e.g. [{x:400,y:200},{x:400,y:520}]; this tool normalises [x, y] to {x, y} before the engine sees it." },
|
|
2303
2394
|
stitchLen: { type: 'number', description: 'apply_thread: nominal stitch length px (default 18).' },
|
|
2304
2395
|
rowGap: { type: 'number', description: 'apply_thread: row spacing px; defaults from thread width so rows abut.' },
|
|
2305
2396
|
variance: { type: 'number', description: 'apply_thread: length jitter 0..1 (default 0.35) — the long-and-short shading.' },
|
|
@@ -2548,6 +2639,13 @@ RECIPE — a playable maze: create_tilemap with wall fills → batch_create colo
|
|
|
2548
2639
|
},
|
|
2549
2640
|
{
|
|
2550
2641
|
name: 'pinepaper_design_system',
|
|
2642
|
+
annotations: {
|
|
2643
|
+
title: 'Design System',
|
|
2644
|
+
readOnlyHint: false,
|
|
2645
|
+
destructiveHint: false,
|
|
2646
|
+
idempotentHint: false,
|
|
2647
|
+
openWorldHint: false,
|
|
2648
|
+
},
|
|
2551
2649
|
description: `The design vocabulary, as data you can read — and eighteen styles that can build a scene.
|
|
2552
2650
|
|
|
2553
2651
|
TWO DIFFERENT THINGS, kept apart on purpose:
|
|
@@ -2584,6 +2682,13 @@ A partial composition reports as a failure with the elements that did not create
|
|
|
2584
2682
|
},
|
|
2585
2683
|
{
|
|
2586
2684
|
name: 'pinepaper_stick',
|
|
2685
|
+
annotations: {
|
|
2686
|
+
title: 'Stick Figure Kit',
|
|
2687
|
+
readOnlyHint: false,
|
|
2688
|
+
destructiveHint: false,
|
|
2689
|
+
idempotentHint: false,
|
|
2690
|
+
openWorldHint: false,
|
|
2691
|
+
},
|
|
2587
2692
|
description: `The vendored stick-figure kit — a rigged figure posed, walking, travelling, holding a prop, with a garment and hair, plus the set it stands on and among.
|
|
2588
2693
|
|
|
2589
2694
|
A different construction from pinepaper_character, which places a figure from the DESIGN GRAPH by concept ("pp:Pigeon"). This is the stick kit specifically.
|
|
@@ -2649,6 +2754,13 @@ The geometry is vendored from mcp-cloud, which makes this a three-repo artifact:
|
|
|
2649
2754
|
},
|
|
2650
2755
|
{
|
|
2651
2756
|
name: 'pinepaper_story',
|
|
2757
|
+
annotations: {
|
|
2758
|
+
title: 'Story to Scene',
|
|
2759
|
+
readOnlyHint: false,
|
|
2760
|
+
destructiveHint: false,
|
|
2761
|
+
idempotentHint: false,
|
|
2762
|
+
openWorldHint: false,
|
|
2763
|
+
},
|
|
2652
2764
|
description: `A piece of prose becomes a scene.
|
|
2653
2765
|
|
|
2654
2766
|
- distill: reduce an article to its story beats and draw NOTHING. This is the half worth having on its own — a caller can read what the distiller made of the text, edit the beats, and only then assemble. Discovering the reading by looking at a finished scene is the expensive way round.
|
|
@@ -2669,6 +2781,13 @@ The geometry is vendored from mcp-cloud, which makes this a three-repo artifact:
|
|
|
2669
2781
|
},
|
|
2670
2782
|
{
|
|
2671
2783
|
name: 'pinepaper_interchange',
|
|
2784
|
+
annotations: {
|
|
2785
|
+
title: 'Interchange Formats',
|
|
2786
|
+
readOnlyHint: false,
|
|
2787
|
+
destructiveHint: false,
|
|
2788
|
+
idempotentHint: false,
|
|
2789
|
+
openWorldHint: false,
|
|
2790
|
+
},
|
|
2672
2791
|
description: `The formats other tools read and write — Lottie, dotLottie, GLB, BVH, PNG sequence.
|
|
2673
2792
|
|
|
2674
2793
|
pinepaper_agent_export covers the PLATFORM formats: png, svg, mp4, webm, gif, pdf, sized for Instagram or YouTube. These are the interchange ones — a Lottie an app plays, a GLB a 3D tool opens, a BVH a rig imports, a PNG sequence an editor ingests. Import is here too, because a format you can only write is half a bridge.
|
|
@@ -2687,6 +2806,13 @@ Three of these refuse quietly in the engine: exportGLB with no perspective objec
|
|
|
2687
2806
|
},
|
|
2688
2807
|
{
|
|
2689
2808
|
name: 'pinepaper_sound',
|
|
2809
|
+
annotations: {
|
|
2810
|
+
title: 'Sound Synthesis & Soundtrack',
|
|
2811
|
+
readOnlyHint: false,
|
|
2812
|
+
destructiveHint: false,
|
|
2813
|
+
idempotentHint: false,
|
|
2814
|
+
openWorldHint: false,
|
|
2815
|
+
},
|
|
2690
2816
|
description: `Synthesis — and the drawing that is the same object.
|
|
2691
2817
|
|
|
2692
2818
|
The engine carries a full audio graph, and none of it was reachable. pinepaper_audio_beats ANALYSES audio that already exists; this MAKES audio.
|
|
@@ -2720,6 +2846,7 @@ render_soundtrack mixes every placed sound to a WAV and writes it to a file. It
|
|
|
2720
2846
|
noiseFreq: { type: 'number', description: 'define_percussion / define_sfx: noise band centre in Hz.' },
|
|
2721
2847
|
hz: { type: 'number', description: 'define_percussion / define_sfx: base frequency in Hz.' },
|
|
2722
2848
|
pitch: { type: 'object', description: 'define_percussion / define_sfx: { from, tau } pitch glide. The glide IS the drum — omit it and a kick comes out a beep.' },
|
|
2849
|
+
duration: { type: 'number', description: 'render_soundtrack: seconds to render (options.duration also works; this wins if both are given). Without either, the studio uses the timeline length (3 s when there is none).' },
|
|
2723
2850
|
sampleRate: { type: 'number', description: 'render_soundtrack: samples per second (default 48000).' },
|
|
2724
2851
|
bitDepth: { type: 'number', enum: [16, 32], description: 'render_soundtrack: 16 (default) or 32-bit float.' },
|
|
2725
2852
|
note: { type: 'string', description: "play_tone: scientific pitch, e.g. 'A4'." },
|
|
@@ -2739,6 +2866,13 @@ render_soundtrack mixes every placed sound to a WAV and writes it to a file. It
|
|
|
2739
2866
|
},
|
|
2740
2867
|
{
|
|
2741
2868
|
name: 'pinepaper_motion',
|
|
2869
|
+
annotations: {
|
|
2870
|
+
title: 'Group & Field Motion',
|
|
2871
|
+
readOnlyHint: false,
|
|
2872
|
+
destructiveHint: false,
|
|
2873
|
+
idempotentHint: false,
|
|
2874
|
+
openWorldHint: false,
|
|
2875
|
+
},
|
|
2742
2876
|
description: `The generators' Animation knob, pointed at anything — the same motion engine every generator's own animation runs on, reachable for any group or list of items.
|
|
2743
2877
|
|
|
2744
2878
|
Two kinds, and the second is the one nothing else here can do:
|
|
@@ -2770,6 +2904,13 @@ This is not pinepaper_animate: that applies a loop preset to ONE item. This move
|
|
|
2770
2904
|
},
|
|
2771
2905
|
{
|
|
2772
2906
|
name: 'pinepaper_path',
|
|
2907
|
+
annotations: {
|
|
2908
|
+
title: 'Path Operations',
|
|
2909
|
+
readOnlyHint: false,
|
|
2910
|
+
destructiveHint: true,
|
|
2911
|
+
idempotentHint: false,
|
|
2912
|
+
openWorldHint: false,
|
|
2913
|
+
},
|
|
2773
2914
|
description: `Destructive path operations — the ones that REPLACE or ADD items rather than restyling them.
|
|
2774
2915
|
|
|
2775
2916
|
- boolean: unite | subtract | intersect | exclude | divide. CONSUMES its operands and leaves one result, so the input ids stop existing. The first id is the base; the rest apply to it in order.
|
|
@@ -2832,7 +2973,7 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
|
|
|
2832
2973
|
anyOf: [{ type: 'boolean' }, { type: 'object' }],
|
|
2833
2974
|
description: 'create: include the walkable character (boolean or config).',
|
|
2834
2975
|
},
|
|
2835
|
-
patch: { type: 'object', description: 'configure: partial world spec, validated against describe.' },
|
|
2976
|
+
patch: { type: 'object', description: 'configure: partial world spec, validated against describe. 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.' },
|
|
2836
2977
|
actorId: { type: 'string', description: 'Actor id.' },
|
|
2837
2978
|
x: { type: 'number', description: 'add_actor: world x.' },
|
|
2838
2979
|
z: { type: 'number', description: 'add_actor: world z.' },
|
|
@@ -2843,18 +2984,18 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
|
|
|
2843
2984
|
camera: { type: 'object', description: "set_camera: { mode: follow|fixed|orbit, target?, radius?, speed?, eye?, lookAt? }." },
|
|
2844
2985
|
object: { type: 'object', description: 'add_object: { x, z, height?, color?, y? }. metalness/roughness are accepted and IGNORED here — a plain object is drawn by the prop shader, which declares no such uniform. They render on the mesh path only.' },
|
|
2845
2986
|
pathId: { type: 'string', description: 'extrude_path / lathe_path: the canvas path to turn into geometry. The path itself is unchanged.' },
|
|
2846
|
-
mesh: { type: 'object', description: 'extrude_path / lathe_path options: { id?, depth
|
|
2987
|
+
mesh: { type: 'object', description: 'extrude_path / lathe_path options: { id?, depth? (WORLD units, default 1), unitsPerPixel? (canvas px → world units, default 0.01), upright? (extrude: stand the drawing up, extruded along Z; default flat, a floor plan extruded along Y), placement? (extrude: \'centered\' default: centred on x/z, base at y; \'canvas\': the outline\'s canvas position), caps?, segments?, arc?, flatness?, x?, y?, z?, rotY?, rotYDegrees?, scale?, doubleSided?, color?, metalness?, roughness? }. color is hex or [r,g,b] (default light grey — without it the mesh rendered black); it becomes the shader\'s diffuse uniform. arc is DEGREES; rotY is RADIANS (fed straight to cos/sin in the shader) — pass rotYDegrees to write degrees. These names are the ones the engine records as the mesh provenance, so a captured meshProvenance.opts spreads straight back in.' },
|
|
2847
2988
|
meshId: { type: 'string', description: 'remove_mesh: the mesh id.' },
|
|
2848
|
-
light: { type: 'object', description: 'add_light / set_light: { id?, x?, y?, z?, color
|
|
2989
|
+
light: { type: 'object', description: 'add_light / set_light: { id?, x?, y?, z?, color? (hex or linear [r, g, b], may exceed 1), intensity?, range? }. At most 8 point lights; the ninth is refused by name because the shader array is fixed-size.' },
|
|
2849
2990
|
lightId: { type: 'string', description: 'set_light / remove_light: the light id.' },
|
|
2850
|
-
material: { type: 'object', description: 'add_material / set_material: { id?, color?, emissive?, metalness?, roughness?, emissiveIntensity?, clearcoat?, clearcoatRoughness?, sheenColor?, sheenRoughness? }. A shared surface — one edit restyles every object using it. metalness/roughness render on the mesh path only.' },
|
|
2991
|
+
material: { type: 'object', description: 'add_material / set_material: colours (color, emissive, sheenColor) take hex or [r, g, b] in 0..1. { id?, color?, emissive?, metalness?, roughness?, emissiveIntensity?, clearcoat?, clearcoatRoughness?, sheenColor?, sheenRoughness? }. A shared surface — one edit restyles every object using it. metalness/roughness render on the mesh path only.' },
|
|
2851
2992
|
materialId: { type: 'string', description: 'set_material / remove_material: the material id.' },
|
|
2852
2993
|
source: { type: 'string', description: 'import_obj: the OBJ file TEXT. import_gltf: a URL or a data: URI of the .glb bytes.' },
|
|
2853
2994
|
importOptions: { type: 'object', description: 'import_obj / import_gltf: placement and scale options, passed through.' },
|
|
2854
2995
|
clip: { type: 'string', description: 'set_mesh_clip: which imported animation clip a skinned mesh plays.' },
|
|
2855
2996
|
crossfade: { type: 'number', description: 'set_mesh_clip: seconds to blend out of the previous clip (blended in LOCAL pose space).' },
|
|
2856
2997
|
navTarget: { type: 'string', enum: ['2d', '3d'], description: "set_nav_target: which layer the pointer drives." },
|
|
2857
|
-
point: { type: 'object', description: 'ground_height
|
|
2998
|
+
point: { type: 'object', description: 'ground_height: { x, z } in WORLD coordinates (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.' },
|
|
2858
2999
|
origin: { type: 'array', items: { type: 'number' }, description: 'raycast: ray origin [x, y, z].' },
|
|
2859
3000
|
direction: { type: 'array', items: { type: 'number' }, description: 'raycast: ray direction [x, y, z], need not be normalised.' },
|
|
2860
3001
|
multiplier: { type: 'number', description: 'dolly_camera: >1 away, <1 closer.' },
|
|
@@ -2877,7 +3018,9 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
|
|
|
2877
3018
|
annotations: {
|
|
2878
3019
|
title: 'Character (place and direct a figure from the graph)',
|
|
2879
3020
|
readOnlyHint: false,
|
|
3021
|
+
destructiveHint: false,
|
|
2880
3022
|
idempotentHint: false,
|
|
3023
|
+
openWorldHint: false,
|
|
2881
3024
|
},
|
|
2882
3025
|
description: `Place a CHARACTER from the design graph and DIRECT it — no geometry, no bones, no poses.
|
|
2883
3026
|
|
|
@@ -3425,7 +3568,7 @@ applyFunction: \`
|
|
|
3425
3568
|
annotations: {
|
|
3426
3569
|
title: 'Execute Custom Code',
|
|
3427
3570
|
readOnlyHint: false,
|
|
3428
|
-
destructiveHint:
|
|
3571
|
+
destructiveHint: true,
|
|
3429
3572
|
idempotentHint: false,
|
|
3430
3573
|
openWorldHint: true,
|
|
3431
3574
|
},
|
|
@@ -3599,12 +3742,9 @@ The authoritative list for a given item is pinepaper_get_animatable_properties
|
|
|
3599
3742
|
|
|
3600
3743
|
EASING — IT SHAPES THE SEGMENT ARRIVING AT THE KEYFRAME IT IS ON.
|
|
3601
3744
|
The move from keyframe A to keyframe B is eased by B's easing, not A's. Easing on the FIRST keyframe does nothing (no segment arrives at it) — a Ken Burns with easing only on its first key plays linear. Put the easing on the key you are moving TO.
|
|
3602
|
-
|
|
3603
|
-
-
|
|
3604
|
-
|
|
3605
|
-
- easeInOut: Slow start and end
|
|
3606
|
-
- bounce: Bounce effect
|
|
3607
|
-
- elastic: Elastic overshoot
|
|
3745
|
+
${EASING_LINES}
|
|
3746
|
+
- or a custom cubic-bezier [x1, y1, x2, y2] — x in 0..1, y may exceed 0..1 for overshoot. keyframe_animate and camera_animate only.
|
|
3747
|
+
Every easing renders exactly in video, GIF and APNG. Lottie and CSS get the closest single Bézier: springPlayful loses its wobble, and the in-out quart/quint/expo/circ curves are within 2–4 %. SMIL is exact for springs and those four.
|
|
3608
3748
|
|
|
3609
3749
|
CLIP WINDOW (optional — Premiere/Canva-style clip behavior):
|
|
3610
3750
|
- timeOffset: Shift the whole clip in canvas time (seconds). The animation only runs while canvas time is in [timeOffset, timeOffset + (clipOutPoint - clipInPoint)].
|
|
@@ -3632,9 +3772,8 @@ EXAMPLE — Reveal at 2s, skip first second of keyframe data, end at 4s of keyfr
|
|
|
3632
3772
|
additionalProperties: true,
|
|
3633
3773
|
},
|
|
3634
3774
|
easing: {
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
description: 'Shapes the segment ARRIVING at this keyframe (from the previous one). Ignored on the first keyframe.',
|
|
3775
|
+
...KEYFRAME_EASING_JSON_SCHEMA,
|
|
3776
|
+
description: 'Shapes the segment ARRIVING at this keyframe (from the previous one). Ignored on the first keyframe. A name, or a custom cubic-bezier [x1, y1, x2, y2] — x in 0..1, y may exceed 0..1 for overshoot.',
|
|
3638
3777
|
},
|
|
3639
3778
|
},
|
|
3640
3779
|
required: ['time', 'properties'],
|
|
@@ -3802,7 +3941,7 @@ MASK TYPES: rectangle, circle, ellipse, star, triangle, hexagon, heart, rounded`
|
|
|
3802
3941
|
properties: {
|
|
3803
3942
|
time: { type: 'number', description: 'Normalized time (0-1)' },
|
|
3804
3943
|
properties: { type: 'object', description: 'Mask properties (x, y, width, height, radius, scale, rotation, opacity)' },
|
|
3805
|
-
easing: { type: 'string', enum: [
|
|
3944
|
+
easing: { type: 'string', enum: [...KEYFRAME_EASINGS] },
|
|
3806
3945
|
},
|
|
3807
3946
|
required: ['time', 'properties'],
|
|
3808
3947
|
},
|
|
@@ -3813,7 +3952,7 @@ MASK TYPES: rectangle, circle, ellipse, star, triangle, hexagon, heart, rounded`
|
|
|
3813
3952
|
properties: {
|
|
3814
3953
|
startTime: { type: 'number', description: 'Start time in seconds', default: 0 },
|
|
3815
3954
|
duration: { type: 'number', description: 'Duration in seconds', default: 0.8 },
|
|
3816
|
-
easing: { type: 'string', enum: [
|
|
3955
|
+
easing: { type: 'string', enum: [...KEYFRAME_EASINGS], default: 'easeOut' },
|
|
3817
3956
|
reversed: { type: 'boolean', description: 'Reverse animation (hide instead of reveal)', default: false },
|
|
3818
3957
|
loop: { type: 'boolean', description: 'Loop the animation', default: false },
|
|
3819
3958
|
},
|
|
@@ -3930,7 +4069,7 @@ Needs a studio that publishes the item-side table; without one, the tool says so
|
|
|
3930
4069
|
idempotentHint: true,
|
|
3931
4070
|
openWorldHint: false,
|
|
3932
4071
|
},
|
|
3933
|
-
description: `[Utility]
|
|
4072
|
+
description: `[Utility] List the easing names the studio accepts: one table serves keyframes, masks, relations and the camera. Reads the live engine's list when it reports one.`,
|
|
3934
4073
|
inputSchema: {
|
|
3935
4074
|
type: 'object',
|
|
3936
4075
|
properties: {},
|
|
@@ -4054,8 +4193,8 @@ EXAMPLE (curved arc between waypoints):
|
|
|
4054
4193
|
pitch: { type: 'number', description: '3D tilt in degrees — rendered where the studio supports camera tilt (result.tiltRenders).' },
|
|
4055
4194
|
yaw: { type: 'number', description: '3D rotation in degrees — rendered where the studio supports camera tilt (result.tiltRenders).' },
|
|
4056
4195
|
easing: {
|
|
4057
|
-
|
|
4058
|
-
|
|
4196
|
+
...KEYFRAME_EASING_JSON_SCHEMA,
|
|
4197
|
+
description: 'A name, or a custom cubic-bezier [x1, y1, x2, y2] — x in 0..1, y may exceed 0..1 for overshoot.',
|
|
4059
4198
|
},
|
|
4060
4199
|
pathOut: {
|
|
4061
4200
|
type: 'array',
|
|
@@ -4927,7 +5066,7 @@ flags as a badly-composed scene, and they do not survive an artboard change.`,
|
|
|
4927
5066
|
name: 'pinepaper_brand_kit',
|
|
4928
5067
|
annotations: {
|
|
4929
5068
|
title: 'Brand Kit', readOnlyHint: false, destructiveHint: false,
|
|
4930
|
-
idempotentHint: true, openWorldHint:
|
|
5069
|
+
idempotentHint: true, openWorldHint: true,
|
|
4931
5070
|
},
|
|
4932
5071
|
description: `Apply a brand kit — colours, fonts and roles — across the scene.
|
|
4933
5072
|
|
|
@@ -4935,6 +5074,7 @@ ACTIONS:
|
|
|
4935
5074
|
- plan: Report what WOULD change (count, per-role breakdown, WCAG contrast audit) WITHOUT touching
|
|
4936
5075
|
the canvas. Call this first on an unfamiliar scene; a whole-scene recolour is hard to eyeball afterwards.
|
|
4937
5076
|
- apply: Commit it. A history snapshot is taken FIRST, so one undo puts everything back.
|
|
5077
|
+
- from_url: { url, screenshots? } — read the product's REAL brand from its own site before animating anything: screenshots down the page, the logo (header image or SVG, else the site icon), and a proposed kit from the colours and fonts the site's CSS computes (buttons and links weigh most). Saved to files; the result lists them. Read in an isolated browser context: the studio is not touched. Review the kit against the screenshots, then plan / apply it. Use the brand you are given, never an invented one.
|
|
4938
5078
|
|
|
4939
5079
|
KIT SHAPE — only name and colors.primary are required:
|
|
4940
5080
|
{ "name": "Acme", "colors": { "primary": "#e11d48", "secondary": "#0ea5e9",
|
|
@@ -4949,7 +5089,9 @@ Both actions return a WCAG contrast audit, so you can see failing pairs before s
|
|
|
4949
5089
|
inputSchema: {
|
|
4950
5090
|
type: 'object',
|
|
4951
5091
|
properties: {
|
|
4952
|
-
action: { type: 'string', enum: ['plan', 'apply'], description: 'plan (dry run) or
|
|
5092
|
+
action: { type: 'string', enum: ['plan', 'apply', 'from_url'], description: 'plan (dry run), apply, or from_url (read a site into a proposed kit)' },
|
|
5093
|
+
url: { type: 'string', description: "from_url: the product's site, http(s)." },
|
|
5094
|
+
screenshots: { type: 'integer', minimum: 1, maximum: 6, description: 'from_url: screenshots down the page (default 3).' },
|
|
4953
5095
|
kit: {
|
|
4954
5096
|
type: 'object',
|
|
4955
5097
|
description: 'Brand kit. Requires name and colors.primary; other roles are inferred.',
|
|
@@ -4975,7 +5117,7 @@ Both actions return a WCAG contrast audit, so you can see failing pairs before s
|
|
|
4975
5117
|
},
|
|
4976
5118
|
selectionOnly: { type: 'boolean', description: 'Restrict to current selection' },
|
|
4977
5119
|
},
|
|
4978
|
-
required: ['action'
|
|
5120
|
+
required: ['action'],
|
|
4979
5121
|
},
|
|
4980
5122
|
},
|
|
4981
5123
|
{
|
|
@@ -6984,7 +7126,7 @@ EXAMPLES:
|
|
|
6984
7126
|
annotations: {
|
|
6985
7127
|
title: 'Execute Generator',
|
|
6986
7128
|
readOnlyHint: false,
|
|
6987
|
-
destructiveHint:
|
|
7129
|
+
destructiveHint: true,
|
|
6988
7130
|
idempotentHint: false,
|
|
6989
7131
|
openWorldHint: false,
|
|
6990
7132
|
},
|
|
@@ -7825,7 +7967,7 @@ EXAMPLES:
|
|
|
7825
7967
|
annotations: {
|
|
7826
7968
|
title: 'Build Scene — Items, Animations, Effects in One Call',
|
|
7827
7969
|
readOnlyHint: false,
|
|
7828
|
-
destructiveHint:
|
|
7970
|
+
destructiveHint: true,
|
|
7829
7971
|
idempotentHint: false,
|
|
7830
7972
|
openWorldHint: false,
|
|
7831
7973
|
},
|
|
@@ -7835,7 +7977,7 @@ NOTE: For diagrams (flowcharts, UML), use pinepaper_create_diagram_shape + pinep
|
|
|
7835
7977
|
|
|
7836
7978
|
⚠️ EVERY operation executes LIVE on the canvas. Calling this twice DOUBLES all items. Call ONCE per pipeline.
|
|
7837
7979
|
|
|
7838
|
-
⚠️ SIZING: All positions/sizes MUST fit within the canvas. start_job returns canvasSize. Keep items within 5%-95% of canvas width/height. Max canvas:
|
|
7980
|
+
⚠️ SIZING: All positions/sizes MUST fit within the canvas. start_job returns canvasSize. Keep items within 5%-95% of canvas width/height. Max canvas: 16384 per side (above 8192, Safari / iOS may fail).
|
|
7839
7981
|
|
|
7840
7982
|
ONTOLOGY PREFLIGHT: Before code generation, this tool validates itemType, relationType, effectType, and generatorName against the canonical PinePaper vocabulary. A typo (e.g. "circel") returns a structured VALIDATION_ERROR with the operation index, the offending field, the canonical valid list, and a suggested fix — fix and retry rather than guessing. Pass skipValidation: true to bypass when intentionally using experimental vocabulary.
|
|
7841
7983
|
|
|
@@ -7922,8 +8064,8 @@ EXAMPLE — Animated sky scene with timed reveals:
|
|
|
7922
8064
|
description: 'Operation type',
|
|
7923
8065
|
},
|
|
7924
8066
|
// Canvas setup
|
|
7925
|
-
width: { type: 'number', description: 'Canvas width for set_canvas_size (100-
|
|
7926
|
-
height: { type: 'number', description: 'Canvas height for set_canvas_size (100-
|
|
8067
|
+
width: { type: 'number', description: 'Canvas width for set_canvas_size (100-16384)' },
|
|
8068
|
+
height: { type: 'number', description: 'Canvas height for set_canvas_size (100-16384)' },
|
|
7927
8069
|
preset: { type: 'string', description: 'Canvas preset for set_canvas_size' },
|
|
7928
8070
|
backgroundColor: { type: 'string', description: 'For set_background: hex color' },
|
|
7929
8071
|
generatorName: {
|
|
@@ -8146,7 +8288,16 @@ VERIFY MOTION BEFORE YOU RENDER. An export takes seconds to minutes and shows yo
|
|
|
8146
8288
|
alphaQuantizer: { type: 'integer', minimum: 0, maximum: 63, description: 'webm with transparent: alpha-stream quality, 0 = lossless … 63.' },
|
|
8147
8289
|
loop: { anyOf: [{ type: 'boolean' }, { type: 'integer', minimum: 0, maximum: 1000 }], description: 'gif / apng: true = loop forever, false / 0 / 1 = play once, n = play n times (email clients often want a few plays, not forever). Refused on video formats — see SEAMLESS LOOPS.' },
|
|
8148
8290
|
broadcast: { type: 'boolean', description: 'mp4 only: broadcast-safe — BT.709, limited range (16-235), tagged bt709, constant bitrate with an 8 Mbps floor at 720p+ (4 below). result.video reports what the encoder did.' },
|
|
8291
|
+
frames: { type: 'integer', minimum: 1, maximum: 36000, description: 'Animated formats: exactly this many frames instead of duration. A duration makes ceil(duration x fps) frames (7.91667 s at 24 fps is 191, not 190), so use frames for frame-exact segments.' },
|
|
8149
8292
|
deterministic: { type: 'boolean', description: 'mp4 / webm: byte-identical files for the same scene (pins the container timestamps), so exports can be checksummed or cached by hash.' },
|
|
8293
|
+
motionBlur: {
|
|
8294
|
+
type: 'object',
|
|
8295
|
+
description: '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 (it reports renders and renderCost). The result carries motionBlur (what the exporter applied).',
|
|
8296
|
+
properties: {
|
|
8297
|
+
subframes: { type: 'integer', minimum: 1, maximum: 32, description: 'Renders per frame (default 8; 1 = no blur).' },
|
|
8298
|
+
shutter: { type: 'number', minimum: 0, maximum: 1, description: 'Fraction of the frame interval the shutter is open (default 0.5).' },
|
|
8299
|
+
},
|
|
8300
|
+
},
|
|
8150
8301
|
broadcastHeadroom: { type: 'integer', minimum: 0, maximum: 40, description: 'mp4 with broadcast only, default 12: luma codes kept clear at both ends, so encoder ringing stays legal (12 measured clean on hard edges; 4 = ~100x fewer out-of-range samples, 12 = ~1000x; never zero). Fidelity raises luma_out_of_range with the legaliser command when any remain.' },
|
|
8151
8302
|
bitrate: { type: 'integer', minimum: 100000, maximum: 200000000, description: 'mp4 / webm: target bits/s, replacing the quality-derived one. The browser encoder treats it as a CEILING; result.video.achievedBitrate is what it produced.' },
|
|
8152
8303
|
minBitrate: { type: 'integer', minimum: 100000, maximum: 200000000, description: 'mp4 / webm: a floor; a miss is a fidelity warning naming the re-encode a delivery spec needs.' },
|
|
@@ -8172,7 +8323,7 @@ VERIFY MOTION BEFORE YOU RENDER. An export takes seconds to minutes and shows yo
|
|
|
8172
8323
|
properties: {
|
|
8173
8324
|
x: { type: 'number' }, y: { type: 'number' },
|
|
8174
8325
|
width: { type: 'number', exclusiveMinimum: 0 }, height: { type: 'number', exclusiveMinimum: 0 },
|
|
8175
|
-
outputWidth: { type: 'integer', minimum: 1, maximum:
|
|
8326
|
+
outputWidth: { type: 'integer', minimum: 1, maximum: 16384 }, outputHeight: { type: 'integer', minimum: 1, maximum: 16384 },
|
|
8176
8327
|
excludeForeign: { type: 'boolean', description: 'Default true: an item reaching into the region but centred outside it (a neighbouring card overflow) is left out of this image; result.excludedItems names them. false renders everything that touches the region.' },
|
|
8177
8328
|
},
|
|
8178
8329
|
required: ['x', 'y', 'width', 'height'],
|
|
@@ -8204,8 +8355,8 @@ VERIFY MOTION BEFORE YOU RENDER. An export takes seconds to minutes and shows yo
|
|
|
8204
8355
|
duration: {
|
|
8205
8356
|
type: 'number',
|
|
8206
8357
|
minimum: 0.5,
|
|
8207
|
-
maximum:
|
|
8208
|
-
description: 'Video duration in seconds for mp4/webm/gif (default 5). Max
|
|
8358
|
+
maximum: 600,
|
|
8359
|
+
description: 'Video duration in seconds for mp4/webm/gif (default 5). Max 600 for mp4/webm (past about a minute the file is paged back from the studio\'s export store); GIF is capped at 15 because it has no bitrate target, so size scales with frames × dimensions — use mp4/webm for longer clips. The file has ceil(duration × fps) frames — for an exact count use frames instead. Static formats ignore this. If you set play_timeline to N seconds, pass the same N here.',
|
|
8209
8360
|
},
|
|
8210
8361
|
},
|
|
8211
8362
|
// Nothing is required: platform defaults to 'auto', which is the canvas's
|