@pinepaper.studio/mcp-server 1.6.27 → 1.6.28

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.
Files changed (72) hide show
  1. package/README.md +85 -2
  2. package/SECURITY.md +4 -3
  3. package/dist/build-info.json +2 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +6 -3
  6. package/dist/index.js.map +1 -1
  7. package/dist/ontology/upstream-vocabulary.d.ts +2 -2
  8. package/dist/ontology/upstream-vocabulary.js +2 -2
  9. package/dist/tools/choreograph.d.ts.map +1 -1
  10. package/dist/tools/choreograph.js +7 -0
  11. package/dist/tools/choreograph.js.map +1 -1
  12. package/dist/tools/definitions.d.ts.map +1 -1
  13. package/dist/tools/definitions.js +35 -18
  14. package/dist/tools/definitions.js.map +1 -1
  15. package/dist/tools/engine-lists.d.ts +11 -4
  16. package/dist/tools/engine-lists.d.ts.map +1 -1
  17. package/dist/tools/engine-lists.js +49 -4
  18. package/dist/tools/engine-lists.js.map +1 -1
  19. package/dist/tools/engine-methods.d.ts.map +1 -1
  20. package/dist/tools/engine-methods.js +1 -0
  21. package/dist/tools/engine-methods.js.map +1 -1
  22. package/dist/tools/engine-surface.d.ts +1 -1
  23. package/dist/tools/engine-surface.d.ts.map +1 -1
  24. package/dist/tools/engine-surface.js +16 -4
  25. package/dist/tools/engine-surface.js.map +1 -1
  26. package/dist/tools/export-file-receipt.d.ts +21 -0
  27. package/dist/tools/export-file-receipt.d.ts.map +1 -1
  28. package/dist/tools/export-file-receipt.js +36 -13
  29. package/dist/tools/export-file-receipt.js.map +1 -1
  30. package/dist/tools/handlers/export.d.ts.map +1 -1
  31. package/dist/tools/handlers/export.js +6 -2
  32. package/dist/tools/handlers/export.js.map +1 -1
  33. package/dist/tools/handlers/generate.js +2 -2
  34. package/dist/tools/handlers/generate.js.map +1 -1
  35. package/dist/tools/handlers.d.ts +5 -0
  36. package/dist/tools/handlers.d.ts.map +1 -1
  37. package/dist/tools/handlers.js +13 -0
  38. package/dist/tools/handlers.js.map +1 -1
  39. package/dist/tools/lane.d.ts +7 -3
  40. package/dist/tools/lane.d.ts.map +1 -1
  41. package/dist/tools/lane.js +20 -5
  42. package/dist/tools/lane.js.map +1 -1
  43. package/dist/tools/scene-cache-export.d.ts.map +1 -1
  44. package/dist/tools/scene-cache-export.js +8 -4
  45. package/dist/tools/scene-cache-export.js.map +1 -1
  46. package/dist/tools/shape-params.d.ts +2 -2
  47. package/dist/tools/shape-params.js +2 -2
  48. package/dist/tools/stick-vocabulary.d.ts +1 -1
  49. package/dist/tools/stick-vocabulary.js +1 -1
  50. package/dist/tools/toolkits.d.ts +1 -1
  51. package/dist/tools/toolkits.d.ts.map +1 -1
  52. package/dist/tools/toolkits.js +8 -2
  53. package/dist/tools/toolkits.js.map +1 -1
  54. package/dist/types/code-generator.d.ts.map +1 -1
  55. package/dist/types/code-generator.js +103 -10
  56. package/dist/types/code-generator.js.map +1 -1
  57. package/dist/types/schemas.d.ts +27 -2
  58. package/dist/types/schemas.d.ts.map +1 -1
  59. package/dist/types/schemas.js +7 -23
  60. package/dist/types/schemas.js.map +1 -1
  61. package/dist/utils/asset-paths.d.ts.map +1 -1
  62. package/dist/utils/asset-paths.js +11 -0
  63. package/dist/utils/asset-paths.js.map +1 -1
  64. package/dist/utils/capabilities.d.ts +15 -0
  65. package/dist/utils/capabilities.d.ts.map +1 -0
  66. package/dist/utils/capabilities.js +63 -0
  67. package/dist/utils/capabilities.js.map +1 -0
  68. package/dist/utils/env.d.ts.map +1 -1
  69. package/dist/utils/env.js +2 -1
  70. package/dist/utils/env.js.map +1 -1
  71. package/manifest.json +18 -2
  72. package/package.json +1 -1
