@pinepaper.studio/mcp-server 1.6.2 → 1.6.4
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 +45 -8
- package/README.pt-BR.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +561 -7
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +73 -2
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/minimal-descriptions.d.ts.map +1 -1
- package/dist/tools/minimal-descriptions.js +21 -3
- package/dist/tools/minimal-descriptions.js.map +1 -1
- package/dist/tools/toolkits.d.ts.map +1 -1
- package/dist/tools/toolkits.js +25 -0
- package/dist/tools/toolkits.js.map +1 -1
- package/dist/types/code-generator.d.ts +51 -2
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +775 -37
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +2688 -119
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +357 -8
- package/dist/types/schemas.js.map +1 -1
- package/manifest.json +65 -9
- package/package.json +4 -4
|
@@ -1730,11 +1730,34 @@ NOTES:
|
|
|
1730
1730
|
- URL-based: the URL is fetched in the browser, so it must be reachable + CORS-permitted.
|
|
1731
1731
|
- Use the returned id with set_playback_rate / set_clip / remove, and (for video) modify/animate it like any item.
|
|
1732
1732
|
|
|
1733
|
+
VIDEO EDITING:
|
|
1734
|
+
- set_time_remap: { id, remapTrack: [{time, value, easing?}, …] | null } — canvas-time → SOURCE-time curve: speed ramps, freeze frames, reverse. null restores 1:1.
|
|
1735
|
+
- speed_ramp: { id, segments: [{duration, speed}, …] } — the human way to say a remap. speed 0 freezes, negative reverses.
|
|
1736
|
+
- match_cut: { fromItemId, toItemId, subject? (detect|bounds), label?, at?, settle?, fade?, consent? } — cut between two shots with the SUBJECT aligned across the cut (on-device detection; first use returns {needsConsent, cost} until consent: true).
|
|
1737
|
+
- 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).
|
|
1738
|
+
- stop_live_matte: { id } — stop a live matte, keep the last cut.
|
|
1739
|
+
|
|
1733
1740
|
EXAMPLE: { action: 'upload_video', url: 'https://…/clip.mp4', scale: 0.5, timeOffset: 1 }`,
|
|
1734
1741
|
inputSchema: {
|
|
1735
1742
|
type: 'object',
|
|
1736
1743
|
properties: {
|
|
1737
|
-
action: { type: 'string', enum: ['upload_video', 'upload_audio', 'list', 'remove', 'set_playback_rate', 'set_clip'], description: "Media action" },
|
|
1744
|
+
action: { type: 'string', enum: ['upload_video', 'upload_audio', 'list', 'remove', 'set_playback_rate', 'set_clip', 'set_time_remap', 'speed_ramp', 'match_cut', 'apply_track_matte', 'stop_live_matte'], description: "Media action" },
|
|
1745
|
+
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.' },
|
|
1746
|
+
segments: { type: 'array', items: { type: 'object', properties: { duration: { type: 'number' }, speed: { type: 'number' } }, required: ['duration', 'speed'] }, description: 'speed_ramp: consecutive {duration, speed} segments. speed 0 = freeze frame, negative = reverse.' },
|
|
1747
|
+
fromItemId: { type: 'string', description: 'match_cut: outgoing shot item id.' },
|
|
1748
|
+
toItemId: { type: 'string', description: 'match_cut: incoming shot item id.' },
|
|
1749
|
+
subject: { type: 'string', enum: ['detect', 'bounds'], description: "match_cut: subject source — 'detect' (on-device DETR, needs consent on first use) or 'bounds'." },
|
|
1750
|
+
label: { type: 'string', description: 'match_cut: narrow detection to one class ("person") when the biggest object is not the subject.' },
|
|
1751
|
+
at: { type: 'number', description: 'match_cut: cut time in seconds (default 0).' },
|
|
1752
|
+
settle: { type: 'number', description: 'match_cut: seconds to relax to natural framing (default 0.8).' },
|
|
1753
|
+
fade: { type: 'number', description: 'match_cut: crossfade seconds; 0 = hard cut (default 0.12).' },
|
|
1754
|
+
consent: { type: 'boolean', description: 'match_cut: allow the detection model download (a first run without it returns needsConsent instead of downloading silently).' },
|
|
1755
|
+
matteItemId: { type: 'string', description: 'apply_track_matte: the item whose pixels drive the alpha — a headline, a gradient, any raster or vector.' },
|
|
1756
|
+
channel: { type: 'string', enum: ['luma', 'alpha', 'red'], description: 'apply_track_matte: matte channel (default luma).' },
|
|
1757
|
+
invert: { type: 'boolean', description: 'apply_track_matte: invert the matte.' },
|
|
1758
|
+
strength: { type: 'number', description: 'apply_track_matte: 0–1.' },
|
|
1759
|
+
hideMatte: { type: 'boolean', description: 'apply_track_matte: hide the matte item after applying.' },
|
|
1760
|
+
live: { type: 'boolean', description: 'apply_track_matte: keep re-cutting as the matte moves/animates — the kinetic mask reveal. Omitted = bake once, destructively.' },
|
|
1738
1761
|
url: { type: 'string', description: 'Media URL — required for upload_video / upload_audio.' },
|
|
1739
1762
|
id: { type: 'string', description: 'Media id — required for remove / set_playback_rate / set_clip.' },
|
|
1740
1763
|
rate: { type: 'number', description: 'Playback rate 0.25–4 — set_playback_rate.' },
|
|
@@ -1752,6 +1775,39 @@ EXAMPLE: { action: 'upload_video', url: 'https://…/clip.mp4', scale: 0.5, time
|
|
|
1752
1775
|
required: ['action'],
|
|
1753
1776
|
},
|
|
1754
1777
|
},
|
|
1778
|
+
{
|
|
1779
|
+
name: 'pinepaper_text_style',
|
|
1780
|
+
annotations: {
|
|
1781
|
+
title: 'Text Styles (Display Titles / Variable Fonts)',
|
|
1782
|
+
readOnlyHint: false,
|
|
1783
|
+
destructiveHint: false,
|
|
1784
|
+
idempotentHint: false,
|
|
1785
|
+
openWorldHint: false,
|
|
1786
|
+
},
|
|
1787
|
+
description: `Display TEXT STYLES and variable-font axes — the stacked-layer titles a fill+stroke+shadow cannot make, and weight/width/slant as animatable properties.
|
|
1788
|
+
|
|
1789
|
+
ACTIONS:
|
|
1790
|
+
- apply_style: { itemId, styleKey, palette?, variant?, content?, fontFamily?, fontSize? } — render a text item as a layered display title (offset copies, outlines, fills). The result GROUP ADOPTS the text's registry id, so existing relations/keyframes/handles keep pointing at it. fontFamily: 'suggested' opts into the face the style was designed around (arcade wants a pixel face — a layer stack can outline any typeface but cannot pixellate one); never automatic.
|
|
1791
|
+
- set_font_axes: { itemId, axes: { weight?, width?, slant? } } — STANDARD variable-font axes only (Canvas 2D has no font-variation-settings, so custom foundry axes are unreachable — platform limit, stated, not silent). Returns {applied, rejected}: CHECK rejected — an axis silently ignored is the failure this surface exists to prevent. All three are ordinary animatable properties: keyframe fontWeight and type breathes between weights.
|
|
1792
|
+
- list_styles: {} — styles + palettes + axes, for pickers. Call this first; styleKey strings come from here.
|
|
1793
|
+
|
|
1794
|
+
EXAMPLE: { action: 'apply_style', itemId: 'title_1', styleKey: 'arcade', fontFamily: 'suggested' }`,
|
|
1795
|
+
inputSchema: {
|
|
1796
|
+
type: 'object',
|
|
1797
|
+
properties: {
|
|
1798
|
+
action: { type: 'string', enum: ['apply_style', 'set_font_axes', 'list_styles'], description: 'Text style operation' },
|
|
1799
|
+
itemId: { type: 'string', description: 'Text item id — apply_style / set_font_axes.' },
|
|
1800
|
+
styleKey: { type: 'string', description: 'apply_style: style name from list_styles.' },
|
|
1801
|
+
palette: { description: 'apply_style: named colourway (string) or explicit color array.' },
|
|
1802
|
+
variant: { type: 'number', description: 'apply_style: variation index within the style.' },
|
|
1803
|
+
content: { type: 'string', description: 'apply_style: replace the text while styling.' },
|
|
1804
|
+
fontFamily: { type: 'string', description: "apply_style: font override; 'suggested' = the face the style was designed around." },
|
|
1805
|
+
fontSize: { type: 'number', description: 'apply_style: font size (defaults to the item\'s).' },
|
|
1806
|
+
axes: { type: 'object', properties: { weight: { type: 'number' }, width: {}, slant: { type: 'number' } }, description: 'set_font_axes: weight (wght), width (wdth), slant (slnt).' },
|
|
1807
|
+
},
|
|
1808
|
+
required: ['action'],
|
|
1809
|
+
},
|
|
1810
|
+
},
|
|
1755
1811
|
{
|
|
1756
1812
|
name: 'pinepaper_crop_image',
|
|
1757
1813
|
annotations: {
|
|
@@ -1829,6 +1885,145 @@ EXAMPLE: { itemId: 'item_7', color: '#00b140', threshold: 60, smoothing: 12 }`,
|
|
|
1829
1885
|
required: ['itemId'],
|
|
1830
1886
|
},
|
|
1831
1887
|
},
|
|
1888
|
+
{
|
|
1889
|
+
name: 'pinepaper_shatter_image',
|
|
1890
|
+
annotations: {
|
|
1891
|
+
title: 'Shatter Image (Grid of Tiles)',
|
|
1892
|
+
readOnlyHint: false,
|
|
1893
|
+
destructiveHint: false,
|
|
1894
|
+
idempotentHint: false,
|
|
1895
|
+
openWorldHint: false,
|
|
1896
|
+
},
|
|
1897
|
+
description: `Split a raster into a GRID OF TILES — the primitive under every "photo breaks apart / assembles from pieces" effect.
|
|
1898
|
+
|
|
1899
|
+
Deliberately INERT: the pieces land exactly where the picture was, so the canvas looks unchanged until something animates them (per-tile keyframes, blast/scatter relations, physics bodies, staggered_with). The GROUP inherits the original item's registry id — relations, handles, and composition slots keep pointing at the thing in its place.
|
|
1900
|
+
|
|
1901
|
+
Params: { itemId, pieces? (≈100; grid solves for square-ish tiles at source aspect, so 100 of a 3:2 photo → 12×8), rows?/cols? (exact grid, override pieces), keepSource? (hide-don't-delete the original so the photo can be restored) }
|
|
1902
|
+
Returns: { groupId (= the original id, adopted), tiles, rows, cols }
|
|
1903
|
+
|
|
1904
|
+
RECIPE — assemble-from-pieces: shatter with keepSource, keyframe each tile from a random offscreen position to {x:0,y:0} delta, stagger by index, then swap the source back on completion.`,
|
|
1905
|
+
inputSchema: {
|
|
1906
|
+
type: 'object',
|
|
1907
|
+
properties: {
|
|
1908
|
+
itemId: { type: 'string', description: 'The raster to shatter (or a group containing one).' },
|
|
1909
|
+
pieces: { type: 'number', description: 'Approximate piece count (default 100).' },
|
|
1910
|
+
rows: { type: 'number', description: 'Exact grid rows — overrides pieces.' },
|
|
1911
|
+
cols: { type: 'number', description: 'Exact grid cols — overrides pieces.' },
|
|
1912
|
+
keepSource: { type: 'boolean', description: 'Keep the original hidden for later restore.' },
|
|
1913
|
+
},
|
|
1914
|
+
required: ['itemId'],
|
|
1915
|
+
},
|
|
1916
|
+
},
|
|
1917
|
+
{
|
|
1918
|
+
name: 'pinepaper_import_layered_character',
|
|
1919
|
+
annotations: {
|
|
1920
|
+
title: 'Import Layered Character (Decomposer Output)',
|
|
1921
|
+
readOnlyHint: false,
|
|
1922
|
+
destructiveHint: false,
|
|
1923
|
+
idempotentHint: false,
|
|
1924
|
+
openWorldHint: true,
|
|
1925
|
+
},
|
|
1926
|
+
description: `Land a DECOMPOSED character illustration as one Group of role-bound parts — eyes that blink, a mouth that talks, with ZERO further wiring.
|
|
1927
|
+
|
|
1928
|
+
Input is the output of a single-image layer decomposer (See-through — one flat anime drawing in, ~20 occlusion-inpainted layers out — or any PSD-style layer dump with the same manifest shape). The decomposition model does NOT run here: it needs a GPU and minutes per image; run it where it fits and import the folder.
|
|
1929
|
+
|
|
1930
|
+
Layer tags map onto the exact role tokens the expresses presets read (eye_left, pupil_right, mouth…), so pinepaper_animate/expresses blink-smile-talk work on the imported character immediately.
|
|
1931
|
+
|
|
1932
|
+
Params: { info (the decomposer manifest: tags, xyxy rects, depth_median draw order, frame_size [h,w]), images ({tag: PNG data URL or CORS-reachable https URL} — data URLs are the reliable path; ~20 layers of base64 is a big call, use URLs when you can), position?, scale?, name? }
|
|
1933
|
+
Returns: { groupId, parts, roles, rolesWired, warnings }
|
|
1934
|
+
|
|
1935
|
+
CHECK rolesWired: a character importing with 0 roles renders perfectly and silently refuses to animate — that number is the difference between a picture and a puppet.`,
|
|
1936
|
+
inputSchema: {
|
|
1937
|
+
type: 'object',
|
|
1938
|
+
properties: {
|
|
1939
|
+
info: { type: 'object', description: 'Decomposer manifest (See-through meta.json contents).' },
|
|
1940
|
+
images: { type: 'object', description: 'Layer tag → PNG data URL (or https URL, CORS permitting).' },
|
|
1941
|
+
position: { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } }, description: 'Placement (default canvas center).' },
|
|
1942
|
+
scale: { type: 'number', description: 'Uniform scale after the frame→canvas fit.' },
|
|
1943
|
+
name: { type: 'string', description: 'Group name.' },
|
|
1944
|
+
},
|
|
1945
|
+
required: ['info', 'images'],
|
|
1946
|
+
},
|
|
1947
|
+
},
|
|
1948
|
+
{
|
|
1949
|
+
name: 'pinepaper_game',
|
|
1950
|
+
annotations: {
|
|
1951
|
+
title: 'Game Logic (Pathfinding / Tilemaps)',
|
|
1952
|
+
readOnlyHint: false,
|
|
1953
|
+
destructiveHint: false,
|
|
1954
|
+
idempotentHint: true,
|
|
1955
|
+
openWorldHint: false,
|
|
1956
|
+
},
|
|
1957
|
+
description: `GAME-LOGIC primitives as pure computation: A* pathfinding and tilemap boards. Nothing here draws — the outputs plug into the surfaces that do.
|
|
1958
|
+
|
|
1959
|
+
ACTIONS:
|
|
1960
|
+
- pathfind: { grid: { cols, rows, cellSize?, origin?, blocked?|obstacles? }, start, goal, diagonal? } → { path: [{x,y}], waypoints }
|
|
1961
|
+
start/goal are WORLD coordinates (what items have). obstacles is world rects — pass item bounds and the walls block the board. Deterministic (fixed tie-breaking): same board, same route, every time — replays hold. Diagonal never cuts corners. FEED path DIRECTLY into pinepaper_add_relation moves_along_path (params.path) or a keyframe track: that is the movement system, this is only the route.
|
|
1962
|
+
- create_tilemap: { cols, rows, tileSize? (32), origin?, tileset? ([{id (1-based; 0=empty), solid?, name?, fill?}]), fills? ([{x0,y0,x1,y1,tileId}] tile-space rects, painted in order) } → { map, grid, collisionRects, rectCount }
|
|
1963
|
+
map is DATA — persist it in item.data or the project document; render tiles only if the design wants them drawn (a 60×40 board should not be 2,400 canvas items for pure logic). grid feeds pathfind unchanged. collisionRects are greedy-merged (a 60-tile wall is ONE rect) — hand them to pinepaper_physics as static bodies.
|
|
1964
|
+
|
|
1965
|
+
RECIPE — a playable maze: create_tilemap with wall fills → batch_create colored rects from map data where you want visuals → pathfind player→exit → moves_along_path along the result. Multiplayer sync is deliberately NOT here: it is a live client-runtime protocol (EventSync), not a canvas op.`,
|
|
1966
|
+
inputSchema: {
|
|
1967
|
+
type: 'object',
|
|
1968
|
+
properties: {
|
|
1969
|
+
action: { type: 'string', enum: ['pathfind', 'create_tilemap'], description: 'Game operation' },
|
|
1970
|
+
grid: { type: 'object', properties: { cols: { type: 'number' }, rows: { type: 'number' }, cellSize: { type: 'number' }, origin: { type: 'object' }, blocked: { type: 'array', items: { type: 'number' } }, obstacles: { type: 'array', items: { type: 'object' } } }, description: 'pathfind: the board.' },
|
|
1971
|
+
start: { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } }, description: 'pathfind: start (world coords).' },
|
|
1972
|
+
goal: { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } }, description: 'pathfind: goal (world coords).' },
|
|
1973
|
+
diagonal: { type: 'boolean', description: 'pathfind: 8-way movement (default true).' },
|
|
1974
|
+
cols: { type: 'number', description: 'create_tilemap: board width in tiles.' },
|
|
1975
|
+
rows: { type: 'number', description: 'create_tilemap: board height in tiles.' },
|
|
1976
|
+
tileSize: { type: 'number', description: 'create_tilemap: world units per tile (default 32).' },
|
|
1977
|
+
origin: { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } }, description: 'create_tilemap: world position of tile (0,0).' },
|
|
1978
|
+
tileset: { type: 'array', items: { type: 'object' }, description: 'create_tilemap: [{id, solid?, name?, fill?}] — 1-based ids.' },
|
|
1979
|
+
fills: { type: 'array', items: { type: 'object' }, description: 'create_tilemap: [{x0,y0,x1,y1,tileId}] painted in order.' },
|
|
1980
|
+
},
|
|
1981
|
+
required: ['action'],
|
|
1982
|
+
},
|
|
1983
|
+
},
|
|
1984
|
+
{
|
|
1985
|
+
name: 'pinepaper_world3d',
|
|
1986
|
+
annotations: {
|
|
1987
|
+
title: '3D World (Terrain / Actors / Camera)',
|
|
1988
|
+
readOnlyHint: false,
|
|
1989
|
+
destructiveHint: false,
|
|
1990
|
+
idempotentHint: false,
|
|
1991
|
+
openWorldHint: false,
|
|
1992
|
+
},
|
|
1993
|
+
description: `A real 3D WORLD under the canvas — depth-buffered terrain, sky, sun shadows, an addressable actor stage and a directed camera. ADDITIVE: Paper.js keeps every vector item, hit-test and export path; the world composites underneath the (transparent) Paper canvas.
|
|
1994
|
+
|
|
1995
|
+
ACTIONS:
|
|
1996
|
+
- create: { spec? ('forest' | 'snowMountain' | 'field' | 'jungle' | a full spec object), character? (true/config → a walkable character) } → { configurableKeys }
|
|
1997
|
+
- describe: {} → the ENGINE'S OWN parameter schema — every world parameter with type, range and description. Call this before configure; the tool does not restate the schema, so it cannot drift from the engine.
|
|
1998
|
+
- configure: { patch } — live deep-merged change to anything describe lists (colours, fog, sun, terrain amplitude/ridge, seed…). Schema-validated: a wrong key errors NAMING the right one, never a silent no-op.
|
|
1999
|
+
- add_actor: { actorId?, x, z, height?, sprite? (a canvas item id), live? } → { actorId }. live: true re-rasterizes the item as it animates — a RIGGED character (walk cycle, expressions) PERFORMS in the world instead of standing there as a photograph of itself.
|
|
2000
|
+
- remove_actor / list_actors / set_actor_pose: { actorId, pose: { x?, z?, angle? } } — the setter a timeline, sequencer or agent drives frame by frame.
|
|
2001
|
+
- set_camera: { camera: { mode: 'follow' | 'fixed' | 'orbit', target? (actorId), radius?, speed?, eye?, lookAt? } }
|
|
2002
|
+
- add_object: { object: { x, z, height?, color? } } (y defaults to sitting ON the terrain) / remove_object: { objectId }
|
|
2003
|
+
- remove_world: {} — tears the world down; the Paper canvas was always its own layer and is untouched.
|
|
2004
|
+
|
|
2005
|
+
RECIPE — a character walking through a forest: create {spec:'forest'} → import/rig a character on the canvas → add_actor {sprite: itemId, live: true} → set_camera {mode:'follow', target} → drive set_actor_pose over time (or let the built-in character control walk).`,
|
|
2006
|
+
inputSchema: {
|
|
2007
|
+
type: 'object',
|
|
2008
|
+
properties: {
|
|
2009
|
+
action: { type: 'string', enum: ['create', 'describe', 'configure', 'add_actor', 'remove_actor', 'list_actors', 'set_actor_pose', 'set_camera', 'add_object', 'remove_object', 'remove_world'], description: 'World operation' },
|
|
2010
|
+
spec: { description: "create: preset id ('forest'|'snowMountain'|'field'|'jungle') or a full spec object." },
|
|
2011
|
+
character: { description: 'create: include the walkable character (boolean or config).' },
|
|
2012
|
+
patch: { type: 'object', description: 'configure: partial world spec, validated against describe.' },
|
|
2013
|
+
actorId: { type: 'string', description: 'Actor id.' },
|
|
2014
|
+
x: { type: 'number', description: 'add_actor: world x.' },
|
|
2015
|
+
z: { type: 'number', description: 'add_actor: world z.' },
|
|
2016
|
+
height: { type: 'number', description: 'add_actor: world-unit size.' },
|
|
2017
|
+
sprite: { type: 'string', description: 'add_actor: canvas item id for the sprite.' },
|
|
2018
|
+
live: { type: 'boolean', description: 'add_actor: re-rasterize as the item animates.' },
|
|
2019
|
+
pose: { type: 'object', description: 'set_actor_pose: { x?, z?, angle? }.' },
|
|
2020
|
+
camera: { type: 'object', description: "set_camera: { mode: follow|fixed|orbit, target?, radius?, speed?, eye?, lookAt? }." },
|
|
2021
|
+
object: { type: 'object', description: 'add_object: { x, z, height?, color?, y? }.' },
|
|
2022
|
+
objectId: { type: 'string', description: 'remove_object: object id.' },
|
|
2023
|
+
},
|
|
2024
|
+
required: ['action'],
|
|
2025
|
+
},
|
|
2026
|
+
},
|
|
1832
2027
|
{
|
|
1833
2028
|
name: 'pinepaper_rigging',
|
|
1834
2029
|
annotations: {
|
|
@@ -1851,12 +2046,20 @@ ACTIONS:
|
|
|
1851
2046
|
- set_target_path: { skeletonId, chainId, waypoints[] ({x,y,out?,in?} bezier tangents), duration?, loop? } — the IK effector travels an ARC, not a straight line
|
|
1852
2047
|
- save_pose: { skeletonId, name? } → { poseId } (snapshot current bone angles)
|
|
1853
2048
|
- save_shape_key: { skeletonId, name? } → { shapeKeyId } (per-item visual delta for facial-style blends)
|
|
2049
|
+
- import_bvh: { bvhText, view? (side|front), fps? (15; 0 = every frame), height? (320px), rootPosition?, name? } → { skeletonId, poses, duration, warnings } — build a NEW rig from a BVH mocap clip (CMU / Mixamo), stick figure included. Root translation returns as a separate track rather than baked into poses.
|
|
2050
|
+
- retarget_bvh: { bvhText, skeletonId, fps?, name? } → { skeletonId, matched, unmatchedSource, unmatchedTarget, poses, duration } — drive an EXISTING rig with a BVH clip, bones matched by name. CHECK matched/unmatched in the result: a retarget that matched 2 of 15 bones "succeeds" and looks broken.
|
|
2051
|
+
- import_spine: { spineJson, rootPosition?, name? } → { skeletonId, bones, placeholders, animations, warnings } — import a Spine editor JSON export (bones, poses, attachment placeholders).
|
|
1854
2052
|
|
|
1855
2053
|
BREAKDOWN POSES (S12): a breakdown keyframe shapes the ARC + SPACING between key poses (favor biases spacing; boneOffsets lag bones so a tip drags its root — follow-through; movingHold keeps a hold alive). This is the difference between mechanical and lifelike motion.`,
|
|
1856
2054
|
inputSchema: {
|
|
1857
2055
|
type: 'object',
|
|
1858
2056
|
properties: {
|
|
1859
|
-
action: { type: 'string', enum: ['create_skeleton', 'add_bone', 'attach_item', 'create_ik_chain', 'add_pose_keyframe', 'set_target_path', 'save_pose', 'save_shape_key'], description: 'Rigging operation' },
|
|
2057
|
+
action: { type: 'string', enum: ['create_skeleton', 'add_bone', 'attach_item', 'create_ik_chain', 'add_pose_keyframe', 'set_target_path', 'save_pose', 'save_shape_key', 'import_bvh', 'retarget_bvh', 'import_spine'], description: 'Rigging operation' },
|
|
2058
|
+
bvhText: { type: 'string', description: 'import_bvh / retarget_bvh: the .bvh file contents.' },
|
|
2059
|
+
spineJson: { type: 'string', description: 'import_spine: the Spine JSON export, as a string.' },
|
|
2060
|
+
view: { type: 'string', enum: ['side', 'front'], description: 'import_bvh: 3D→2D projection plane. CMU walks read best from the side (default).' },
|
|
2061
|
+
fps: { type: 'number', description: 'import_bvh / retarget_bvh: pose sampling rate (default 15; 0 keeps every frame).' },
|
|
2062
|
+
height: { type: 'number', description: 'import_bvh: skeleton height on canvas, px (default 320).' },
|
|
1860
2063
|
skeletonId: { type: 'string', description: 'Skeleton id — every action except create_skeleton.' },
|
|
1861
2064
|
boneId: { type: 'string', description: 'Bone id — attach_item.' },
|
|
1862
2065
|
itemId: { type: 'string', description: 'Canvas item id — attach_item.' },
|
|
@@ -3182,9 +3385,14 @@ ACTIONS:
|
|
|
3182
3385
|
idempotentHint: false,
|
|
3183
3386
|
openWorldHint: false,
|
|
3184
3387
|
},
|
|
3185
|
-
description: `Transform items: nudge position, flip, or change z-order.
|
|
3388
|
+
description: `Transform items: fit to the canvas, nudge position, flip, or change z-order.
|
|
3186
3389
|
|
|
3187
3390
|
ACTIONS:
|
|
3391
|
+
- fit: Scale + centre an item to the EXPORT FRAME. Params: itemId, mode (contain|cover, default contain).
|
|
3392
|
+
Use this after uploading a video or image — an uploaded clip lands at its NATIVE size, so a 640x360
|
|
3393
|
+
source occupies a fraction of a 1920x1080 canvas until you fit it. Accepts a canvas item id OR a
|
|
3394
|
+
media id (vraster_…) returned by pinepaper_media upload_video. 'contain' letterboxes, 'cover' crops.
|
|
3395
|
+
Idempotent: fitting twice is the same as fitting once.
|
|
3188
3396
|
- nudge: Move item by dx/dy offset. Params: itemId, dx, dy
|
|
3189
3397
|
- flip: Mirror item. Params: itemId, direction (horizontal|vertical)
|
|
3190
3398
|
- reorder: Change z-order. Params: itemId, order (bringToFront|sendToBack|moveUp|moveDown)`,
|
|
@@ -3193,9 +3401,14 @@ ACTIONS:
|
|
|
3193
3401
|
properties: {
|
|
3194
3402
|
action: {
|
|
3195
3403
|
type: 'string',
|
|
3196
|
-
enum: ['nudge', 'flip', 'reorder'],
|
|
3404
|
+
enum: ['fit', 'nudge', 'flip', 'reorder'],
|
|
3197
3405
|
description: 'Transform action',
|
|
3198
3406
|
},
|
|
3407
|
+
mode: {
|
|
3408
|
+
type: 'string',
|
|
3409
|
+
enum: ['contain', 'cover'],
|
|
3410
|
+
description: 'For fit: contain (whole item visible, may letterbox) or cover (fill frame, crop overflow)',
|
|
3411
|
+
},
|
|
3199
3412
|
itemId: { type: 'string', description: 'Target item ID' },
|
|
3200
3413
|
dx: { type: 'number', description: 'Horizontal offset (for nudge)' },
|
|
3201
3414
|
dy: { type: 'number', description: 'Vertical offset (for nudge)' },
|
|
@@ -3213,6 +3426,333 @@ ACTIONS:
|
|
|
3213
3426
|
required: ['action'],
|
|
3214
3427
|
},
|
|
3215
3428
|
},
|
|
3429
|
+
{
|
|
3430
|
+
name: 'pinepaper_compose',
|
|
3431
|
+
annotations: {
|
|
3432
|
+
title: 'Compose', readOnlyHint: false, destructiveHint: false,
|
|
3433
|
+
idempotentHint: false, openWorldHint: false,
|
|
3434
|
+
},
|
|
3435
|
+
description: `Arrange items into a NAMED COMPOSITION and film it — "here are four photos, lay them out
|
|
3436
|
+
nicely and give me a clip".
|
|
3437
|
+
|
|
3438
|
+
ACTIONS:
|
|
3439
|
+
- list_patterns: ~25 patterns with slot counts and when-to-use definitions. CALL THIS FIRST and
|
|
3440
|
+
CHOOSE FROM THE LIST — do not fall back to a remembered favourite. The catalogue spans media
|
|
3441
|
+
grids, no-crop mosaics, typographic cards (title, quote, magazine cover, before/after), vector
|
|
3442
|
+
slots (badge, logo lockup), and video shapes (lower-third, picture-in-picture).
|
|
3443
|
+
- list_reveals: temporal reveals (how items ARRIVE over time — staggers, cascades, beat-driven
|
|
3444
|
+
hard cuts). A composition without a reveal is a still.
|
|
3445
|
+
- list_styles: visual style bundles (tone treatments + spacing + the motion each implies).
|
|
3446
|
+
- apply: Arrange items. Params: pattern, itemIds (IN SLOT ORDER — the order is the composition),
|
|
3447
|
+
reveal + revealOptions ({ duration, beats }), style, text (fills the pattern's own text slots),
|
|
3448
|
+
assets (SVG for vector slots), audio + grid (beat-sync the cuts to a track), treatment,
|
|
3449
|
+
applyCamera, craft.
|
|
3450
|
+
- list_treatments / set_treatment: camera treatments; re-film via rootId.
|
|
3451
|
+
|
|
3452
|
+
PICKING WELL: match the pattern to the CONTENT (count, aspect ratios, whether type or video is
|
|
3453
|
+
involved) — the list_patterns definitions state this per pattern. Vary reveals and styles across a
|
|
3454
|
+
session; identical choices on every scene is a sign the list was never consulted.
|
|
3455
|
+
|
|
3456
|
+
WHAT THIS RETURNS, AND WHY IT MATTERS: apply positions ONE root and wires structural relations
|
|
3457
|
+
(beside / below / on_top_of) for the rest — it does not set a coordinate per item. The composition
|
|
3458
|
+
therefore stays live: move the root and everything follows, swap in a taller image and its
|
|
3459
|
+
neighbours re-flow. It also records pp:composedAs / pp:fillsSlot so the scene can be queried and
|
|
3460
|
+
re-filmed later.
|
|
3461
|
+
|
|
3462
|
+
Prefer this over positioning images yourself. Hand-placed coordinates are what pinepaper_lint_scene
|
|
3463
|
+
flags as a badly-composed scene, and they do not survive an artboard change.`,
|
|
3464
|
+
inputSchema: {
|
|
3465
|
+
type: 'object',
|
|
3466
|
+
properties: {
|
|
3467
|
+
action: { type: 'string', enum: ['list_patterns', 'list_treatments', 'apply', 'set_treatment'] },
|
|
3468
|
+
pattern: { type: 'string', description: 'Pattern key from list_patterns' },
|
|
3469
|
+
itemIds: { type: 'array', items: { type: 'string' }, description: 'Items in slot order' },
|
|
3470
|
+
treatment: { type: 'string', description: 'Camera treatment key' },
|
|
3471
|
+
rootId: { type: 'string', description: 'Composition root (from apply) — for set_treatment' },
|
|
3472
|
+
applyCamera: { type: 'boolean', description: 'false to arrange without compiling a camera track' },
|
|
3473
|
+
loop: { type: 'boolean' },
|
|
3474
|
+
craft: { type: 'object', description: 'Override craft ratios (gutter, margin…), as fractions of the short canvas edge' },
|
|
3475
|
+
},
|
|
3476
|
+
required: ['action'],
|
|
3477
|
+
},
|
|
3478
|
+
},
|
|
3479
|
+
{
|
|
3480
|
+
name: 'pinepaper_brand_kit',
|
|
3481
|
+
annotations: {
|
|
3482
|
+
title: 'Brand Kit', readOnlyHint: false, destructiveHint: false,
|
|
3483
|
+
idempotentHint: true, openWorldHint: false,
|
|
3484
|
+
},
|
|
3485
|
+
description: `Apply a brand kit — colours, fonts and roles — across the scene.
|
|
3486
|
+
|
|
3487
|
+
ACTIONS:
|
|
3488
|
+
- plan: Report what WOULD change (count, per-role breakdown, WCAG contrast audit) WITHOUT touching
|
|
3489
|
+
the canvas. Call this first on an unfamiliar scene; a whole-scene recolour is hard to eyeball afterwards.
|
|
3490
|
+
- apply: Commit it. A history snapshot is taken FIRST, so one undo puts everything back.
|
|
3491
|
+
|
|
3492
|
+
KIT SHAPE — only name and colors.primary are required:
|
|
3493
|
+
{ "name": "Acme", "colors": { "primary": "#e11d48", "secondary": "#0ea5e9",
|
|
3494
|
+
"accent": "#f59e0b", "background": "#ffffff", "text": "#111111" },
|
|
3495
|
+
"fonts": { "heading": "Inter", "body": "Inter" } }
|
|
3496
|
+
Missing roles are filled from what is present (secondary←primary, accent←secondary, background←white,
|
|
3497
|
+
text←whatever is readable on the background) and reported as warnings, so a kit with just a primary is
|
|
3498
|
+
a valid thing to send.
|
|
3499
|
+
|
|
3500
|
+
Items are matched to roles by their current styling. Pass selectionOnly to restrict to the selection.
|
|
3501
|
+
Both actions return a WCAG contrast audit, so you can see failing pairs before shipping them.`,
|
|
3502
|
+
inputSchema: {
|
|
3503
|
+
type: 'object',
|
|
3504
|
+
properties: {
|
|
3505
|
+
action: { type: 'string', enum: ['plan', 'apply'], description: 'plan (dry run) or apply' },
|
|
3506
|
+
kit: {
|
|
3507
|
+
type: 'object',
|
|
3508
|
+
description: 'Brand kit. Requires name and colors.primary; other roles are inferred.',
|
|
3509
|
+
properties: {
|
|
3510
|
+
name: { type: 'string' },
|
|
3511
|
+
colors: {
|
|
3512
|
+
type: 'object',
|
|
3513
|
+
properties: {
|
|
3514
|
+
primary: { type: 'string' },
|
|
3515
|
+
secondary: { type: 'string' },
|
|
3516
|
+
accent: { type: 'string' },
|
|
3517
|
+
background: { type: 'string' },
|
|
3518
|
+
text: { type: 'string' },
|
|
3519
|
+
},
|
|
3520
|
+
required: ['primary'],
|
|
3521
|
+
},
|
|
3522
|
+
fonts: {
|
|
3523
|
+
type: 'object',
|
|
3524
|
+
properties: { heading: { type: 'string' }, body: { type: 'string' } },
|
|
3525
|
+
},
|
|
3526
|
+
},
|
|
3527
|
+
required: ['name', 'colors'],
|
|
3528
|
+
},
|
|
3529
|
+
selectionOnly: { type: 'boolean', description: 'Restrict to current selection' },
|
|
3530
|
+
},
|
|
3531
|
+
required: ['action', 'kit'],
|
|
3532
|
+
},
|
|
3533
|
+
},
|
|
3534
|
+
{
|
|
3535
|
+
name: 'pinepaper_component',
|
|
3536
|
+
annotations: {
|
|
3537
|
+
title: 'Component', readOnlyHint: false, destructiveHint: false,
|
|
3538
|
+
idempotentHint: false, openWorldHint: false,
|
|
3539
|
+
},
|
|
3540
|
+
description: `Reusable components with per-instance overrides — a master and its instances.
|
|
3541
|
+
|
|
3542
|
+
ACTIONS:
|
|
3543
|
+
- define: Turn items into a component master. Params: itemIds[], name
|
|
3544
|
+
- list: All defined components
|
|
3545
|
+
- instantiate: Place an instance. Params: componentId, position, overrides
|
|
3546
|
+
- set_override: Change ONE part of ONE instance without detaching it. Params: instanceId, componentKey, prop, value
|
|
3547
|
+
- sync: Push the master's current state to every instance (overrides are preserved). Params: componentId
|
|
3548
|
+
- update_from_instance: Promote an instance's edits back into the master. Params: instanceId
|
|
3549
|
+
- detach: Make an instance an ordinary independent item. Params: instanceId
|
|
3550
|
+
|
|
3551
|
+
Overrides are the point: an instance can differ from its master and still receive master updates.`,
|
|
3552
|
+
inputSchema: {
|
|
3553
|
+
type: 'object',
|
|
3554
|
+
properties: {
|
|
3555
|
+
action: {
|
|
3556
|
+
type: 'string',
|
|
3557
|
+
enum: ['define', 'list', 'instantiate', 'set_override', 'sync', 'update_from_instance', 'detach'],
|
|
3558
|
+
},
|
|
3559
|
+
itemIds: { type: 'array', items: { type: 'string' }, description: 'Items to define a component from' },
|
|
3560
|
+
componentId: { type: 'string' },
|
|
3561
|
+
instanceId: { type: 'string' },
|
|
3562
|
+
name: { type: 'string' },
|
|
3563
|
+
position: {
|
|
3564
|
+
type: 'object',
|
|
3565
|
+
properties: { x: { type: 'number' }, y: { type: 'number' } },
|
|
3566
|
+
},
|
|
3567
|
+
componentKey: { type: 'string', description: 'Which part of the component to override' },
|
|
3568
|
+
prop: { type: 'string', description: 'Property to override, e.g. fillColor or content' },
|
|
3569
|
+
value: { description: 'Override value' },
|
|
3570
|
+
overrides: { type: 'object', description: 'Overrides applied at instantiate time' },
|
|
3571
|
+
},
|
|
3572
|
+
required: ['action'],
|
|
3573
|
+
},
|
|
3574
|
+
},
|
|
3575
|
+
{
|
|
3576
|
+
name: 'pinepaper_artboard',
|
|
3577
|
+
annotations: {
|
|
3578
|
+
title: 'Artboard', readOnlyHint: false, destructiveHint: false,
|
|
3579
|
+
idempotentHint: true, openWorldHint: false,
|
|
3580
|
+
},
|
|
3581
|
+
description: `Resize the artboard and control how items reflow when it changes.
|
|
3582
|
+
|
|
3583
|
+
ACTIONS:
|
|
3584
|
+
- list_presets: Named artboard sizes (social, print, web…)
|
|
3585
|
+
- set: Resize. Params: preset OR width+height. Items reflow according to their constraints.
|
|
3586
|
+
- set_constraints: Declare how ONE item behaves on resize. Params: itemId, horizontal, vertical
|
|
3587
|
+
(left|right|center|scale|both). Without constraints an item just scales with the frame, which is
|
|
3588
|
+
wrong for things that should stay pinned to an edge.
|
|
3589
|
+
|
|
3590
|
+
Use this to retarget a finished design to another aspect ratio instead of rebuilding it.`,
|
|
3591
|
+
inputSchema: {
|
|
3592
|
+
type: 'object',
|
|
3593
|
+
properties: {
|
|
3594
|
+
action: { type: 'string', enum: ['list_presets', 'set', 'set_constraints'] },
|
|
3595
|
+
preset: { type: 'string', description: 'Preset key from list_presets' },
|
|
3596
|
+
width: { type: 'number' },
|
|
3597
|
+
height: { type: 'number' },
|
|
3598
|
+
itemId: { type: 'string' },
|
|
3599
|
+
horizontal: { type: 'string', description: 'left | right | center | scale | both' },
|
|
3600
|
+
vertical: { type: 'string', description: 'top | bottom | center | scale | both' },
|
|
3601
|
+
},
|
|
3602
|
+
required: ['action'],
|
|
3603
|
+
},
|
|
3604
|
+
},
|
|
3605
|
+
{
|
|
3606
|
+
name: 'pinepaper_comment',
|
|
3607
|
+
annotations: {
|
|
3608
|
+
title: 'Comment', readOnlyHint: false, destructiveHint: false,
|
|
3609
|
+
idempotentHint: false, openWorldHint: false,
|
|
3610
|
+
},
|
|
3611
|
+
description: `Notes pinned to a place and a moment — review feedback on a scene.
|
|
3612
|
+
|
|
3613
|
+
ACTIONS:
|
|
3614
|
+
- add: Params: text, plus AT LEAST ONE anchor — itemId (the pin follows the item), x+y (a point on the
|
|
3615
|
+
canvas), or time (a moment on the timeline). They combine: "the exit is too fast" on item_4 at 4.2s.
|
|
3616
|
+
- list: Comments with their CURRENT resolved position. Params: time (only those visible at that
|
|
3617
|
+
moment), includeResolved
|
|
3618
|
+
- resolve: Mark done. Params: id, resolved. The comment is KEPT — the thread is the record.
|
|
3619
|
+
- delete: Remove outright. Params: id
|
|
3620
|
+
|
|
3621
|
+
An unanchored comment is refused: a note with no anchor is a text file with extra steps. Deleting an
|
|
3622
|
+
item ORPHANS its comments rather than dropping them, and they report themselves as orphaned.`,
|
|
3623
|
+
inputSchema: {
|
|
3624
|
+
type: 'object',
|
|
3625
|
+
properties: {
|
|
3626
|
+
action: { type: 'string', enum: ['add', 'list', 'resolve', 'delete'] },
|
|
3627
|
+
id: { type: 'string' },
|
|
3628
|
+
text: { type: 'string' },
|
|
3629
|
+
author: { type: 'string' },
|
|
3630
|
+
itemId: { type: 'string', description: 'Anchor to an item — the pin follows it' },
|
|
3631
|
+
x: { type: 'number' },
|
|
3632
|
+
y: { type: 'number' },
|
|
3633
|
+
time: { type: 'number', description: 'Anchor to a moment (seconds)' },
|
|
3634
|
+
resolved: { type: 'boolean' },
|
|
3635
|
+
includeResolved: { type: 'boolean' },
|
|
3636
|
+
},
|
|
3637
|
+
required: ['action'],
|
|
3638
|
+
},
|
|
3639
|
+
},
|
|
3640
|
+
{
|
|
3641
|
+
name: 'pinepaper_provenance',
|
|
3642
|
+
annotations: {
|
|
3643
|
+
title: 'Provenance', readOnlyHint: true, destructiveHint: false,
|
|
3644
|
+
idempotentHint: true, openWorldHint: false,
|
|
3645
|
+
},
|
|
3646
|
+
description: `Where an item came from and what depends on it.
|
|
3647
|
+
|
|
3648
|
+
ACTIONS:
|
|
3649
|
+
- get: Who created and last touched this item (human vs agent), and when. Params: itemId
|
|
3650
|
+
- lineage: Walk back to the roots — the chain of imports/derivations that produced it. Params: itemId
|
|
3651
|
+
- dependents: What breaks if you delete it. Params: itemId — check this BEFORE removing something
|
|
3652
|
+
other items were built from.
|
|
3653
|
+
- record: Note that this item derives from a source. Params: itemId, kind, sourceRef, meta`,
|
|
3654
|
+
inputSchema: {
|
|
3655
|
+
type: 'object',
|
|
3656
|
+
properties: {
|
|
3657
|
+
action: { type: 'string', enum: ['get', 'lineage', 'dependents', 'record'] },
|
|
3658
|
+
itemId: { type: 'string' },
|
|
3659
|
+
kind: { type: 'string', description: 'Lineage kind, e.g. traced | imported | derived' },
|
|
3660
|
+
sourceRef: { type: 'string', description: 'What it came from (item id, asset id, URL)' },
|
|
3661
|
+
meta: { type: 'object' },
|
|
3662
|
+
},
|
|
3663
|
+
required: ['action', 'itemId'],
|
|
3664
|
+
},
|
|
3665
|
+
},
|
|
3666
|
+
{
|
|
3667
|
+
name: 'pinepaper_scene_diff',
|
|
3668
|
+
annotations: {
|
|
3669
|
+
title: 'Scene Diff', readOnlyHint: true, destructiveHint: false,
|
|
3670
|
+
idempotentHint: true, openWorldHint: false,
|
|
3671
|
+
},
|
|
3672
|
+
description: `What changed between two states of the scene — added, removed and modified items.
|
|
3673
|
+
|
|
3674
|
+
ACTIONS:
|
|
3675
|
+
- history: Compare two undo states. Params: indexA, indexB (see pinepaper_history get_state for the range)
|
|
3676
|
+
- version: Compare the LIVE scene against a saved version. Params: versionId
|
|
3677
|
+
|
|
3678
|
+
Use this to check your own work: snapshot the history index before a batch, run it, then diff to
|
|
3679
|
+
confirm the calls produced the scene you intended rather than assuming a success result means the
|
|
3680
|
+
graph is right. Also the safe way to preview a destructive change — diff first, then decide.`,
|
|
3681
|
+
inputSchema: {
|
|
3682
|
+
type: 'object',
|
|
3683
|
+
properties: {
|
|
3684
|
+
action: { type: 'string', enum: ['history', 'version'] },
|
|
3685
|
+
indexA: { type: 'number' },
|
|
3686
|
+
indexB: { type: 'number' },
|
|
3687
|
+
versionId: { type: 'string' },
|
|
3688
|
+
},
|
|
3689
|
+
required: ['action'],
|
|
3690
|
+
},
|
|
3691
|
+
},
|
|
3692
|
+
{
|
|
3693
|
+
name: 'pinepaper_audio_beats',
|
|
3694
|
+
annotations: {
|
|
3695
|
+
title: 'Audio Beats', readOnlyHint: false, destructiveHint: false,
|
|
3696
|
+
idempotentHint: false, openWorldHint: false,
|
|
3697
|
+
},
|
|
3698
|
+
description: `Detect beats in audio and bake an item's animation onto them.
|
|
3699
|
+
|
|
3700
|
+
ACTIONS:
|
|
3701
|
+
- analyze: Onsets, BPM, confidence and duration. Params: source (asset id, data URL or URL),
|
|
3702
|
+
sensitivity, minGap. Read-only — run it first to see what the track gives you.
|
|
3703
|
+
- animate_to_beat: Write a keyframe track that pulses on the beat. Params: itemId, plus EITHER
|
|
3704
|
+
source (analyze first) OR beats[] (times you already have). grid:true uses an even pulse from the
|
|
3705
|
+
detected tempo instead of the raw onsets — the difference between following the rhythm and
|
|
3706
|
+
following the beat. property (default scale), base, accent, decay shape the hit.
|
|
3707
|
+
|
|
3708
|
+
Analysis runs ONCE and produces keyframes, deliberately not a live analyser: a keyframe track
|
|
3709
|
+
survives MP4, SVG/SMIL and Lottie export, and gives the same answer on every run. A live analyser
|
|
3710
|
+
node survives none of them.`,
|
|
3711
|
+
inputSchema: {
|
|
3712
|
+
type: 'object',
|
|
3713
|
+
properties: {
|
|
3714
|
+
action: { type: 'string', enum: ['analyze', 'animate_to_beat'] },
|
|
3715
|
+
source: { type: 'string', description: 'Asset id, data URL, or URL' },
|
|
3716
|
+
itemId: { type: 'string' },
|
|
3717
|
+
beats: { type: 'array', items: { type: 'number' }, description: 'Beat times (s) you already have' },
|
|
3718
|
+
grid: { type: 'boolean', description: 'Even pulse from the tempo instead of raw onsets' },
|
|
3719
|
+
property: { type: 'string', description: 'Property to pulse (default scale)' },
|
|
3720
|
+
base: { type: 'number' },
|
|
3721
|
+
accent: { type: 'number' },
|
|
3722
|
+
decay: { type: 'number' },
|
|
3723
|
+
sensitivity: { type: 'number', description: 'Lower it if no beats are detected' },
|
|
3724
|
+
minGap: { type: 'number', description: 'Minimum seconds between onsets' },
|
|
3725
|
+
},
|
|
3726
|
+
required: ['action'],
|
|
3727
|
+
},
|
|
3728
|
+
},
|
|
3729
|
+
{
|
|
3730
|
+
name: 'pinepaper_template_params',
|
|
3731
|
+
annotations: {
|
|
3732
|
+
title: 'Template Params', readOnlyHint: false, destructiveHint: false,
|
|
3733
|
+
idempotentHint: true, openWorldHint: false,
|
|
3734
|
+
},
|
|
3735
|
+
description: `Templates that take typed inputs — the same scene rebuilt for your text and colours.
|
|
3736
|
+
|
|
3737
|
+
ACTIONS:
|
|
3738
|
+
- get: The template's declared params with their defaults, types and ranges. Params: templateId.
|
|
3739
|
+
Call this first to discover what a template will accept; most templates declare none.
|
|
3740
|
+
- apply: Load the template with your values. Params: templateId, params
|
|
3741
|
+
|
|
3742
|
+
Values are COERCED to their declared type and range-checked, and anything uncoercible falls back to
|
|
3743
|
+
the default with a warning rather than reaching the template — so a number param cannot arrive as the
|
|
3744
|
+
string "5" and silently produce "55" instead of 10. The same declaration drives the editor panel and
|
|
3745
|
+
this tool, so a control cannot exist that an agent cannot set.`,
|
|
3746
|
+
inputSchema: {
|
|
3747
|
+
type: 'object',
|
|
3748
|
+
properties: {
|
|
3749
|
+
action: { type: 'string', enum: ['get', 'apply'] },
|
|
3750
|
+
templateId: { type: 'string' },
|
|
3751
|
+
params: { type: 'object', description: 'key → value, coerced to the declared types' },
|
|
3752
|
+
},
|
|
3753
|
+
required: ['action', 'templateId'],
|
|
3754
|
+
},
|
|
3755
|
+
},
|
|
3216
3756
|
{
|
|
3217
3757
|
name: 'pinepaper_history',
|
|
3218
3758
|
annotations: {
|
|
@@ -3257,8 +3797,17 @@ ACTIONS:
|
|
|
3257
3797
|
ACTIONS:
|
|
3258
3798
|
- apply: Apply a single filter. Params: itemId, filterName, params
|
|
3259
3799
|
- chain: Apply multiple filters in sequence. Params: itemId, filters (array of {name, params})
|
|
3800
|
+
- analyze_palette: { itemId, maxSwatches? (8) } → { swatches: [{hex, share}…] largest-area-first } — READ an image's palette. The natural loop: analyze image A → recolor_palette (or the paletteMap filter) on image B with A's hexes.
|
|
3801
|
+
- recolor_palette: { itemId, mapping ({from,to}[] or {'#old':'#new'}), amount?, preserveShading? } — swap colours, keep the shading (GPU).
|
|
3260
3802
|
|
|
3261
|
-
Available filters (GPU raster set):
|
|
3803
|
+
Available filters (GPU raster set):
|
|
3804
|
+
- Color: grayscale, sepia, brightness, contrast, saturation, invert, posterize (levels), hsl (hue/saturation/lightness), colorTint (color, intensity, blendMode), colorMatrix (matrix), duotone (shadow, highlight, mix), paletteMap (swatches[], amount, preserveShading — recolor to a fixed palette)
|
|
3805
|
+
- Stylize: vignette (intensity, radius), edgeDetect, halftoneDots (size, angle), halftoneCMYK (size), dither (levels), grain (amount, seed), scanlines (intensity, period), chromaticAberration (amount), blur
|
|
3806
|
+
- Light (additive): bloom (amount, threshold, radius), glow, halation (warm-fringed bloom; color), lightShafts (amount, threshold, decay, density, x, y, color — volumetric rays from a point)
|
|
3807
|
+
- Keying: lumaKey (threshold, softness, invert)
|
|
3808
|
+
- SECOND-INPUT (params.map = another item's id, resolved to its pixels): displace (amount, dispersion, map, mapChannel — the map's brightness pushes pixels), refract (displace with per-channel dispersion — glass), trackMatte (channel, invert, strength — prefer pinepaper_media apply_track_matte for the live version), datamosh (amount, block, map? — self-moshes without a map)
|
|
3809
|
+
|
|
3810
|
+
RECIPE — grunge poster: chain [grain, scanlines, vignette]. Glass header: refract with map = a gradient item.
|
|
3262
3811
|
(Scene-wide filters — sharpen/emboss/noise/vintage etc. — are a different surface: use pinepaper_add_filter.)`,
|
|
3263
3812
|
inputSchema: {
|
|
3264
3813
|
type: 'object',
|
|
@@ -5897,7 +6446,8 @@ EXAMPLES:
|
|
|
5897
6446
|
- {platform: "instagram"} — auto-selects best format
|
|
5898
6447
|
- {platform: "instagram", format: "mp4"} — force MP4
|
|
5899
6448
|
- {platform: "web", format: "svg"} — animated SVG
|
|
5900
|
-
- {platform: "youtube", format: "mp4", framing: "camera"} — render only what the camera frames during the walkthrough
|
|
6449
|
+
- {platform: "youtube", format: "mp4", framing: "camera"} — render only what the camera frames during the walkthrough
|
|
6450
|
+
- {platform: "youtube", format: "mp4", duration: 30, estimateOnly: true} — how big would that be? (renders nothing)`,
|
|
5901
6451
|
inputSchema: {
|
|
5902
6452
|
type: 'object',
|
|
5903
6453
|
properties: {
|
|
@@ -5921,11 +6471,15 @@ EXAMPLES:
|
|
|
5921
6471
|
enum: ['canvas', 'camera'],
|
|
5922
6472
|
description: 'Output framing. "canvas" (default) renders the full canvas. "camera" renders only the first-keyframe viewport of a camera_animates walkthrough — video formats only.',
|
|
5923
6473
|
},
|
|
6474
|
+
estimateOnly: {
|
|
6475
|
+
type: 'boolean',
|
|
6476
|
+
description: 'Preflight only — return the estimated file size for these exact settings and render nothing. Modeled for mp4/webm/gif; png/pdf/svg report confidence "none" (no dimension-based model exists for them).',
|
|
6477
|
+
},
|
|
5924
6478
|
duration: {
|
|
5925
6479
|
type: 'number',
|
|
5926
6480
|
minimum: 0.5,
|
|
5927
6481
|
maximum: 60,
|
|
5928
|
-
description: 'Video duration in seconds for mp4/webm/gif (default 5,
|
|
6482
|
+
description: 'Video duration in seconds for mp4/webm/gif (default 5). Max 60 for mp4/webm; GIF is capped at 15 because it has no bitrate target, so size scales with frames × dimensions — use mp4/webm for longer clips. Static formats ignore this. If you set play_timeline to N seconds, pass the same N here.',
|
|
5929
6483
|
},
|
|
5930
6484
|
},
|
|
5931
6485
|
required: ['platform'],
|