@pinepaper.studio/mcp-server 1.6.18 → 1.6.19

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 (108) hide show
  1. package/README.de.md +1 -1
  2. package/README.es.md +1 -1
  3. package/README.fr.md +1 -1
  4. package/README.hi.md +1 -1
  5. package/README.ja.md +1 -1
  6. package/README.ko.md +1 -1
  7. package/README.md +97 -10
  8. package/README.pt-BR.md +1 -1
  9. package/README.zh-CN.md +1 -1
  10. package/dist/agent/export-manager.js +2 -2
  11. package/dist/agent/session-manager.d.ts +8 -0
  12. package/dist/agent/session-manager.d.ts.map +1 -1
  13. package/dist/agent/session-manager.js +17 -2
  14. package/dist/agent/session-manager.js.map +1 -1
  15. package/dist/assets/asset-manager.d.ts +28 -1
  16. package/dist/assets/asset-manager.d.ts.map +1 -1
  17. package/dist/assets/asset-manager.js +65 -25
  18. package/dist/assets/asset-manager.js.map +1 -1
  19. package/dist/assets/index.d.ts +1 -3
  20. package/dist/assets/index.d.ts.map +1 -1
  21. package/dist/assets/index.js +1 -3
  22. package/dist/assets/index.js.map +1 -1
  23. package/dist/assets/repositories/iconify.js +1 -1
  24. package/dist/assets/repositories/iconify.js.map +1 -1
  25. package/dist/assets/repositories/openclipart.js +1 -1
  26. package/dist/assets/repositories/openclipart.js.map +1 -1
  27. package/dist/assets/repositories/svgrepo.js +2 -2
  28. package/dist/assets/repositories/svgrepo.js.map +1 -1
  29. package/dist/assets/types.d.ts +2 -2
  30. package/dist/assets/types.d.ts.map +1 -1
  31. package/dist/browser/puppeteer-controller.d.ts +25 -0
  32. package/dist/browser/puppeteer-controller.d.ts.map +1 -1
  33. package/dist/browser/puppeteer-controller.js +107 -1
  34. package/dist/browser/puppeteer-controller.js.map +1 -1
  35. package/dist/build-info.json +2 -2
  36. package/dist/cli.js +4 -2
  37. package/dist/cli.js.map +1 -1
  38. package/dist/design/design-systems.d.ts +2 -0
  39. package/dist/design/design-systems.d.ts.map +1 -1
  40. package/dist/design/design-systems.js +2 -0
  41. package/dist/design/design-systems.js.map +1 -1
  42. package/dist/execution/canvas-state.d.ts.map +1 -1
  43. package/dist/execution/canvas-state.js +19 -43
  44. package/dist/execution/canvas-state.js.map +1 -1
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +20 -7
  47. package/dist/index.js.map +1 -1
  48. package/dist/ontology/design-graph.js +2 -2
  49. package/dist/ontology/design-graph.js.map +1 -1
  50. package/dist/ontology/upstream-vocabulary.d.ts +2 -2
  51. package/dist/ontology/upstream-vocabulary.js +2 -2
  52. package/dist/ontology/validator.js +1 -1
  53. package/dist/ontology/validator.js.map +1 -1
  54. package/dist/tools/compact-descriptions.d.ts.map +1 -1
  55. package/dist/tools/compact-descriptions.js +15 -14
  56. package/dist/tools/compact-descriptions.js.map +1 -1
  57. package/dist/tools/definitions.d.ts +1 -1
  58. package/dist/tools/definitions.d.ts.map +1 -1
  59. package/dist/tools/definitions.js +422 -126
  60. package/dist/tools/definitions.js.map +1 -1
  61. package/dist/tools/engine-lists.d.ts +17 -2
  62. package/dist/tools/engine-lists.d.ts.map +1 -1
  63. package/dist/tools/engine-lists.js +102 -2
  64. package/dist/tools/engine-lists.js.map +1 -1
  65. package/dist/tools/engine-methods.d.ts.map +1 -1
  66. package/dist/tools/engine-methods.js +14 -1
  67. package/dist/tools/engine-methods.js.map +1 -1
  68. package/dist/tools/engine-surface.d.ts +1 -1
  69. package/dist/tools/engine-surface.d.ts.map +1 -1
  70. package/dist/tools/engine-surface.js +49 -8
  71. package/dist/tools/engine-surface.js.map +1 -1
  72. package/dist/tools/handlers/camera.js +8 -8
  73. package/dist/tools/handlers/camera.js.map +1 -1
  74. package/dist/tools/handlers/export.d.ts.map +1 -1
  75. package/dist/tools/handlers/export.js +77 -5
  76. package/dist/tools/handlers/export.js.map +1 -1
  77. package/dist/tools/handlers/font.js +10 -10
  78. package/dist/tools/handlers/font.js.map +1 -1
  79. package/dist/tools/handlers/generate.d.ts +12 -12
  80. package/dist/tools/handlers/maps.d.ts.map +1 -1
  81. package/dist/tools/handlers/maps.js +23 -3
  82. package/dist/tools/handlers/maps.js.map +1 -1
  83. package/dist/tools/handlers.d.ts +21 -0
  84. package/dist/tools/handlers.d.ts.map +1 -1
  85. package/dist/tools/handlers.js +240 -37
  86. package/dist/tools/handlers.js.map +1 -1
  87. package/dist/tools/minimal-descriptions.d.ts.map +1 -1
  88. package/dist/tools/minimal-descriptions.js +32 -25
  89. package/dist/tools/minimal-descriptions.js.map +1 -1
  90. package/dist/tools/shape-params.d.ts +2 -2
  91. package/dist/tools/shape-params.d.ts.map +1 -1
  92. package/dist/tools/shape-params.js +5 -2
  93. package/dist/tools/shape-params.js.map +1 -1
  94. package/dist/tools/stick-vocabulary.d.ts +1 -1
  95. package/dist/tools/stick-vocabulary.js +1 -1
  96. package/dist/tools/toolkits.d.ts.map +1 -1
  97. package/dist/tools/toolkits.js +5 -0
  98. package/dist/tools/toolkits.js.map +1 -1
  99. package/dist/types/code-generator.d.ts +57 -3
  100. package/dist/types/code-generator.d.ts.map +1 -1
  101. package/dist/types/code-generator.js +1007 -253
  102. package/dist/types/code-generator.js.map +1 -1
  103. package/dist/types/schemas.d.ts +1842 -890
  104. package/dist/types/schemas.d.ts.map +1 -1
  105. package/dist/types/schemas.js +229 -51
  106. package/dist/types/schemas.js.map +1 -1
  107. package/manifest.json +22 -2
  108. package/package.json +1 -1
@@ -8,7 +8,7 @@
8
8
  * 3. TypeScript type inference
9
9
  */
10
10
  import { z } from 'zod';