@@ -14,7 +14,7 @@ import { TOOL_TAGS } from './toolkits.js';
14
14
  import { STICK_GAITS, STICK_POSES, STICK_SEQUENCES, STICK_EXPRESSIONS, STICK_PROPS } from './stick-vocabulary.js';
15
15
  import { AgentBatchOperationTypeSchema, GeneratorNameSchema, ItemTypeSchema } from '../types/schemas.js';
16
16
  import { expressionGrammarText } from './expression-grammar.js';
17
- import { DESIGN_MEDIA, DESIGN_MEDIA_APPLY, FILTER_TYPES, FILTER_DOCS, LINEAGE_KINDS, STYLED_SCENE_STYLES, STYLED_SCENE_STYLE_INFO, PATTERN_KINDS } from './engine-lists.js';
17
+ import { DESIGN_MEDIA, DESIGN_MEDIA_APPLY, FILTER_TYPES, FILTER_DOCS, LINEAGE_KINDS, STYLED_SCENE_STYLES, STYLED_SCENE_STYLE_INFO, PATTERN_KINDS, ANIMATION_TYPES, ANIMATION_WAVEFORMS, ANIMATION_ORIGINS } from './engine-lists.js';
18
18
  import { EffectTypeSchema, AgentExportFormatSchema, RelationTypeSchema, SimpleAnimationTypeSchema, EasingSchema, KEYFRAME_EASINGS, EASING_DESCRIPTIONS, KEYFRAME_EASING_JSON_SCHEMA, GEOMETRY_OPERATIONS, THREAD_STITCHES, } from '../types/schemas.js';
