@pinepaper.studio/mcp-server 1.6.17 → 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.md +92 -0
- 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 +138 -35
- 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 +4 -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/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 +226 -10
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/minimal-descriptions.js +4 -4
- 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/types/code-generator.d.ts +40 -2
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +260 -26
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +2170 -698
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +152 -36
- package/dist/types/schemas.js.map +1 -1
- 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/vendor/design/design-style.js +1 -1
- package/manifest.json +1 -1
- package/package.json +7 -4
|
@@ -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
|
// =============================================================================
|
|
@@ -1738,6 +1740,8 @@ EXAMPLES:
|
|
|
1738
1740
|
annotations: { title: 'Beat-Snapped Cuts', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1739
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.
|
|
1740
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
|
+
|
|
1741
1745
|
- source: the music — an upload id, URL, data: URL or local file path. Or beats: [seconds] to skip the analysis.
|
|
1742
1746
|
- every: 'beat' (default) | 'bar' | N beats. beatsPerBar (default 4). range: [from, to] seconds.
|
|
1743
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.
|
|
@@ -1805,6 +1809,12 @@ EXAMPLE: { checks: ["contrast", "flash"], duration: 6, fps: 20 }`,
|
|
|
1805
1809
|
annotations: { title: 'Generate Image (cloud)', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
1806
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).
|
|
1807
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
|
+
|
|
1808
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.
|
|
1809
1819
|
|
|
1810
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).
|
|
@@ -1847,7 +1857,7 @@ The result: assets [{assetId, ref, width, height}] — the sizes the files HAVE
|
|
|
1847
1857
|
{
|
|
1848
1858
|
name: 'pinepaper_generate_estimate',
|
|
1849
1859
|
annotations: { title: 'Estimate Generation (free)', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1850
|
-
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.`,
|
|
1851
1861
|
inputSchema: {
|
|
1852
1862
|
type: 'object',
|
|
1853
1863
|
properties: {
|
|
@@ -2135,6 +2145,13 @@ By default returns a cheap per-frame hash + byte size (token-light), plus unique
|
|
|
2135
2145
|
|
|
2136
2146
|
Set includeDataUrls: true to also get each frame as a PNG data URL (large — only for a few frames).
|
|
2137
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
|
+
|
|
2138
2155
|
USE WHEN:
|
|
2139
2156
|
- Verifying an animation is deterministic / reproducible
|
|
2140
2157
|
- Checking whether a scene actually animates across a time range
|
|
@@ -2157,8 +2174,26 @@ EXAMPLE: { times: [0, 0.5, 1, 1.5, 2], seed: 42 }`,
|
|
|
2157
2174
|
type: 'boolean',
|
|
2158
2175
|
description: 'Include each frame as a PNG data URL (large). Default false → hashes only.',
|
|
2159
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
|
+
},
|
|
2160
2196
|
},
|
|
2161
|
-
required: ['times'],
|
|
2162
2197
|
},
|
|
2163
2198
|
},
|
|
2164
2199
|
{
|
|
@@ -2194,11 +2229,15 @@ VIDEO EDITING:
|
|
|
2194
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).
|
|
2195
2230
|
- stop_live_matte: { id } — stop a live matte, keep the last cut.
|
|
2196
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
|
+
|
|
2197
2236
|
EXAMPLE: { action: 'upload_video', url: 'https://…/clip.mp4', scale: 0.5, timeOffset: 1 }`,
|
|
2198
2237
|
inputSchema: {
|
|
2199
2238
|
type: 'object',
|
|
2200
2239
|
properties: {
|
|
2201
|
-
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" },
|
|
2202
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) }." },
|
|
2203
2242
|
atPlayhead: { type: 'boolean', description: 'upload_video / upload_audio: start at the current playback time (instead of timeOffset).' },
|
|
2204
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.' },
|
|
@@ -2218,7 +2257,11 @@ EXAMPLE: { action: 'upload_video', url: 'https://…/clip.mp4', scale: 0.5, time
|
|
|
2218
2257
|
hideMatte: { type: 'boolean', description: 'apply_track_matte: hide the matte item after applying.' },
|
|
2219
2258
|
live: { type: 'boolean', description: 'apply_track_matte: keep re-cutting as the matte moves/animates — the kinetic mask reveal. Omitted = bake once, destructively.' },
|
|
2220
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." },
|
|
2221
|
-
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).' },
|
|
2222
2265
|
rate: { type: 'number', description: 'Playback rate 0.25–4 — set_playback_rate.' },
|
|
2223
2266
|
inPoint: { type: 'number', description: 'Clip in-point (media-time s) — set_clip.' },
|
|
2224
2267
|
outPoint: { type: 'number', description: 'Clip out-point (media-time s, > inPoint) — set_clip.' },
|
|
@@ -2309,9 +2352,10 @@ READ THIS BEFORE PROMISING A MEDIUM. Naming a medium does not create its marks.
|
|
|
2309
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.
|
|
2310
2353
|
|
|
2311
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.
|
|
2312
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).
|
|
2313
2357
|
- resolve: { medium } → { ok, fidelity, reason, markMaker }. ok:false with a reason is a real answer — pass it on rather than trying anyway.
|
|
2314
|
-
- list_stitches: the
|
|
2358
|
+
- list_stitches: the thread stitches with their params.
|
|
2315
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.
|
|
2316
2360
|
- apply_hatch: rule an item with hatching. Same deal — silhouette is the region, source is hidden, re-hatching replaces.
|
|
2317
2361
|
- list_flow_fields: the 7 fields that bend a hatch line.
|
|
@@ -2341,11 +2385,12 @@ EXAMPLE — a stitched leaf:
|
|
|
2341
2385
|
inputSchema: {
|
|
2342
2386
|
type: 'object',
|
|
2343
2387
|
properties: {
|
|
2344
|
-
action: { type: 'string', enum: ['list_media', 'resolve', 'list_stitches', 'apply_thread', 'apply_hatch', 'list_flow_fields', 'list_hatch_options'], description: 'Medium operation' },
|
|
2345
|
-
medium: { type: 'string', description:
|
|
2346
|
-
|
|
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).' },
|
|
2347
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." },
|
|
2348
|
-
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." },
|
|
2349
2394
|
stitchLen: { type: 'number', description: 'apply_thread: nominal stitch length px (default 18).' },
|
|
2350
2395
|
rowGap: { type: 'number', description: 'apply_thread: row spacing px; defaults from thread width so rows abut.' },
|
|
2351
2396
|
variance: { type: 'number', description: 'apply_thread: length jitter 0..1 (default 0.35) — the long-and-short shading.' },
|
|
@@ -2594,6 +2639,13 @@ RECIPE — a playable maze: create_tilemap with wall fills → batch_create colo
|
|
|
2594
2639
|
},
|
|
2595
2640
|
{
|
|
2596
2641
|
name: 'pinepaper_design_system',
|
|
2642
|
+
annotations: {
|
|
2643
|
+
title: 'Design System',
|
|
2644
|
+
readOnlyHint: false,
|
|
2645
|
+
destructiveHint: false,
|
|
2646
|
+
idempotentHint: false,
|
|
2647
|
+
openWorldHint: false,
|
|
2648
|
+
},
|
|
2597
2649
|
description: `The design vocabulary, as data you can read — and eighteen styles that can build a scene.
|
|
2598
2650
|
|
|
2599
2651
|
TWO DIFFERENT THINGS, kept apart on purpose:
|
|
@@ -2630,6 +2682,13 @@ A partial composition reports as a failure with the elements that did not create
|
|
|
2630
2682
|
},
|
|
2631
2683
|
{
|
|
2632
2684
|
name: 'pinepaper_stick',
|
|
2685
|
+
annotations: {
|
|
2686
|
+
title: 'Stick Figure Kit',
|
|
2687
|
+
readOnlyHint: false,
|
|
2688
|
+
destructiveHint: false,
|
|
2689
|
+
idempotentHint: false,
|
|
2690
|
+
openWorldHint: false,
|
|
2691
|
+
},
|
|
2633
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.
|
|
2634
2693
|
|
|
2635
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.
|
|
@@ -2695,6 +2754,13 @@ The geometry is vendored from mcp-cloud, which makes this a three-repo artifact:
|
|
|
2695
2754
|
},
|
|
2696
2755
|
{
|
|
2697
2756
|
name: 'pinepaper_story',
|
|
2757
|
+
annotations: {
|
|
2758
|
+
title: 'Story to Scene',
|
|
2759
|
+
readOnlyHint: false,
|
|
2760
|
+
destructiveHint: false,
|
|
2761
|
+
idempotentHint: false,
|
|
2762
|
+
openWorldHint: false,
|
|
2763
|
+
},
|
|
2698
2764
|
description: `A piece of prose becomes a scene.
|
|
2699
2765
|
|
|
2700
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.
|
|
@@ -2715,6 +2781,13 @@ The geometry is vendored from mcp-cloud, which makes this a three-repo artifact:
|
|
|
2715
2781
|
},
|
|
2716
2782
|
{
|
|
2717
2783
|
name: 'pinepaper_interchange',
|
|
2784
|
+
annotations: {
|
|
2785
|
+
title: 'Interchange Formats',
|
|
2786
|
+
readOnlyHint: false,
|
|
2787
|
+
destructiveHint: false,
|
|
2788
|
+
idempotentHint: false,
|
|
2789
|
+
openWorldHint: false,
|
|
2790
|
+
},
|
|
2718
2791
|
description: `The formats other tools read and write — Lottie, dotLottie, GLB, BVH, PNG sequence.
|
|
2719
2792
|
|
|
2720
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.
|
|
@@ -2733,6 +2806,13 @@ Three of these refuse quietly in the engine: exportGLB with no perspective objec
|
|
|
2733
2806
|
},
|
|
2734
2807
|
{
|
|
2735
2808
|
name: 'pinepaper_sound',
|
|
2809
|
+
annotations: {
|
|
2810
|
+
title: 'Sound Synthesis & Soundtrack',
|
|
2811
|
+
readOnlyHint: false,
|
|
2812
|
+
destructiveHint: false,
|
|
2813
|
+
idempotentHint: false,
|
|
2814
|
+
openWorldHint: false,
|
|
2815
|
+
},
|
|
2736
2816
|
description: `Synthesis — and the drawing that is the same object.
|
|
2737
2817
|
|
|
2738
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.
|
|
@@ -2786,6 +2866,13 @@ render_soundtrack mixes every placed sound to a WAV and writes it to a file. It
|
|
|
2786
2866
|
},
|
|
2787
2867
|
{
|
|
2788
2868
|
name: 'pinepaper_motion',
|
|
2869
|
+
annotations: {
|
|
2870
|
+
title: 'Group & Field Motion',
|
|
2871
|
+
readOnlyHint: false,
|
|
2872
|
+
destructiveHint: false,
|
|
2873
|
+
idempotentHint: false,
|
|
2874
|
+
openWorldHint: false,
|
|
2875
|
+
},
|
|
2789
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.
|
|
2790
2877
|
|
|
2791
2878
|
Two kinds, and the second is the one nothing else here can do:
|
|
@@ -2817,6 +2904,13 @@ This is not pinepaper_animate: that applies a loop preset to ONE item. This move
|
|
|
2817
2904
|
},
|
|
2818
2905
|
{
|
|
2819
2906
|
name: 'pinepaper_path',
|
|
2907
|
+
annotations: {
|
|
2908
|
+
title: 'Path Operations',
|
|
2909
|
+
readOnlyHint: false,
|
|
2910
|
+
destructiveHint: true,
|
|
2911
|
+
idempotentHint: false,
|
|
2912
|
+
openWorldHint: false,
|
|
2913
|
+
},
|
|
2820
2914
|
description: `Destructive path operations — the ones that REPLACE or ADD items rather than restyling them.
|
|
2821
2915
|
|
|
2822
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.
|
|
@@ -2879,7 +2973,7 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
|
|
|
2879
2973
|
anyOf: [{ type: 'boolean' }, { type: 'object' }],
|
|
2880
2974
|
description: 'create: include the walkable character (boolean or config).',
|
|
2881
2975
|
},
|
|
2882
|
-
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.' },
|
|
2883
2977
|
actorId: { type: 'string', description: 'Actor id.' },
|
|
2884
2978
|
x: { type: 'number', description: 'add_actor: world x.' },
|
|
2885
2979
|
z: { type: 'number', description: 'add_actor: world z.' },
|
|
@@ -2890,18 +2984,18 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
|
|
|
2890
2984
|
camera: { type: 'object', description: "set_camera: { mode: follow|fixed|orbit, target?, radius?, speed?, eye?, lookAt? }." },
|
|
2891
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.' },
|
|
2892
2986
|
pathId: { type: 'string', description: 'extrude_path / lathe_path: the canvas path to turn into geometry. The path itself is unchanged.' },
|
|
2893
|
-
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.' },
|
|
2894
2988
|
meshId: { type: 'string', description: 'remove_mesh: the mesh id.' },
|
|
2895
|
-
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.' },
|
|
2896
2990
|
lightId: { type: 'string', description: 'set_light / remove_light: the light id.' },
|
|
2897
|
-
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.' },
|
|
2898
2992
|
materialId: { type: 'string', description: 'set_material / remove_material: the material id.' },
|
|
2899
2993
|
source: { type: 'string', description: 'import_obj: the OBJ file TEXT. import_gltf: a URL or a data: URI of the .glb bytes.' },
|
|
2900
2994
|
importOptions: { type: 'object', description: 'import_obj / import_gltf: placement and scale options, passed through.' },
|
|
2901
2995
|
clip: { type: 'string', description: 'set_mesh_clip: which imported animation clip a skinned mesh plays.' },
|
|
2902
2996
|
crossfade: { type: 'number', description: 'set_mesh_clip: seconds to blend out of the previous clip (blended in LOCAL pose space).' },
|
|
2903
2997
|
navTarget: { type: 'string', enum: ['2d', '3d'], description: "set_nav_target: which layer the pointer drives." },
|
|
2904
|
-
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.' },
|
|
2905
2999
|
origin: { type: 'array', items: { type: 'number' }, description: 'raycast: ray origin [x, y, z].' },
|
|
2906
3000
|
direction: { type: 'array', items: { type: 'number' }, description: 'raycast: ray direction [x, y, z], need not be normalised.' },
|
|
2907
3001
|
multiplier: { type: 'number', description: 'dolly_camera: >1 away, <1 closer.' },
|
|
@@ -2924,7 +3018,9 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
|
|
|
2924
3018
|
annotations: {
|
|
2925
3019
|
title: 'Character (place and direct a figure from the graph)',
|
|
2926
3020
|
readOnlyHint: false,
|
|
3021
|
+
destructiveHint: false,
|
|
2927
3022
|
idempotentHint: false,
|
|
3023
|
+
openWorldHint: false,
|
|
2928
3024
|
},
|
|
2929
3025
|
description: `Place a CHARACTER from the design graph and DIRECT it — no geometry, no bones, no poses.
|
|
2930
3026
|
|
|
@@ -3472,7 +3568,7 @@ applyFunction: \`
|
|
|
3472
3568
|
annotations: {
|
|
3473
3569
|
title: 'Execute Custom Code',
|
|
3474
3570
|
readOnlyHint: false,
|
|
3475
|
-
destructiveHint:
|
|
3571
|
+
destructiveHint: true,
|
|
3476
3572
|
idempotentHint: false,
|
|
3477
3573
|
openWorldHint: true,
|
|
3478
3574
|
},
|
|
@@ -3646,12 +3742,9 @@ The authoritative list for a given item is pinepaper_get_animatable_properties
|
|
|
3646
3742
|
|
|
3647
3743
|
EASING — IT SHAPES THE SEGMENT ARRIVING AT THE KEYFRAME IT IS ON.
|
|
3648
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.
|
|
3649
|
-
|
|
3650
|
-
-
|
|
3651
|
-
|
|
3652
|
-
- easeInOut: Slow start and end
|
|
3653
|
-
- bounce: Bounce effect
|
|
3654
|
-
- 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.
|
|
3655
3748
|
|
|
3656
3749
|
CLIP WINDOW (optional — Premiere/Canva-style clip behavior):
|
|
3657
3750
|
- timeOffset: Shift the whole clip in canvas time (seconds). The animation only runs while canvas time is in [timeOffset, timeOffset + (clipOutPoint - clipInPoint)].
|
|
@@ -3679,9 +3772,8 @@ EXAMPLE — Reveal at 2s, skip first second of keyframe data, end at 4s of keyfr
|
|
|
3679
3772
|
additionalProperties: true,
|
|
3680
3773
|
},
|
|
3681
3774
|
easing: {
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
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.',
|
|
3685
3777
|
},
|
|
3686
3778
|
},
|
|
3687
3779
|
required: ['time', 'properties'],
|
|
@@ -3849,7 +3941,7 @@ MASK TYPES: rectangle, circle, ellipse, star, triangle, hexagon, heart, rounded`
|
|
|
3849
3941
|
properties: {
|
|
3850
3942
|
time: { type: 'number', description: 'Normalized time (0-1)' },
|
|
3851
3943
|
properties: { type: 'object', description: 'Mask properties (x, y, width, height, radius, scale, rotation, opacity)' },
|
|
3852
|
-
easing: { type: 'string', enum: [
|
|
3944
|
+
easing: { type: 'string', enum: [...KEYFRAME_EASINGS] },
|
|
3853
3945
|
},
|
|
3854
3946
|
required: ['time', 'properties'],
|
|
3855
3947
|
},
|
|
@@ -3860,7 +3952,7 @@ MASK TYPES: rectangle, circle, ellipse, star, triangle, hexagon, heart, rounded`
|
|
|
3860
3952
|
properties: {
|
|
3861
3953
|
startTime: { type: 'number', description: 'Start time in seconds', default: 0 },
|
|
3862
3954
|
duration: { type: 'number', description: 'Duration in seconds', default: 0.8 },
|
|
3863
|
-
easing: { type: 'string', enum: [
|
|
3955
|
+
easing: { type: 'string', enum: [...KEYFRAME_EASINGS], default: 'easeOut' },
|
|
3864
3956
|
reversed: { type: 'boolean', description: 'Reverse animation (hide instead of reveal)', default: false },
|
|
3865
3957
|
loop: { type: 'boolean', description: 'Loop the animation', default: false },
|
|
3866
3958
|
},
|
|
@@ -3977,7 +4069,7 @@ Needs a studio that publishes the item-side table; without one, the tool says so
|
|
|
3977
4069
|
idempotentHint: true,
|
|
3978
4070
|
openWorldHint: false,
|
|
3979
4071
|
},
|
|
3980
|
-
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.`,
|
|
3981
4073
|
inputSchema: {
|
|
3982
4074
|
type: 'object',
|
|
3983
4075
|
properties: {},
|
|
@@ -4101,8 +4193,8 @@ EXAMPLE (curved arc between waypoints):
|
|
|
4101
4193
|
pitch: { type: 'number', description: '3D tilt in degrees — rendered where the studio supports camera tilt (result.tiltRenders).' },
|
|
4102
4194
|
yaw: { type: 'number', description: '3D rotation in degrees — rendered where the studio supports camera tilt (result.tiltRenders).' },
|
|
4103
4195
|
easing: {
|
|
4104
|
-
|
|
4105
|
-
|
|
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.',
|
|
4106
4198
|
},
|
|
4107
4199
|
pathOut: {
|
|
4108
4200
|
type: 'array',
|
|
@@ -4974,7 +5066,7 @@ flags as a badly-composed scene, and they do not survive an artboard change.`,
|
|
|
4974
5066
|
name: 'pinepaper_brand_kit',
|
|
4975
5067
|
annotations: {
|
|
4976
5068
|
title: 'Brand Kit', readOnlyHint: false, destructiveHint: false,
|
|
4977
|
-
idempotentHint: true, openWorldHint:
|
|
5069
|
+
idempotentHint: true, openWorldHint: true,
|
|
4978
5070
|
},
|
|
4979
5071
|
description: `Apply a brand kit — colours, fonts and roles — across the scene.
|
|
4980
5072
|
|
|
@@ -4982,6 +5074,7 @@ ACTIONS:
|
|
|
4982
5074
|
- plan: Report what WOULD change (count, per-role breakdown, WCAG contrast audit) WITHOUT touching
|
|
4983
5075
|
the canvas. Call this first on an unfamiliar scene; a whole-scene recolour is hard to eyeball afterwards.
|
|
4984
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.
|
|
4985
5078
|
|
|
4986
5079
|
KIT SHAPE — only name and colors.primary are required:
|
|
4987
5080
|
{ "name": "Acme", "colors": { "primary": "#e11d48", "secondary": "#0ea5e9",
|
|
@@ -4996,7 +5089,9 @@ Both actions return a WCAG contrast audit, so you can see failing pairs before s
|
|
|
4996
5089
|
inputSchema: {
|
|
4997
5090
|
type: 'object',
|
|
4998
5091
|
properties: {
|
|
4999
|
-
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).' },
|
|
5000
5095
|
kit: {
|
|
5001
5096
|
type: 'object',
|
|
5002
5097
|
description: 'Brand kit. Requires name and colors.primary; other roles are inferred.',
|
|
@@ -5022,7 +5117,7 @@ Both actions return a WCAG contrast audit, so you can see failing pairs before s
|
|
|
5022
5117
|
},
|
|
5023
5118
|
selectionOnly: { type: 'boolean', description: 'Restrict to current selection' },
|
|
5024
5119
|
},
|
|
5025
|
-
required: ['action'
|
|
5120
|
+
required: ['action'],
|
|
5026
5121
|
},
|
|
5027
5122
|
},
|
|
5028
5123
|
{
|
|
@@ -7031,7 +7126,7 @@ EXAMPLES:
|
|
|
7031
7126
|
annotations: {
|
|
7032
7127
|
title: 'Execute Generator',
|
|
7033
7128
|
readOnlyHint: false,
|
|
7034
|
-
destructiveHint:
|
|
7129
|
+
destructiveHint: true,
|
|
7035
7130
|
idempotentHint: false,
|
|
7036
7131
|
openWorldHint: false,
|
|
7037
7132
|
},
|
|
@@ -7872,7 +7967,7 @@ EXAMPLES:
|
|
|
7872
7967
|
annotations: {
|
|
7873
7968
|
title: 'Build Scene — Items, Animations, Effects in One Call',
|
|
7874
7969
|
readOnlyHint: false,
|
|
7875
|
-
destructiveHint:
|
|
7970
|
+
destructiveHint: true,
|
|
7876
7971
|
idempotentHint: false,
|
|
7877
7972
|
openWorldHint: false,
|
|
7878
7973
|
},
|
|
@@ -8195,6 +8290,14 @@ VERIFY MOTION BEFORE YOU RENDER. An export takes seconds to minutes and shows yo
|
|
|
8195
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.' },
|
|
8196
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.' },
|
|
8197
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
|
+
},
|
|
8198
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.' },
|
|
8199
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.' },
|
|
8200
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.' },
|