11
- import { ENGINE_EASING_NAMES, DESIGN_MEDIA, DESIGN_MEDIA_APPLY } from '../tools/engine-lists.js';
11
+ import { ENGINE_EASING_NAMES, DESIGN_MEDIA, DESIGN_MEDIA_APPLY, FILTER_TYPES, LINEAGE_KINDS, STYLED_SCENE_STYLES } from '../tools/engine-lists.js';
12
12
  // =============================================================================
13
13
  // COMMON SCHEMAS
14
14
  // =============================================================================
@@ -1105,22 +1105,10 @@ export const BlastParamsSchema = z.object({
1105
1105
  // 'saturate' is what the published JSON schema offered for releases, while
1106
1106
  // the engine registers 'saturation' and this enum rejected 'saturate' — a
1107
1107
  // caller who followed the docs got a validation error. Accepted and mapped.
1108
- export const FilterTypeSchema = z.preprocess((v) => (v === 'saturate' ? 'saturation' : v), z.enum([
1109
- 'grayscale',
1110
- 'sepia',
1111
- 'blur',
1112
- 'brightness',
1113
- 'contrast',
1114
- 'saturation',
1115
- 'invert',
1116
- 'noise',
1117
- 'vignette',
1118
- 'vintage',
1119
- 'colorOverlay',
1120
- 'sharpen',
1121
- 'emboss',
1122
- 'posterize',
1123
- ])).describe('Type of visual filter to apply');
1108
+ // GENERATED list (FilterSystem.js registerFilter, scripts/sync-engine-lists.mjs).
1109
+ // The hand copy here hid six of twenty: dither, halftoneCMYK, halftoneDots,
1110
+ // edgeDetect, hsl and colorTint. 'saturate' stays accepted as 'saturation'.
1111
+ export const FilterTypeSchema = z.preprocess((v) => (v === 'saturate' ? 'saturation' : v), z.enum(FILTER_TYPES)).describe('Type of visual filter to apply');
1124
1112
  // =============================================================================
1125
1113
  // TOOL INPUT SCHEMAS
1126
1114
  // =============================================================================
@@ -1441,9 +1429,12 @@ export const SetCanvasSizeInputSchema = z.object({
1441
1429
  // not manage it.
1442
1430
  // The refusal NAMES the route: a 728x90 banner refused with a bare "too
1443
1431
  // small" left a caller to export the preset instead (round 8 BB, 1.48).
1444
- width: z.number().min(100, "a canvas side under 100 px is not supported. For small assets (a 728x90 or 320x50 banner, a 28 px emote) design at 2-4x — e.g. 1456x180 — and export with agent_export region {x:0, y:0, width, height, outputWidth:728, outputHeight:90}. For VIDEO at a small size, design at 2x and export with agent_export scale: 0.5.").max(16384).describe('Canvas width (100-16384; above 8192 Safari / iOS may fail)'),
1445
- height: z.number().min(100, "a canvas side under 100 px is not supported. For small assets (a 728x90 or 320x50 banner, a 28 px emote) design at 2-4x — e.g. 1456x180 — and export with agent_export region {x:0, y:0, width, height, outputWidth:728, outputHeight:90}. For VIDEO at a small size, design at 2x and export with agent_export scale: 0.5.").max(16384).describe('Canvas height (100-16384; above 8192 Safari / iOS may fail)'),
1446
- preset: z.string().optional().describe('Optional preset name'),
1432
+ width: z.number().min(100, "a canvas side under 100 px is not supported. For small assets (a 728x90 or 320x50 banner, a 28 px emote) design at 2-4x — e.g. 1456x180 — and export with agent_export region {x:0, y:0, width, height, outputWidth:728, outputHeight:90}. For VIDEO at a small size, design at 2x and export with agent_export scale: 0.5.").max(16384).optional().describe('Canvas width (100-16384; above 8192 Safari / iOS may fail)'),
1433
+ height: z.number().min(100, "a canvas side under 100 px is not supported. For small assets (a 728x90 or 320x50 banner, a 28 px emote) design at 2-4x — e.g. 1456x180 — and export with agent_export region {x:0, y:0, width, height, outputWidth:728, outputHeight:90}. For VIDEO at a small size, design at 2x and export with agent_export scale: 0.5.").max(16384).optional().describe('Canvas height (100-16384; above 8192 Safari / iOS may fail)'),
1434
+ preset: z.string().optional().describe('A named size instead of width/height; an unknown name is refused with the known ones listed.'),
1435
+ }).refine((v) => !!v.preset || (v.width !== undefined && v.height !== undefined), {
1436
+ // {preset} alone was refused for a missing width (1.6.19 gate).
1437
+ message: 'pass width and height, or a preset', path: ['width'],
1447
1438
  });
1448
1439
  // Export
1449
1440
  export const ExportSVGInputSchema = z.object({
@@ -1521,12 +1512,12 @@ export const SetToolkitInputSchema = z.object({
1521
1512
  // =============================================================================
1522
1513
  export const SearchAssetsInputSchema = z.object({
1523
1514
  query: z.string().describe('Search term (e.g., "rocket", "heart", "user icon")'),
1524
- repository: z.enum(['all', 'svgrepo', 'openclipart', 'iconify', 'fontawesome']).optional().describe('Which repository to search (default: all)'),
1515
+ repository: z.enum(['all', 'iconify', 'fontawesome']).optional().describe('Which repository to search (default: all)'),
1525
1516
  limit: z.number().min(1).max(50).optional().describe('Max results to return (default: 10)'),
1526
1517
  includeSvgContent: z.boolean().optional().describe('Include actual SVG content for each result (default: false). When true, fetches and returns the SVG string for each asset, enabling AI to display or describe the icons.'),
1527
1518
  });
1528
1519
  export const ImportAssetInputSchema = z.object({
1529
- assetId: z.string().optional().describe('Asset ID from search results (e.g., "svgrepo_12345")'),
1520
+ assetId: z.string().optional().describe('Asset ID from search results (e.g., "iconify_mdi_rocket")'),
1530
1521
  url: z.string().optional().describe('Direct URL to SVG (alternative to assetId)'),
1531
1522
  position: z.object({
1532
1523
  x: z.number(),
@@ -1738,8 +1729,23 @@ export const CreateDiagramShapeInputSchema = z.object({
1738
1729
  /**
1739
1730
  * Connect items input
1740
1731
  */
1741
- export const ConnectInputSchema = z.object({
1742
- id: z.string().optional().describe('Stable connector ID. Assign your own and reuse it as `connectorId` in update/remove — the engine otherwise mints a Date.now()-based ID no caller can predict'),
1732
+ // strokeColor / strokeWidth are what a model guesses (Paper's names); they
1733
+ // were stripped and the connector drew in the default colour (gate run 3).
1734
+ export const ConnectInputSchema = z.preprocess((v) => {
1735
+ if (!v || typeof v !== 'object' || Array.isArray(v))
1736
+ return v;
1737
+ const o = { ...v };
1738
+ if (o.lineColor === undefined && o.strokeColor !== undefined) {
1739
+ o.lineColor = o.strokeColor;
1740
+ delete o.strokeColor;
1741
+ }
1742
+ if (o.lineWidth === undefined && o.strokeWidth !== undefined) {
1743
+ o.lineWidth = o.strokeWidth;
1744
+ delete o.strokeWidth;
1745
+ }
1746
+ return o;
1747
+ }, z.object({
1748
+ id: z.string().optional().describe('Requested connector id. The studio may not honour it (the result says idHonoured): always use the connectorId the result returns for update/remove.'),
1743
1749
  sourceItemId: z.string().describe('Registry ID of source item'),
1744
1750
  targetItemId: z.string().describe('Registry ID of target item'),
1745
1751
  routing: ConnectorRoutingSchema.optional().default('orthogonal'),
@@ -1752,12 +1758,12 @@ export const ConnectInputSchema = z.object({
1752
1758
  curvature: z.number().min(0.1).max(1.0).optional().default(0.5).describe('Curve intensity for curved routing'),
1753
1759
  boltEnabled: z.boolean().optional().default(true).describe('Enable animated bolt effect'),
1754
1760
  boltColor: z.string().optional().default('#fbbf24').describe('Bolt animation color'),
1755
- });
1761
+ }));
1756
1762
  /**
1757
1763
  * Connect specific ports input
1758
1764
  */
1759
1765
  export const ConnectPortsInputSchema = z.object({
1760
- id: z.string().optional().describe('Stable connector ID. Assign your own and reuse it as `connectorId` in update/remove — the engine otherwise mints a Date.now()-based ID no caller can predict'),
1766
+ id: z.string().optional().describe('Requested connector id. The studio may not honour it (the result says idHonoured): always use the connectorId the result returns for update/remove.'),
1761
1767
  sourceItemId: z.string().describe('Registry ID of source item'),
1762
1768
  sourcePort: PortPositionSchema.describe('Port position on source'),
1763
1769
  targetItemId: z.string().describe('Registry ID of target item'),
@@ -1824,16 +1830,34 @@ export const GetDiagramShapesInputSchema = z.object({
1824
1830
  /**
1825
1831
  * Update connector input
1826
1832
  */
1833
+ // Paper.js's own names are what a model guesses for a connector's style, and
1834
+ // an unknown key was STRIPPED, so {strokeColor} changed nothing and reported
1835
+ // success (gate run 3). The Paper names are mapped; anything else is refused
1836
+ // with the keys that work.
1837
+ const CONNECTOR_STYLE_ALIASES = { strokeColor: 'lineColor', color: 'lineColor', strokeWidth: 'lineWidth', width: 'lineWidth' };
1838
+ const CONNECTOR_STYLE_KEYS = ['lineColor', 'lineWidth', 'headStyle', 'tailStyle', 'routing', 'lineStyle'];
1827
1839
  export const UpdateConnectorInputSchema = z.object({
1828
1840
  connectorId: z.string().describe('Connector ID to update'),
1829
- style: z.object({
1841
+ style: z.preprocess((v) => {
1842
+ if (!v || typeof v !== 'object' || Array.isArray(v))
1843
+ return v;
1844
+ const out = {};
1845
+ for (const [k, val] of Object.entries(v)) {
1846
+ if (k === 'dashArray') {
1847
+ out.lineStyle = Array.isArray(val) && val.length ? 'dashed' : 'solid';
1848
+ continue;
1849
+ }
1850
+ out[CONNECTOR_STYLE_ALIASES[k] ?? k] = val;
1851
+ }
1852
+ return out;
1853
+ }, z.object({
1830
1854
  lineColor: z.string().optional(),
1831
1855
  lineWidth: z.number().optional(),
1832
1856
  headStyle: ArrowHeadStyleSchema.optional(),
1833
1857
  tailStyle: ArrowHeadStyleSchema.optional(),
1834
1858
  routing: ConnectorRoutingSchema.optional(),
1835
1859
  lineStyle: ConnectorLineStyleSchema.optional(),
1836
- }).optional(),
1860
+ }).strict(`a connector's style takes ${CONNECTOR_STYLE_KEYS.join(', ')} (strokeColor, strokeWidth and dashArray are accepted for lineColor, lineWidth and lineStyle)`)).optional(),
1837
1861
  label: z.string().optional().describe('Update connector label'),
1838
1862
  labelPosition: z.number().min(0).max(1).optional().describe('Label position along path (0-1)'),
1839
1863
  });
@@ -2039,10 +2063,8 @@ export const AgentBatchOperationSchema = z.object({
2039
2063
  type: AgentBatchOperationTypeSchema,
2040
2064
  // Create operation fields
2041
2065
  itemType: z.string().optional().describe('Item type for create operations'),
2042
- position: z.object({
2043
- x: z.number(),
2044
- y: z.number(),
2045
- }).optional().describe('Position for create operations'),
2066
+ // PositionSchema, as create_item takes: [x, y] was refused here (1.6.19 gate).
2067
+ position: PositionSchema.optional().describe('Position for create operations: {x, y} or [x, y]'),
2046
2068
  properties: z.record(z.unknown()).optional().describe('Properties for create/modify operations'),
2047
2069
  // Modify/Animate/Delete/Keyframe/Mask/Effect target
2048
2070
  itemId: z.string().optional().describe('Target item ID or $N reference'),
@@ -2171,6 +2193,19 @@ export const GIF_MAX_DURATION_S = 15;
2171
2193
  * out-of-memory — it turns into a message naming the ceiling and what to do.
2172
2194
  */
2173
2195
  export const VIDEO_MAX_DURATION_S = 600;
2196
+ /**
2197
+ * pinepaper_styled_scene (FxTool K2): one declarative composition drawn in a
2198
+ * built-in style, as ONE canvas item that redraws itself every exported frame.
2199
+ * The styles are GENERATED (STYLED_SCENE_STYLES); the engine also resolves its
2200
+ * own aliases ('watercolour', 'papercut'), so style is a string here and an
2201
+ * unknown one is refused by the engine with the known list.
2202
+ */
2203
+ export const StyledSceneInputSchema = z.object({
2204
+ style: z.string().optional().describe(`The look: ${STYLED_SCENE_STYLES.join(' | ')} (default watercolor). The engine also takes its aliases (watercolour, papercut, 1bit…).`),
2205
+ duration: z.number().positive().max(VIDEO_MAX_DURATION_S).optional().describe('Seconds the scene runs (default the composition\'s, 8). Export for this long.'),
2206
+ id: z.string().optional().describe('Scene id (default "styled-scene"). Calling again with the same id REPLACES that scene, e.g. to change style.'),
2207
+ spec: z.record(z.string(), z.unknown()).optional().describe("The composition, every key optional (omitted keys take the 'valley' preset): preset, size [W, H], duration, transition {name, start, length}, camera {zoom: [from, to], pan}, palette, sun, clouds, ridges, foreground, river, settlements, structure, pylons, vegetation, effects {steam, ripples, scars}, hud {label}."),
2208
+ });
2174
2209
  /**
2175
2210
  * Smart export input schema
2176
2211
  */
@@ -2591,6 +2626,7 @@ export const HighlightRegionsInputSchema = z.object({
2591
2626
  strokeColor: z.string().optional().describe('Highlight stroke color'),
2592
2627
  stroke: z.string().optional().describe("The engine's own name for the stroke."),
2593
2628
  strokeWidth: z.number().optional().describe('Highlight stroke width'),
2629
+ opacity: z.number().min(0).max(1).optional().describe('0..1: a TINT over the land. The colour is pre-blended over the region\'s land fill and drawn opaque, so the export shows exactly the fill reported. Hex colours only.'),
2594
2630
  animate: z.boolean().optional().describe('Animate the highlight'),
2595
2631
  }).optional().describe('Highlight options'),
2596
2632
  }).describe('Highlight regions input');
@@ -2656,12 +2692,24 @@ export const GetRegionAtPointInputSchema = z.object({
2656
2692
  y: z.number().describe('Canvas Y coordinate'),
2657
2693
  }).describe('Get region at point input');
2658
2694
  // Map Animation Schemas
2659
- export const MapRegionKeyframeSchema = z.object({
2695
+ // The engine reads fillColor OR color, plus easing (MapSystem.animateRegions);
2696
+ // requiring fillColor refused keyframes written with color (gate D17, 1.6.19).
2697
+ // A keyframe may nest its values under `properties`, the shape every other
2698
+ // keyframe tool takes; it is flattened rather than refused (gate run 2).
2699
+ export const MapRegionKeyframeSchema = z.preprocess((k) => {
2700
+ if (!k || typeof k !== 'object' || Array.isArray(k))
2701
+ return k;
2702
+ const { properties, ...rest } = k;
2703
+ return properties && typeof properties === 'object' && !Array.isArray(properties) ? { ...properties, ...rest } : k;
2704
+ }, z.object({
2660
2705
  time: z.number().describe('Time in seconds'),
2661
- fillColor: z.string().describe('Fill color at this keyframe'),
2706
+ fillColor: z.string().optional().describe('Fill color at this keyframe (or color)'),
2707
+ color: z.string().optional().describe('Same as fillColor'),
2662
2708
  strokeColor: z.string().optional().describe('Stroke color (optional)'),
2663
2709
  opacity: z.number().optional().describe('Opacity 0-1 (optional)'),
2664
- }).describe('Map region keyframe');
2710
+ easing: z.string().optional().describe('Easing into this keyframe'),
2711
+ }).refine((k) => !!(k.fillColor || k.color), { message: 'each keyframe needs fillColor (or color)', path: ['fillColor'] }))
2712
+ .describe('Map region keyframe');
2665
2713
  export const WaveDirectionSchema = z.enum(['horizontal', 'vertical', 'radial']).describe('Wave animation direction');
2666
2714
  export const AnimateMapRegionsInputSchema = z.object({
2667
2715
  duration: z.number().optional().default(5).describe('Total animation duration in seconds'),
@@ -2671,9 +2719,10 @@ export const AnimateMapRegionsInputSchema = z.object({
2671
2719
  export const AnimateMapWaveInputSchema = z.object({
2672
2720
  duration: z.number().optional().default(10).describe('Total wave duration in seconds'),
2673
2721
  loop: z.boolean().optional().default(true).describe('Loop the animation'),
2674
- colors: z.array(z.string()).optional().describe('Array of colors for the wave'),
2722
+ colors: z.array(z.string()).optional().describe('Colors the wave cycles through (at least 2)'),
2675
2723
  waveDirection: WaveDirectionSchema.optional().default('horizontal').describe('Direction of wave effect'),
2676
- }).describe('Animate map wave input');
2724
+ waveSpeed: z.number().positive().optional().describe('Speed of the wave (default 1)'),
2725
+ }).describe('Animate map wave input: the wave runs across ALL regions of the map');
2677
2726
  export const StopMapAnimationsInputSchema = z.object({
2678
2727
  regions: z.array(z.string()).optional().describe('Specific region IDs to stop (omit for all)'),
2679
2728
  resetColors: z.boolean().optional().default(true).describe('Reset regions to default colors'),
@@ -3135,7 +3184,9 @@ export const CommentInputSchema = z.object({
3135
3184
  export const ProvenanceInputSchema = z.object({
3136
3185
  action: z.enum(['get', 'lineage', 'dependents', 'record']),
3137
3186
  itemId: z.string(),
3138
- kind: z.string().optional(),
3187
+ // GENERATED (LINEAGE_KINDS): the default was 'derived', which the engine
3188
+ // refuses, and the docs named three kinds that do not exist (1.6.19 gate).
3189
+ kind: z.enum(LINEAGE_KINDS).optional().describe('record: how the item came from sourceRef (default derivedFrom).'),
3139
3190
  sourceRef: z.string().optional(),
3140
3191
  meta: z.record(z.string(), z.unknown()).optional(),
3141
3192
  });
@@ -3539,6 +3590,41 @@ export const ValidateSceneInputSchema = z.object({
3539
3590
  * deterministic capture; the times come from `times`, or from one of the
3540
3591
  * modes below.
3541
3592
  */
3593
+ /**
3594
+ * pinepaper_export_scene / pinepaper_import_scene (D28): a restorable save.
3595
+ * The default export is a summary for inspection; `full` writes the engine's
3596
+ * project document (captureProjectDocument) to a file.
3597
+ */
3598
+ /**
3599
+ * pinepaper_place_on_surface (D22, FxTool #56): a UI onto a screen in a photo,
3600
+ * corner-pinned, matted and lit, baked into the photo's pixels.
3601
+ */
3602
+ const PointPair = z.tuple([z.number(), z.number()]);
3603
+ export const PlaceOnSurfaceInputSchema = z.object({
3604
+ photoId: z.string().describe('The photo: an image item (or a group holding one).'),
3605
+ sourceId: z.string().describe('The UI to place: any canvas item or group, or an image item.'),
3606
+ quad: z.tuple([PointPair, PointPair, PointPair, PointPair]).optional().describe('The screen\'s four corners TL, TR, BR, BL. Omit to detect it (the largest bright, neutral region).'),
3607
+ quadSpace: z.enum(['image', 'canvas']).optional().describe("quad's coordinates: 'image' (default) photo pixels, or 'canvas'."),
3608
+ occlusion: z.enum(['luma', 'none']).optional().describe("'luma' (default) keeps a thumb or hand in front of the screen; 'none' covers it."),
3609
+ key: z.number().optional().describe('Brightness above which the photo counts as screen (default: found from the photo).'),
3610
+ erode: z.number().min(0).optional().describe('Pull the matte in from the bezel, px (default 2).'),
3611
+ matteBlur: z.number().min(0).optional().describe('Soften the matte edge (default 0.8).'),
3612
+ soften: z.number().min(0).optional().describe('Soften the UI to the photo\'s focus (default 0.5).'),
3613
+ glare: z.number().min(0).max(1).optional().describe('Glass glare kept over the UI (default 0.10).'),
3614
+ spill: z.number().min(0).max(1).optional().describe('Screen light spilling onto the surroundings (default 0.35).'),
3615
+ spillRadius: z.number().min(0).optional().describe('Spill reach, px (default 40).'),
3616
+ tint: z.tuple([z.number(), z.number(), z.number()]).optional().describe('Multiply the UI by [r, g, b], 0..1, to match the photo\'s white balance.'),
3617
+ slot: z.string().optional().describe("Name for this screen (default 'screen'). Placing again in the same slot REPLACES it; several screens in one photo use different slots."),
3618
+ hideSource: z.boolean().optional().describe('Hide the UI item after placing it (default true).'),
3619
+ });
3620
+ export const ExportSceneInputSchema = z.object({
3621
+ full: z.boolean().optional().describe('Save a RESTORABLE project document to a file (keyframes, relations, rigging, scene chains), not the summary. Restore it with pinepaper_import_scene.'),
3622
+ name: z.string().optional().describe('full: a name recorded in the document.'),
3623
+ });
3624
+ export const ImportSceneInputSchema = z.object({
3625
+ path: z.string().min(1).describe('The project document a full pinepaper_export_scene saved (absolute path, or relative to the server working directory).'),
3626
+ strict: z.boolean().optional().describe('Refuse a document with validation problems (default true). false loads anyway and returns the problems as warnings.'),
3627
+ });
3542
3628
  export const CaptureSheetSchema = z.object({
3543
3629
  every: z.number().positive().optional().describe('One frame every N seconds from 0 to duration (needs duration).'),
3544
3630
  duration: z.number().positive().optional().describe('Scene length in seconds, for every and loopSeam.'),
@@ -3571,7 +3657,7 @@ export const InstantiateOntologyInputSchema = z.object({
3571
3657
  canvas: z.object({
3572
3658
  width: z.number().positive(),
3573
3659
  height: z.number().positive(),
3574
- }).optional().describe('Canvas size used to tile component roots (default 1080×1080).'),
3660
+ }).optional().describe('Canvas size to lay out for (default: the live canvas).'),
3575
3661
  defaultGeometry: z.record(z.string(), z.object({
3576
3662
  width: z.number(),
3577
3663
  height: z.number(),
@@ -3684,7 +3770,26 @@ export const TextStyleInputSchema = z.object({
3684
3770
  scale: z.number().optional().describe('Size multiplier (default 1).'),
3685
3771
  strokeColor: z.string().optional().describe("Ink colour (default '#ffffff')."),
3686
3772
  strokeWidth: z.number().optional().describe('Pen width (default 2).'),
3687
- }).optional().describe('cursive: where and how it is written. The result is a STROKED PATH, so pinepaper_animate\'s draw-on and pinepaper_path\'s outline_stroke both apply to it — which is the reason to prefer it over a script font.'),
3773
+ nib: z.union([
3774
+ z.boolean(),
3775
+ z.enum(['monoline', 'broad']),
3776
+ z.object({
3777
+ angle: z.number().optional().describe('Nib angle in degrees.'),
3778
+ width: z.number().positive().optional().describe('Widest stroke, in px.'),
3779
+ contrast: z.number().min(0).max(1).optional().describe('0..1; the hairline is width × (1 − contrast).'),
3780
+ taper: z.number().min(0).max(0.5).optional().describe('0..0.5; thin entry and exit.'),
3781
+ }).strict(),
3782
+ ]).optional().describe("The pen. 'monoline' (or false): one even line. 'broad' (or true): a broad nib, thick across it and hairline along it; an object tunes the broad nib. Omitted: the studio's default pen."),
3783
+ // Draw-on (gate D41): the engine keyframes trimEnd 0→1 itself; these were
3784
+ // stripped, so a handwritten title arrived fully drawn.
3785
+ animate: z.boolean().optional().describe('true writes the words on, stroke by stroke, over `duration` seconds — no keyframe_animate needed (default false: drawn at once).'),
3786
+ duration: z.number().positive().optional().describe('animate: seconds to write the whole line (default 2).'),
3787
+ easing: z.enum(ENGINE_EASING_NAMES).optional().describe("animate: the write-on easing (default 'easeOut')."),
3788
+ timeOffset: z.number().min(0).optional().describe('animate: seconds into the timeline before writing starts (default 0).'),
3789
+ singleStroke: z.boolean().optional().describe('Primary strokes only — drop the secondary strokes (default false).'),
3790
+ showGhost: z.boolean().optional().describe('Show a faint outline of the whole line under the write-on (default false).'),
3791
+ tension: z.number().optional().describe("Curve tension of the pen path (the studio's default when omitted)."),
3792
+ }).strict().optional().describe('cursive: where and how it is written. The result is a STROKED PATH, so pinepaper_animate\'s draw-on and pinepaper_path\'s outline_stroke both apply to it — which is the reason to prefer it over a script font.'),
3688
3793
  collageOptions: z.record(z.string(), z.unknown()).optional().describe('to_collage: style, palette and the rest, same vocabulary as pinepaper_create_letter_collage.'),
3689
3794
  itemId: z.string().optional().describe('Text item id — apply_style / set_font_axes.'),
3690
3795
  styleKey: z.string().optional().describe("apply_style: style name from list_styles (e.g. 'stacked', 'arcade' — the pixel/arcade styles suggest their own face)."),
@@ -3708,7 +3813,13 @@ export const TextStyleInputSchema = z.object({
3708
3813
  }).passthrough().optional().describe('set_font_axes: weight/width/slant, or OpenType tags (wght/wdth/slnt). Unknown axes are passed to the engine and come back in `rejected` rather than being dropped here. All three named axes are animatable (addKeyframe with fontWeight interpolates).'),
3709
3814
  })
3710
3815
  .refine((v) => v.action !== 'apply_style' || (!!v.itemId && !!v.styleKey), { message: 'apply_style requires itemId and styleKey', path: ['styleKey'] })
3711
- .refine((v) => v.action !== 'set_font_axes' || (!!v.itemId && !!v.axes && Object.keys(v.axes).length > 0), { message: 'set_font_axes requires itemId and at least one axis', path: ['axes'] });
3816
+ .refine((v) => v.action !== 'set_font_axes' || (!!v.itemId && !!v.axes && Object.keys(v.axes).length > 0), { message: 'set_font_axes requires itemId and at least one axis', path: ['axes'] })
3817
+ // cursive WRITES new words; with no text it reached the engine and failed as
3818
+ // "Cannot read properties of undefined (reading 'length')" (gate, P2).
3819
+ .refine((v) => v.action !== 'cursive' || (typeof v.text === 'string' && v.text.trim() !== ''), {
3820
+ message: "cursive writes new text from `text`; it does not restyle an existing item. Pass text (the words to write); for a style on an existing text item use apply_style with its itemId.",
3821
+ path: ['text']
3822
+ });
3712
3823
  /**
3713
3824
  * pinepaper_text_effect — the 37 character-level text effects
3714
3825
  * (terminaltexteffects' vocabulary, reimplemented in FxTool from source).
@@ -3737,9 +3848,9 @@ export const TextStyleInputSchema = z.object({
3737
3848
  * `list_media` before promising a medium in prose.
3738
3849
  */
3739
3850
  export const DesignMediumInputSchema = z.object({
3740
- action: z.enum(['list_media', 'resolve', 'list_stitches', 'apply_thread', 'apply_hatch', 'list_flow_fields', 'list_hatch_options', 'apply'])
3851
+ action: z.enum(['list_media', 'resolve', 'list_stitches', 'apply_thread', 'apply_hatch', 'list_flow_fields', 'list_hatch_options', 'apply', 'sdf_stroke'])
3741
3852
  .describe("'apply' (render an item in ANY medium the engine can make: medium + options — watercolor, oil, cutPaper, thread, hatch) · 'list_media' (every medium with fidelity + limitation) · 'resolve' (can this medium be made here, and how honestly) · 'list_stitches' · 'apply_thread' (render an item in thread) · 'apply_hatch' (rule an item with hatching — value through line density) · 'list_flow_fields' · 'list_hatch_options'"),
3742
- medium: z.string().optional().describe(`resolve / apply: medium key — ${DESIGN_MEDIA.join(', ')}. apply works for ${Object.keys(DESIGN_MEDIA_APPLY).join(', ')}; the others are refused by the engine with the reason (encaustic is absent here; charcoal and ink are brushes, not fills).`),
3853
+ medium: z.string().optional().describe(`resolve / apply: medium key — ${DESIGN_MEDIA.join(', ')}. apply works for ${Object.keys(DESIGN_MEDIA_APPLY).join(', ')}; a medium the studio cannot apply is refused by the engine with the reason. ink and charcoal, where the studio applies them, restyle the item IN PLACE (same id).`),
3743
3854
  options: z.record(z.string(), z.unknown()).optional().describe('apply: the medium\'s own options. watercolor: color, brush, water 0..1, opacity, edge 0..1 (the dark rim), angle, bleed, seed, keepSource. oil: color, brush, length, density, toneRange, relief, angle, seed, keepSource. cutPaper: color, facet, jitter, shadow, shadowColor, shadowOpacity, shadowOffset [dx, dy], seed, keepSource. thread / hatch: as apply_thread / apply_hatch.'),
3744
3855
  itemId: z.string().optional().describe('apply / apply_thread / apply_hatch: a closed path, compound path (holes kept), or a group of them (applied per path). The result takes the source\'s place in the stacking order and the source is hidden; applying again replaces it. One undo step.'),
3745
3856
  stitch: z.enum(THREAD_STITCHES).optional()
@@ -3756,9 +3867,9 @@ export const DesignMediumInputSchema = z.object({
3756
3867
  stitchLen: z.number().positive().optional().describe('apply_thread: nominal stitch length px (default 18).'),
3757
3868
  rowGap: z.number().positive().optional().describe('apply_thread: row spacing px. Defaults from the thread width so the rows abut; setting it wider is what makes a fill read as hatching.'),
3758
3869
  variance: z.number().min(0).max(1).optional().describe('apply_thread: length jitter 0..1 (default 0.35) — the variance IS the long-and-short shading.'),
3759
- width: z.number().positive().optional().describe('apply_thread: thread width px (default 2).'),
3870
+ width: z.number().positive().optional().describe('apply_thread: thread width px (default 2). sdf_stroke: stroke width px (default 8).'),
3760
3871
  sheen: z.boolean().optional().describe('apply_thread: draw the highlight along each stitch (default true) — thread is not matte.'),
3761
- color: z.string().optional().describe("apply_thread: thread colour, defaulting to the item's own fill."),
3872
+ color: z.string().optional().describe("apply_thread: thread colour, defaulting to the item's own fill. sdf_stroke: stroke colour."),
3762
3873
  seed: z.number().int().optional().describe('apply_thread: PRNG seed (default 1). Same seed stitches the same way.'),
3763
3874
  count: z.number().int().positive().optional().describe('apply_thread: stitch count for seed/satin.'),
3764
3875
  // THE OPTIONS THE ENGINE READS AND NOTHING NAMED. ThreadPainting.paint reads
@@ -3791,12 +3902,78 @@ export const DesignMediumInputSchema = z.object({
3791
3902
  flowField: z.enum(['hand', 'curved', 'zigzag', 'waves', 'seabed', 'spiral', 'columns']).optional()
3792
3903
  .describe('apply_hatch: bend each line along a flow field. The straight scanline is the ruling; the field is what makes it read as drawn rather than printed. See list_flow_fields.'),
3793
3904
  t: z.number().optional().describe('apply_hatch: flow-field time offset — advances the field without changing the seed.'),
3905
+ // --- SDF brush stroke (FxTool D5) ---
3906
+ points: z.array(z.union([
3907
+ z.object({ x: z.number(), y: z.number(), pressure: z.number().min(0).max(1).optional() }),
3908
+ z.tuple([z.number(), z.number()]),
3909
+ z.tuple([z.number(), z.number(), z.number()]),
3910
+ ])).min(1).optional().describe('sdf_stroke: the stroke path in canvas px, each {x, y, pressure?} or [x, y, pressure?]. Pressure 0..1 scales the width at that point.'),
3911
+ softness: z.number().min(0).max(1).optional().describe('sdf_stroke: edge softness 0..1 (default 0.2).'),
3912
+ opacity: z.number().min(0).max(1).optional().describe('sdf_stroke: 0..1 (default 1).'),
3913
+ resolution: z.number().positive().optional().describe('sdf_stroke: bitmap pixels per canvas px (default 2).'),
3914
+ name: z.string().optional().describe('sdf_stroke: item name.'),
3794
3915
  })
3795
3916
  .refine((v) => v.action !== 'apply_thread' || !!v.itemId, { message: 'apply_thread requires itemId', path: ['itemId'] })
3796
3917
  .refine((v) => v.action !== 'resolve' || !!v.medium, { message: 'resolve requires medium', path: ['medium'] })
3918
+ .refine((v) => v.action !== 'sdf_stroke' || (Array.isArray(v.points) && v.points.length > 0), { message: 'sdf_stroke requires points', path: ['points'] })
3797
3919
  .refine((v) => v.action !== 'apply_hatch' || !!v.itemId, { message: 'apply_hatch requires itemId', path: ['itemId'] })
3798
3920
  .refine((v) => v.action !== 'apply' || (!!v.itemId && !!v.medium), { message: 'apply requires itemId and medium', path: ['medium'] })
3799
3921
  .refine((v) => v.action !== 'apply' || !v.medium || DESIGN_MEDIA.includes(v.medium), { message: `unknown medium; the engine knows ${DESIGN_MEDIA.join(', ')}`, path: ['medium'] });
3922
+ /**
3923
+ * pinepaper_relight — a lighting pass over the 3D world layer (FxTool D5).
3924
+ * CPU-backed; normals from the world's luminance (no depth). Applied in the
3925
+ * preview and in PNG / PDF / MP4 / WebM; not GIF / APNG; not saved with the
3926
+ * project. Lights are validated by the engine, which names every error.
3927
+ */
3928
+ const RelightColorSchema = z.union([z.string(), z.tuple([z.number(), z.number(), z.number()])]);
3929
+ export const RelightInputSchema = z.object({
3930
+ action: z.enum(['set', 'get', 'clear', 'list_lights'])
3931
+ .describe("'set' (install or replace the rig) · 'get' (the current rig) · 'clear' (remove it) · 'list_lights' (the light types)"),
3932
+ lights: z.array(z.discriminatedUnion('type', [
3933
+ z.object({
3934
+ type: z.literal('point'),
3935
+ x: z.number(), y: z.number(),
3936
+ z: z.number().optional().describe('Height above the picture, px (default 120).'),
3937
+ radius: z.number().positive().optional().describe('Falloff radius, px (default 400).'),
3938
+ color: RelightColorSchema.optional(),
3939
+ intensity: z.number().min(0).optional().describe('Default 1.'),
3940
+ specular: z.number().min(0).optional().describe('Shininess exponent (default 32).'),
3941
+ }),
3942
+ z.object({
3943
+ type: z.literal('directional'),
3944
+ dir: z.tuple([z.number(), z.number(), z.number()]).describe('[dx, dy, dz]; [0, 0, -1] faces the viewer.'),
3945
+ color: RelightColorSchema.optional(),
3946
+ intensity: z.number().min(0).optional(),
3947
+ }),
3948
+ ])).max(8).optional().describe('set: up to 8 lights. Coordinates are export-frame px. Colours are #rrggbb or [r, g, b].'),
3949
+ ambient: z.number().min(0).optional().describe('set: ambient light (default 0.2).'),
3950
+ strength: z.number().positive().optional().describe('set: normal-map strength (default 2).'),
3951
+ enabled: z.boolean().optional().describe('set: false keeps the rig but turns it off.'),
3952
+ preview: z.union([z.boolean(), z.object({ maxSide: z.number().min(64).max(4096) })]).optional()
3953
+ .describe('set: show it in the live preview (capped-resolution overlay); {maxSide} sets the cap.'),
3954
+ }).refine((v) => v.action !== 'set' || (Array.isArray(v.lights) && v.lights.length > 0) || v.enabled === false, { message: 'set requires at least one light (or enabled: false)', path: ['lights'] });
3955
+ /**
3956
+ * pinepaper_shader_graph — validated JSON pixel graphs (FxTool D5), CPU-backed.
3957
+ * Linear chains only; the engine names every error (unknown type, duplicate,
3958
+ * dangling edge, cycle, branching).
3959
+ */
3960
+ const ShaderGraphJsonSchema = z.object({
3961
+ id: z.string().optional(),
3962
+ nodes: z.array(z.object({ id: z.string(), type: z.string(), params: z.record(z.string(), z.unknown()).optional() })).min(1),
3963
+ edges: z.array(z.object({ from: z.string(), to: z.string() })).optional(),
3964
+ });
3965
+ export const ShaderGraphInputSchema = z.object({
3966
+ action: z.enum(['node_types', 'validate', 'create', 'get', 'list', 'remove', 'apply'])
3967
+ .describe("'node_types' (the node vocabulary + params) · 'validate' (check a graph, store nothing) · 'create' (validate + store) · 'get' / 'remove' (by graphId) · 'list' · 'apply' (run a graph over an item or the scene)"),
3968
+ graph: ShaderGraphJsonSchema.optional().describe('validate / create / apply: {id?, nodes: [{id, type, params?}], edges: [{from, to}]} — a linear chain.'),
3969
+ graphId: z.string().optional().describe('get / remove / apply: a stored graph id.'),
3970
+ target: z.string().optional().describe("apply: an itemId, or 'scene'."),
3971
+ keepSource: z.boolean().optional().describe('apply to an item: keep the source visible (default: hidden).'),
3972
+ asItem: z.boolean().optional().describe("apply to 'scene': add the result as an image item instead of returning it."),
3973
+ })
3974
+ .refine((v) => !['validate', 'create'].includes(v.action) || !!v.graph, { message: 'validate / create require graph', path: ['graph'] })
3975
+ .refine((v) => !['get', 'remove'].includes(v.action) || !!v.graphId, { message: 'get / remove require graphId', path: ['graphId'] })
3976
+ .refine((v) => v.action !== 'apply' || (!!v.target && (!!v.graph || !!v.graphId)), { message: "apply requires target (an itemId or 'scene') and graph or graphId", path: ['target'] });
3800
3977
  export const TextEffectInputSchema = z.object({
3801
3978
  action: z.enum(['apply', 'list'])
3802
3979
  .describe("'apply' (explode a text item into animated characters) · 'list' (the 37 effects with key/label/definition, for a picker)"),
@@ -3985,7 +4162,7 @@ export const World3DInputSchema = z.object({
3985
4162
  }).optional().describe("add_material / set_material: a NAMED, SHARED surface referenced by many objects, so one edit restyles all of them. metalness and roughness reach the shader on the MESH path only. There are no aoMapIntensity / normalScale / envMapIntensity knobs: each scales a map that does not exist, and a knob that scales nothing is worse than a missing one."),
3986
4163
  materialId: z.string().optional().describe('set_material / remove_material: the material id.'),
3987
4164
  // --- Import ---------------------------------------------------------------
3988
- source: z.string().optional().describe('import_obj: the OBJ file TEXT. import_gltf: a URL, or a data: URI of the .glb bytes.'),
4165
+ source: z.string().optional().describe('import_obj: the OBJ file TEXT. import_gltf: the .gltf JSON TEXT with its buffers embedded. A URL, data: URI or file path is NOT fetched (the string is parsed as JSON), so a .glb cannot be imported here.'),
3989
4166
  importOptions: z.record(z.string(), z.unknown()).optional().describe('import_obj / import_gltf: placement and scale options, passed through to the engine.'),
3990
4167
  clip: z.string().optional().describe('set_mesh_clip: which imported animation clip a skinned mesh plays.'),
3991
4168
  crossfade: z.number().optional().describe('set_mesh_clip: seconds to blend out of the clip that was playing. The blend happens in LOCAL pose space — a palette cannot be blended after the fact.'),
@@ -4098,7 +4275,7 @@ export const DesignSystemInputSchema = z.object({
4098
4275
  export const StickInputSchema = z.object({
4099
4276
  action: z.enum(['figure', 'set']).describe("'figure' a posed, walking or travelling stick figure · 'set' the floor, wall and objects it stands on and among."),
4100
4277
  id: z.string().optional().describe('Prefix for the created items.'),
4101
- kind: z.string().optional().describe("figure: which figure. set: which piece of the scene kit ('floor', 'wall', a named object) — list-free, because the kit is vendored and grows there."),
4278
+ kind: z.string().optional().describe("figure: which figure. set: which piece — room (floor + wall; options groundY, wall, floor), tabletop (object + surfaceY), chair (facing), table, counter, door, shelf (items), window. An unknown kind or tabletop object is refused with the engine's current list."),
4102
4279
  at: z.object({ x: z.number().optional(), y: z.number().optional() }).optional().describe('Where it goes.'),
4103
4280
  scale: z.number().optional().describe('Size multiplier.'),
4104
4281
  facing: z.enum(['left', 'right']).optional().describe("set: which way a CHAIR faces. The figure has no facing — it is ignored on action 'figure', which refuses it by name rather than dropping it."),
@@ -4164,7 +4341,7 @@ export const InterchangeInputSchema = z.object({
4164
4341
  .describe("'export_lottie' (JSON) · 'export_dotlottie' (the zipped .lottie) · 'import_lottie' (a Lottie or .lottie back onto the canvas) · 'export_glb' (the perspective 3D objects — needs some to exist) · 'export_bvh' (a skeleton's motion — needs a rig) · 'export_png_sequence' (one file per frame)."),
4165
4342
  options: z.record(z.string(), z.unknown()).optional().describe('Format options, passed through to the exporter: frame range, dimensions, fps, naming.'),
4166
4343
  data: z.union([z.string(), z.record(z.string(), z.unknown())]).optional()
4167
- .describe('import_lottie: the Lottie JSON — an object, a JSON string, or a URL.'),
4344
+ .describe('import_lottie: the Lottie JSON — an object or a JSON string. A URL or a file path is NOT fetched: read the file and pass its contents.'),
4168
4345
  skeletonId: z.string().optional().describe('export_bvh: which skeleton to write. A BVH is one skeleton\'s motion, so this is not optional in practice.'),
4169
4346
  })
4170
4347
  .refine((v) => v.action !== 'import_lottie' || v.data !== undefined, { message: 'import_lottie requires data', path: ['data'] })
@@ -4260,7 +4437,7 @@ export const SoundInputSchema = z.object({
4260
4437
  * pinepaper_motion — the generators' Animation knob, pointed at anything.
4261
4438
  *
4262
4439
  * The same engine every generator's own animation runs on, reachable for any
4263
- * group or list of items. Two kinds: a GROUP motion (drift, sway, rotate,
4440
+ * group or list of items. Two kinds: a GROUP motion (driftX/Y, swayX/Y, rotate,
4264
4441
  * pulse, wave, bounce) moves the target as one; a FIELD motion (ripple,
4265
4442
  * breathe, undulate) sweeps a crest through the children from an origin, with
4266
4443
  * a chosen waveform. The field kind is the one nothing else here can do.
@@ -4273,7 +4450,7 @@ export const MotionInputSchema = z.object({
4273
4450
  action: z.enum(['list', 'apply']).describe("'list' every motion, waveform and origin the engine offers · 'apply' one to a target."),
4274
4451
  itemId: z.string().optional().describe('apply: a group or single item to move.'),
4275
4452
  itemIds: z.array(z.string()).optional().describe('apply: several items, wrapped in a new group that becomes the motion host. The members keep their own identity and ids.'),
4276
- motion: z.string().optional().describe("apply: the motion name — a GROUP motion (drift, sway, rotate, pulse, wave, bounce) moves the target as one; a FIELD motion (ripple, breathe, undulate) sweeps a crest through its children. Call 'list' for the engine's own set rather than guessing."),
4453
+ motion: z.string().optional().describe("apply: the motion name — a GROUP motion (driftX, driftY, swayX, swayY, rotate, pulse, wave, bounce) moves the target as one; a FIELD motion (ripple, breathe, undulate) sweeps a crest through its children. Call 'list' for the engine's own set rather than guessing."),
4277
4454
  speed: z.number().optional().describe('apply: cycles per second, roughly. Higher is faster.'),
4278
4455
  intensity: z.number().optional().describe('apply: amplitude. 0.15 is the usual default elsewhere in this surface.'),
4279
4456
  waveform: z.enum(['sine', 'triangle', 'square', 'sawtooth', 'spike']).optional()
@@ -4472,7 +4649,8 @@ export const RiggingInputSchema = z.object({
4472
4649
  duration: z.number().positive().optional().describe('Seconds to traverse the path (default 1) — set_target_path.'),
4473
4650
  loop: z.boolean().optional().describe('Cycle the path — set_target_path.'),
4474
4651
  })
4475
- .refine((v) => ['create_skeleton', 'import_bvh', 'import_spine'].includes(v.action) || !!v.skeletonId, { message: 'this action requires skeletonId', path: ['skeletonId'] })
4652
+ // Listing ALL skeletons or pose libraries needs no skeleton (gate D6, 1.6.19).
4653
+ .refine((v) => ['create_skeleton', 'import_bvh', 'import_spine', 'list_skeletons', 'list_pose_libraries'].includes(v.action) || !!v.skeletonId, { message: 'this action requires skeletonId', path: ['skeletonId'] })
4476
4654
  .refine((v) => v.action !== 'add_bone' || v.skeletonId != null, { message: 'add_bone requires skeletonId', path: ['skeletonId'] })
4477
4655
  .refine((v) => v.action !== 'attach_item' || (!!v.boneId && !!v.itemId), { message: 'attach_item requires boneId and itemId', path: ['boneId'] })
4478
4656
  .refine((v) => v.action !== 'create_ik_chain' || (!!v.boneIds && v.boneIds.length >= 2), { message: 'create_ik_chain requires boneIds (≥2)', path: ['boneIds'] })