19
19
  /**
20
20
  * Vocabulary lists derived from Zod schemas so tool descriptions stay in
@@ -1640,7 +1640,7 @@ warp (optional): chained parametric warp(s) — displace each sampled point by {
1640
1640
 
1641
1641
  RETURNS: { success, kind, itemId } — itemId is the created path. Bad expressions / empty ranges return { success:false, error }.
1642
1642
 
1643
- To make an item TRAVEL along an equation curve (instead of drawing it), use pinepaper_add_relation relationType moves_along_path with params.equation — but note that equation is read ONLY on the relation's signal path. An option that leaves that path takes the equation with it and the item simply does not move: easing 'bounce' is piecewise and falls back, so bounce + equation is a static item reporting success. Use a signal-friendly easing (linear, easeIn, easeOut, easeInOut, sine, pingpong) with an equation, or supply params.path instead.
1643
+ To make an item TRAVEL along an equation curve (instead of drawing it), use pinepaper_add_relation relationType moves_along_path with params.equation. The same flipY applies there: by default y is math-up, so the item rides the curve mirrored about cy compared with screen coordinates (y down). Pass flipY: false to author the curve in screen coordinates.
1644
1644
 
1645
1645
  EXAMPLES:
1646
1646
  - Sine wave: { kind: "function", expr: "sin(x/40) * 60", min: -300, max: 300, style: { strokeColor: "#4f46e5" } }
@@ -3461,6 +3461,8 @@ PARAMS: { eps? (bounds-match tolerance px, default 4), cap? (max suggestions, de
3461
3461
  MOVES_ALONG_PATH — ride a path (a hand tracing a stroke, a car on a road):
3462
3462
  - sourceId: the item that moves. The path comes from ONE of: targetId (or params.pathId) naming a PATH ITEM, sampled into points for you; params.path as points [[x, y], …]; or params.equation.
3463
3463
  - params.duration: seconds for one traversal (or pass speed directly, 1 ≈ 150 px/s). params.delay: seconds on the timeline before it starts; until then the item waits at the path start. params.offset: [dx, dy] added to the point on the path — set it to (centre - pen tip) so a hand image's TIP, not its centre, rides the stroke. closed (loop back to the start; default from the path item), phase (0–1 start point), easing (linear | easeIn | easeOut | easeInOut | sine | bounce | pingpong), signal (deterministic, frame-rate-invariant — prefer it for export).
3464
+ - The item travels at CONSTANT SPEED along the route: the fraction of the duration elapsed is the fraction of the route's LENGTH covered, not of the equation's parameter or of the point count. Where the curve is long it moves the same speed as where it is short; easing reshapes that progress. For a route timed by the parameter instead, key the position with pinepaper_keyframe_animate.
3465
+ - params.equation is in math coordinates with y UP (flipY: true by default), placed at cx, cy: on screen the curve is mirrored about cy. Write flipY: false to author it in screen coordinates (y down).
3464
3466
  - With no path from any of those it is refused, not accepted as a still item.
3465
3467
 
3466
3468
  USE WHEN:
@@ -3531,7 +3533,7 @@ RELATION COMPATIBILITY:
3531
3533
  Text→Text: font blend
3532
3534
  Cross-type: particle denoising transition
3533
3535
  - group_morphs_to: pair-by-index morph between two paper.Groups (any two — graph vertices+edges, letter collages, dashboard clusters). Path.Line children deform via endpoints; other children translate; excess children fade. Params: duration, hold, loop, easing (linear|easeIn|easeOut|easeInOut), deformLines.
3534
- - moves_along_path: self-relation (targetId=null); item is driven along a path. Source the path EITHER from params.path (array of {x,y} or [x,y]) OR from params.equation (S10 B5) — a math curve the item TRAVERSES, sampled deterministically on the Expression IR: params.equation = { kind: function|parametric|fourier|preset, expr | xExpr/yExpr | harmonics | preset, min, max, samples, scale, cx, cy, flipY, warp } (same equation contract as pinepaper_equation_path, but origin is cx/cy, default 0). READ ONLY ON THE SIGNAL PATH: easing 'bounce' is piecewise and leaves it, which silently discards the equation and leaves the item where it started. Params: path OR equation, speed, closed, phase, easing (linear|easeIn|easeOut|easeInOut|sine|bounce|pingpong).
3536
+ - moves_along_path: self-relation (targetId=null); item is driven along a path. Source the path EITHER from params.path (array of {x,y} or [x,y]) OR from params.equation (S10 B5) — a math curve the item TRAVERSES, sampled deterministically on the Expression IR: params.equation = { kind: function|parametric|fourier|preset, expr | xExpr/yExpr | harmonics | preset, min, max, samples, scale, cx, cy, flipY, warp } (same equation contract as pinepaper_equation_path, but origin is cx/cy, default 0). y is MATH-UP by default (flipY: true): on screen the curve is mirrored about cy, so a curve written in screen coordinates (y down) needs flipY: false. Params: path OR equation, speed, closed, phase, easing (linear|easeIn|easeOut|easeInOut|sine|bounce|pingpong).
3535
3537
 
3536
3538
  GEOMETRIC CONSTRUCTION CONSTRAINTS (Layer 2) — the source item is RE-DERIVED every frame from its anchor item(s), so dragging an anchor updates the dependent live (GeoGebra-style), and the construction persists as relation graph data (edit it by editing relations, not by re-running code). Anchor A is the relation target; extra anchors ride in params:
3537
3539
  - is_midpoint_of: source = midpoint(target, params.other). params.other = id of the second endpoint (B).
@@ -3672,15 +3674,17 @@ USE WHEN:
3672
3674
  {
3673
3675
  name: 'pinepaper_register_custom_relation',
3674
3676
  annotations: {
3675
- title: 'Register Custom Relation',
3677
+ title: 'Register Custom Relation (needs PINEPAPER_ALLOW_CODE)',
3676
3678
  readOnlyHint: false,
3677
3679
  destructiveHint: false,
3678
3680
  idempotentHint: false,
3679
3681
  openWorldHint: false,
3680
3682
  },
3681
- description: `PARAMS ARE DEFINITIONS, not values: { gap: { type: 'number', default: 60, min: 0 } } (type is number | string | boolean | array | object). Once registered, the name is a valid relationType for pinepaper_add_relation in this session.
3683
+ description: `SECURITY SWITCH: needs a server started with PINEPAPER_ALLOW_CODE=1 (off by default; only the person running the server can turn it on). Otherwise it is refused and nothing runs.
3682
3684
 
3683
- Register a custom relation type with compute and apply functions.
3685
+ PARAMS ARE DEFINITIONS, not values: { gap: { type: 'number', default: 60, min: 0 } } (type is number | string | boolean | array | object). Once registered, the name is a valid relationType for pinepaper_add_relation in this session.
3686
+
3687
+ Register a custom relation type with compute and apply functions. For motion without code, use time_expression.
3684
3688
 
3685
3689
  USE WHEN:
3686
3690
  - Creating physics-based behaviors (attraction, repulsion, collision)
@@ -3743,13 +3747,15 @@ applyFunction: \`
3743
3747
  {
3744
3748
  name: 'pinepaper_execute_custom_code',
3745
3749
  annotations: {
3746
- title: 'Execute Custom Code',
3750
+ title: 'Execute Custom Code (needs PINEPAPER_ALLOW_CODE)',
3747
3751
  readOnlyHint: false,
3748
3752
  destructiveHint: true,
3749
3753
  idempotentHint: false,
3750
3754
  openWorldHint: true,
3751
3755
  },
3752
- description: `Execute arbitrary JavaScript code in the PinePaper context.
3756
+ description: `SECURITY SWITCH: needs a server started with PINEPAPER_ALLOW_CODE=1 (off by default; only the person running the server can turn it on). Otherwise it is refused and nothing runs.
3757
+
3758
+ Execute arbitrary JavaScript code in the PinePaper context.
3753
3759
 
3754
3760
  USE WHEN:
3755
3761
  - Advanced Paper.js operations not covered by other tools
@@ -3849,6 +3855,9 @@ ANIMATION TYPES (18 — the list the driver actually switches on):
3849
3855
  - scrollLeft / scrollRight / scrollUp / scrollDown: Continuous scroll, wrapping
3850
3856
  - path: Travel along an assigned path
3851
3857
  - typewriter: Character-by-character reveal
3858
+ - ripple: An opacity wave. On a GROUP it sweeps through the children outward from origin, each a little later; on a single item it pulses the item.
3859
+ - undulate: A vertical lift wave, swept through a group's children the same way (intensity 0.4 ≈ 14 px).
3860
+ ripple and undulate take waveform (the shape of one cycle) and origin (where the wave starts on a group). SVG and Lottie exports draw them static; export MP4, WebM or GIF to keep the wave.
3852
3861
 
3853
3862
  There is NO 'slide'. An unknown type is stored and then ignored — the item sits still while its data claims otherwise, and the call still reports success — so use the exact names above.`,
3854
3863
  inputSchema: {
@@ -3857,7 +3866,7 @@ There is NO 'slide'. An unknown type is stored and then ignored — the item sit
3857
3866
  itemId: { type: 'string', description: 'Registry ID of the item' },
3858
3867
  animationType: {
3859
3868
  type: 'string',
3860
- enum: ['bounce', 'breathe', 'fade', 'glow', 'jelly', 'path', 'pulse', 'rotate', 'scrollDown', 'scrollLeft', 'scrollRight', 'scrollUp', 'shake', 'slideLeftRight', 'slideUpDown', 'swing', 'typewriter', 'wobble'],
3869
+ enum: [...ANIMATION_TYPES],
3861
3870
  description: 'Type of animation',
3862
3871
  },
3863
3872
  speed: {
@@ -3872,6 +3881,8 @@ There is NO 'slide'. An unknown type is stored and then ignored — the item sit
3872
3881
  type: 'number',
3873
3882
  description: 'Animation start delay in seconds.',
3874
3883
  },
3884
+ waveform: { type: 'string', enum: [...ANIMATION_WAVEFORMS], description: 'ripple / undulate: the shape of one cycle (default sine).' },
3885
+ origin: { type: 'string', enum: [...ANIMATION_ORIGINS], description: 'ripple / undulate on a group: where the wave starts (default center).' },
3875
3886
  },
3876
3887
  required: ['itemId', 'animationType'],
3877
3888
  },
@@ -3914,7 +3925,7 @@ ANIMATABLE PROPERTIES:
3914
3925
  - strokeColor: Color string
3915
3926
  - strokeWidth, width, height, radius, shadowBlur, shadowColor: Numbers / colours
3916
3927
  - fontSize: Number
3917
- - TEXT (current studio): fontStyle ('normal' / 'italic' / 'oblique', discrete — switches at the next key), textAlign ('left' / 'center' / 'right', discrete; the anchor stays put), lineHeight (px, or a multiple of fontSize up to 5), letterSpacing (px, or a CSS length such as '0.1em'). An older studio stores these keys without drawing them; the result names any it did not animate (ignoredProperties).
3928
+ - TEXT (current studio): fontStyle ('normal' / 'italic' / 'oblique', discrete — switches at the next key), textAlign ('left' / 'center' / 'right', discrete; the anchor stays put), lineHeight (px, or a multiple of fontSize up to 5), letterSpacing (px, or a CSS length such as '0.1em'). An older studio stores these keys without drawing them; the result names any it did not animate (ignoredProperties). fontWeight keys STEP: a font with fixed weights switches from one weight to the next once, partway through the move, and does not ramp. Key fontSize or scale for a smooth change.
3918
3929
  - STROKE DRAW-ON (paths): trimStart, trimEnd (0-1 of the path's length), trimOffset (shifts the visible window along it), dashArray. A draw-on is trimEnd 0 -> 1.
3919
3930
  The authoritative list for a given item is pinepaper_get_animatable_properties — ask it rather than guessing.
3920
3931
 
@@ -3924,7 +3935,7 @@ ${EASING_LINES}
3924
3935
  - 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.
3925
3936
  COUNTERS: a text item with a counter (create_item / modify_item counter) also animates the numeric property counterValue, drawn through the counter's format — keyframe it to shape a count by hand (hold on a number, then race). Its keys override the counter's own from / to.
3926
3937
 
3927
- HOLD KEYS (where the studio has them): interpolation: 'hold' on a key holds ITS value until the next key, then cuts — no in-between. Easing 'hold' / 'step' on the ARRIVING key does the same. A studio without hold keys refuses rather than interpolate; there, two keys a frame apart make the cut. For a whole layer 'on twos', set stepTiming on the item (create_item / modify_item).
3938
+ HOLD KEYS (where the studio has them): interpolation: 'hold' on a key holds ITS value until the next key, then cuts — no in-between. Easing 'hold' / 'step' on the ARRIVING key does the same. A studio without hold keys refuses rather than interpolate; there, two keys a frame apart make the cut. For a whole layer 'on twos', set stepTiming on the item (create_item / modify_item). A DISCRETE change (a bar turning red at 2 s, a label switching colour) needs this too: without hold / step every key ramps from the one before it, so the colour fades across the whole gap instead of switching. This applies to batch keyframe ops the same way.
3928
3939
 
3929
3940
  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.
3930
3941
 
@@ -4860,6 +4871,7 @@ KINDS:
4860
4871
  - phase_wave — members move on a sine, each a phase later than the one before. params: property ('y' default, 'x', 'rotation', 'opacity'), amplitude (px, degrees or 0..1), period (seconds per cycle), wavelength (members per full cycle), phase (radians). window { from, to } limits when it plays; time counts from \`from\`.
4861
4872
  - stagger — each member's animationDelay, derived from its place. params: step (s between neighbours), start (s), order: index | reverse | center | edges.
4862
4873
  - ring / orbit (the same motion) — members spaced evenly round a circle that turns. params: center ([x, y] or {x, y}; default 960, 540), target (an item id or name: the centre follows it), radius (px, default 200), period (seconds per turn, default 6), phase (RADIANS, the first member's angle), direction (1 clockwise, -1 or 'ccw'). Each member's own position is overwritten while it plays and comes back when the pattern is removed. window as for phase_wave.
4874
+ - STACKING. phase_wave patterns on the same member and property add their offsets to one shared base on the current studio, so sequenced windows combine without drift (an older studio re-based them on each other's writes and could leave the member displaced: there, give a member one pattern per property and sequence by modifying its params and window). ring and orbit write the member's absolute position, so two of them on one member conflict: give each member one ring or orbit.
4863
4875
 
4864
4876
  MEMBERS — by names (a '*' matches any run: "letter-*"), ids, or a tag, in the order they are given; an array is taken as ids or names. A name that matches nothing is reported in the result, not ignored.
4865
4877
 
@@ -8069,6 +8081,7 @@ SPEC:
8069
8081
  UNITS (a number, or a [min, max] range picked per particle where it says range):
8070
8082
  - count: particles per burst, at most 10000 on the current studio (2000 on older studios) — a larger count is drawn at the cap and the reply warns with the number drawn. For more, add a second burst at the same t.
8071
8083
  - speed: px/s (range). angle: degrees, 0 = right, 90 = down (range). gravity: px/s². lifetime: seconds (range, > 0). size: px (range).
8084
+ - gravity, drag, friction, opacity, sizeEnd and jitter also take a range on the current studio, sampled per particle; an older studio draws the midpoint and warns emitter-range-not-read.
8072
8085
  - fadeIn / fadeOut: SECONDS at the start / end of each particle's life. fadeOut is capped at that particle's lifetime (a 0.5 s life cannot fade for 2 s; it fades over its whole life instead), and the current studio warns when it caps one. Omitted (or fade: 'linear'), the particle fades linearly over its WHOLE life; fade: 'none' turns both off.
8073
8086
  - spin: DEGREES PER SECOND, positive = clockwise on screen (range; spinAlternate flips every other particle). It turns rect, confetti, star and dash particles; a circle has no orientation, so spin on circles shows nothing (the current studio warns when every particle is a circle).
8074
8087
  - jitter: PIXELS — on the current studio each particle is displaced up to ±jitter px on each axis from its path, at full size from birth; jitterGrow: true keeps the older ramp (zero at birth, the full ±jitter at the end of life). Older studios (before jitterGrow) displace up to ±jitter/2, growing from 0 at birth. jitterHz: how many times a second that offset is re-rolled (0, the default, keeps one offset per particle).
@@ -8088,8 +8101,10 @@ EXAMPLE — confetti at 2 s:
8088
8101
  },
8089
8102
  {
8090
8103
  name: 'pinepaper_render_hook',
8091
- annotations: { title: 'Render Hook', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
8092
- description: `Custom per-frame drawing that a saved scene CARRIES: SOURCE TEXT, compiled and run under the governor's loop guards with seeded Math, saved in the project document, the template and undo, and re-installed on load. (A raw function added with code is not saved — pinepaper_export_scene { full: true } names any such as unserialized.)
8104
+ annotations: { title: 'Render Hook (register needs PINEPAPER_ALLOW_CODE)', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
8105
+ description: `SECURITY SWITCH: register needs a server started with PINEPAPER_ALLOW_CODE=1 (off by default; only the person running the server can turn it on). list and unregister always work.
8106
+
8107
+ Custom per-frame drawing that a saved scene CARRIES: SOURCE TEXT, compiled and run under the governor's loop guards with seeded Math, saved in the project document, the template and undo, and re-installed on load. (A raw function added with code is not saved — pinepaper_export_scene { full: true } names any such as unserialized.)
8093
8108
 
8094
8109
  ACTIONS:
8095
8110
  - register: { id, source, layer? ('above' | 'below'), deterministic? (true), seed? } — source evaluates to (ctx, t, info) => {}: ctx in canvas coordinates, t the scene time in seconds, info { width, height, frame, seed }. 'above' draws over every item, 'below' under them. deterministic reseeds Math.random from (seed, frame) each call, so a frame always draws the same.
@@ -8149,7 +8164,7 @@ EXAMPLE — a ball comes to life and crosses the frame:
8149
8164
  sound: { description: "true, or {pop?, whoosh?, contact?, shake? (catalogue name or false), gain? 0..1}: a sound on every pop, launch, landing and shake.", oneOf: [{ type: 'boolean' }, { type: 'object', additionalProperties: true }] },
8150
8165
  beats: {
8151
8166
  type: 'array',
8152
- description: 'Beats in time order: [{at, verb, to?, duration?, height?, times?, decay?, arc?, edge?, from?, amount?, hold?, intensity?}].',
8167
+ description: 'Beats in time order: [{at, verb, to?, duration?, height?, times?, decay?, arc?, edge?, from?, amount?, hold?, intensity?}]. intensity is the amplitude of shake in px; no other verb reads it.',
8153
8168
  items: {
8154
8169
  type: 'object',
8155
8170
  properties: {
@@ -9069,7 +9084,7 @@ VERIFY MOTION BEFORE YOU RENDER. An export takes seconds to minutes and shows yo
9069
9084
  loop: { anyOf: [{ type: 'boolean' }, { type: 'integer', minimum: 0, maximum: 1000 }], description: 'gif / apng: true = loop forever, false / 0 / 1 = play once, n = play n times (email clients often want a few plays, not forever). Refused on video formats — see SEAMLESS LOOPS.' },
9070
9085
  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.' },
9071
9086
  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.' },
9072
- 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.' },
9087
+ deterministic: { type: 'boolean', description: 'mp4 / webm: pins the container timestamps. WebM then comes out byte-identical; an MP4\'s encoded bytes can still differ run to run (the H.264 encoder is not bit-exact), so compare MP4s by the receipt\'s frameDigest, which is the same for the same frames, not by sha256.' },
9073
9088
  motionBlur: {
9074
9089
  type: 'object',
9075
9090
  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).',
@@ -9208,13 +9223,15 @@ EXAMPLE RESPONSE:
9208
9223
  {
9209
9224
  name: 'pinepaper_p5_draw',
9210
9225
  annotations: {
9211
- title: 'p5.js Draw',
9226
+ title: 'p5.js Draw (needs PINEPAPER_ALLOW_CODE)',
9212
9227
  readOnlyHint: false,
9213
9228
  destructiveHint: false,
9214
9229
  idempotentHint: false,
9215
9230
  openWorldHint: false,
9216
9231
  },
9217
- description: `Execute p5.js-style drawing code on the PinePaper canvas.
9232
+ description: `SECURITY SWITCH: needs a server started with PINEPAPER_ALLOW_CODE=1 (off by default; only the person running the server can turn it on). Otherwise it is refused and nothing runs.
9233
+
9234
+ Execute p5.js-style drawing code on the PinePaper canvas.
9218
9235
 
9219
9236
  AI assistants can use familiar p5.js syntax - the code is translated to Paper.js automatically.
9220
9237
  This is a SUBSET of p5.js functions optimized for static drawings.
@@ -9675,7 +9692,7 @@ VERBOSITY:
9675
9692
  toolkit: {
9676
9693
  type: 'string',
9677
9694
  description: 'Toolkit profile: core (default), full, agent, diagram, map, font, minimal, lane',
9678
- enum: ['core', 'full', 'agent', 'diagram', 'map', 'font', 'minimal', 'lane'],
9695
+ enum: ['core', 'full', 'agent', 'diagram', 'map', 'font', 'minimal', 'lane', 'edit'],
9679
9696
  },
9680
9697
  load: {
9681
9698
  type: 'array',