@pinepaper.studio/mcp-server 1.5.4 → 1.6.0

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 (88) hide show
  1. package/README.de.md +71 -0
  2. package/README.es.md +71 -0
  3. package/README.fr.md +71 -0
  4. package/README.hi.md +71 -0
  5. package/README.ja.md +71 -0
  6. package/README.ko.md +71 -0
  7. package/README.md +513 -272
  8. package/README.pt-BR.md +71 -0
  9. package/README.zh-CN.md +71 -0
  10. package/assets/easing-splashes.svg +152 -0
  11. package/assets/equation-path.svg +77 -0
  12. package/assets/hero.svg +126 -0
  13. package/assets/live-badge.svg +62 -0
  14. package/assets/poster-sunburst.svg +106 -0
  15. package/dist/__tests__/fixtures/mock-relations.d.ts +48 -0
  16. package/dist/__tests__/fixtures/mock-relations.d.ts.map +1 -1
  17. package/dist/__tests__/fixtures/mock-relations.js +31 -0
  18. package/dist/__tests__/fixtures/mock-relations.js.map +1 -1
  19. package/dist/agent/types.d.ts +6 -6
  20. package/dist/browser/puppeteer-controller.d.ts +34 -0
  21. package/dist/browser/puppeteer-controller.d.ts.map +1 -1
  22. package/dist/browser/puppeteer-controller.js +116 -5
  23. package/dist/browser/puppeteer-controller.js.map +1 -1
  24. package/dist/cli.js +2 -1
  25. package/dist/cli.js.map +1 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +35 -5
  28. package/dist/index.js.map +1 -1
  29. package/dist/ontology/index.d.ts +2 -0
  30. package/dist/ontology/index.d.ts.map +1 -1
  31. package/dist/ontology/index.js +2 -0
  32. package/dist/ontology/index.js.map +1 -1
  33. package/dist/ontology/ontology-compiler.d.ts +78 -0
  34. package/dist/ontology/ontology-compiler.d.ts.map +1 -0
  35. package/dist/ontology/ontology-compiler.js +235 -0
  36. package/dist/ontology/ontology-compiler.js.map +1 -0
  37. package/dist/ontology/ontology-validator.d.ts +103 -0
  38. package/dist/ontology/ontology-validator.d.ts.map +1 -0
  39. package/dist/ontology/ontology-validator.js +573 -0
  40. package/dist/ontology/ontology-validator.js.map +1 -0
  41. package/dist/ontology/types.d.ts +1 -0
  42. package/dist/ontology/types.d.ts.map +1 -1
  43. package/dist/ontology/validator.d.ts +6 -0
  44. package/dist/ontology/validator.d.ts.map +1 -1
  45. package/dist/ontology/validator.js +34 -5
  46. package/dist/ontology/validator.js.map +1 -1
  47. package/dist/ontology/vocabulary.d.ts.map +1 -1
  48. package/dist/ontology/vocabulary.js +91 -3
  49. package/dist/ontology/vocabulary.js.map +1 -1
  50. package/dist/tools/compact-descriptions.d.ts.map +1 -1
  51. package/dist/tools/compact-descriptions.js +0 -1
  52. package/dist/tools/compact-descriptions.js.map +1 -1
  53. package/dist/tools/definitions.d.ts +1 -1
  54. package/dist/tools/definitions.d.ts.map +1 -1
  55. package/dist/tools/definitions.js +997 -573
  56. package/dist/tools/definitions.js.map +1 -1
  57. package/dist/tools/handlers/camera.d.ts.map +1 -1
  58. package/dist/tools/handlers/camera.js +7 -1
  59. package/dist/tools/handlers/camera.js.map +1 -1
  60. package/dist/tools/handlers/maps.d.ts.map +1 -1
  61. package/dist/tools/handlers/maps.js +54 -1
  62. package/dist/tools/handlers/maps.js.map +1 -1
  63. package/dist/tools/handlers/ontology.d.ts.map +1 -1
  64. package/dist/tools/handlers/ontology.js +15 -3
  65. package/dist/tools/handlers/ontology.js.map +1 -1
  66. package/dist/tools/handlers.d.ts.map +1 -1
  67. package/dist/tools/handlers.js +160 -113
  68. package/dist/tools/handlers.js.map +1 -1
  69. package/dist/tools/minimal-descriptions.d.ts.map +1 -1
  70. package/dist/tools/minimal-descriptions.js +23 -16
  71. package/dist/tools/minimal-descriptions.js.map +1 -1
  72. package/dist/tools/toolkits.d.ts.map +1 -1
  73. package/dist/tools/toolkits.js +28 -22
  74. package/dist/tools/toolkits.js.map +1 -1
  75. package/dist/types/code-generator.d.ts +190 -36
  76. package/dist/types/code-generator.d.ts.map +1 -1
  77. package/dist/types/code-generator.js +1216 -571
  78. package/dist/types/code-generator.js.map +1 -1
  79. package/dist/types/schemas.d.ts +2826 -829
  80. package/dist/types/schemas.d.ts.map +1 -1
  81. package/dist/types/schemas.js +595 -79
  82. package/dist/types/schemas.js.map +1 -1
  83. package/dist/version.d.ts +11 -0
  84. package/dist/version.d.ts.map +1 -0
  85. package/dist/version.js +18 -0
  86. package/dist/version.js.map +1 -0
  87. package/manifest.json +299 -71
  88. package/package.json +14 -3
@@ -92,6 +92,12 @@ export const ItemTypeSchema = z.enum([
92
92
  'path',
93
93
  'line',
94
94
  'arc',
95
+ // Named shape variants (regular polygons + paths) — createable directly by name
96
+ 'pentagon',
97
+ 'hexagon',
98
+ 'diamond',
99
+ 'arrow',
100
+ 'heart',
95
101
  ]);
96
102
  // Text item properties
97
103
  export const TextPropertiesSchema = z.object({
@@ -217,6 +223,40 @@ export const RelationTypeSchema = z.enum([
217
223
  'group_morphs_to',
218
224
  // Self-relation; item moves along a custom-drawn path stored in params
219
225
  'moves_along_path',
226
+ // Geometric construction constraints (Layer 2) — the source is RE-DERIVED each
227
+ // frame from its anchor item(s), so dragging an anchor updates it live
228
+ // (GeoGebra-style). Anchor A is the relation target; extra anchors ride in params.
229
+ 'is_midpoint_of', // source = midpoint(target, params.other)
230
+ 'lies_on_line', // source on line target→params.other at fraction params.t
231
+ 'is_centroid_of', // source = centroid(target, ...params.others)
232
+ 'is_circumcenter_of', // source = circumcenter(target, params.other1, params.other2)
233
+ 'concentric_with', // source shares the target's center
234
+ 'construction_reveal', // self-relation: opacity 0→1 at params.revealAt over params.fadeIn (timeline-driven)
235
+ // Procedural / deterministic property binding (Expression IR — FxTool S10 G1).
236
+ // params.signal:true routes through the pure, seed/frame-deterministic signal
237
+ // interpreter (replay-stable), else the per-frame compute fallback runs.
238
+ 'driven_by', // source property = target property * multiplier + offset (+clamp); drives color/stroke/opacity/scale/pos. Needs a target.
239
+ 'time_expression', // self-relation: source property driven by a math expression f(t, v) — t=time, v=params.baseValue
240
+ // Event-driven scene chains (FxTool S11). The source is a pp:event (make one
241
+ // with pinepaper_event); firing it runs these reactions. on_event_fire_after
242
+ // chains events over canvas/wall time; on_event_add_relation mutates the graph
243
+ // (the scene evolves itself). Kick a chain off with pinepaper_event pulse.
244
+ 'on_event_fire_after', // event→event: when source fires, pulse target after params.delay (timeline: wall|canvas)
245
+ 'on_event_add_relation', // event→item: add params.type relation from item to params.target when the event fires
246
+ 'on_event_remove_relation', // event→item: remove params.type relation from item to params.target
247
+ 'on_event_set_color', // event→item: set fill/stroke to params.color
248
+ 'on_event_set_property', // event→item: set item[params.property] = params.value
249
+ 'on_event_set_visibility', // event→item: show/hide (params.visible, default true)
250
+ // Structural layout relations (FxTool S12-E1) — STATIC composition as graph edges.
251
+ // Placement is derived from the TARGET'S BOUNDS and re-derived each frame, so
252
+ // dragging or resizing the target moves the dependent live. Use these instead of
253
+ // hardcoding x/y when the intent is "on", "under", "next to", "inside".
254
+ 'on_top_of', // source bottom edge rests on target top edge (params: gap, align, overhang)
255
+ 'below', // source top edge rests on target bottom edge (params: gap, align, overhang)
256
+ 'beside', // source flanks the target left/right (params: side, gap, align)
257
+ 'inside', // source anchored inside the target's bounds (params: anchor, padding)
258
+ 'centered_on', // source center = target center + (offsetX, offsetY)
259
+ 'aligned_with', // source matches target on ONE axis only — the other stays free (params: axis, offset)
220
260
  ]).describe('Type of relationship between items');
221
261
  // Relation parameter schemas
222
262
  export const OrbitsParamsSchema = z.object({
@@ -337,19 +377,207 @@ export const GroupMorphsToParamsSchema = z.object({
337
377
  easing: z.enum(['linear', 'easeIn', 'easeOut', 'easeInOut']).optional().default('easeInOut').describe('Easing for the position interpolation'),
338
378
  deformLines: z.boolean().optional().default(true).describe('Path.Line children deform via segment endpoints instead of rigid translation'),
339
379
  });
380
+ // =============================================================================
381
+ // EQUATION-DRIVEN PATHS (Expression IR — FxTool S10 B5)
382
+ // Deterministic: math strings are parsed via expr-to-ir (no eval), sampled on
383
+ // the Expression IR. Shared by pinepaper_equation_path (geometry) and the
384
+ // moves_along_path `equation` motion source.
385
+ // =============================================================================
386
+ // Curated curve library (curvePreset). Each is a parametric preset.
387
+ export const CurvePresetSchema = z.enum(['spiral', 'rose', 'lissajous', 'astroid', 'heart', 'spirograph'])
388
+ .describe('Curated curve preset');
389
+ // One Fourier term for kind:"fourier".
390
+ export const HarmonicSchema = z.object({
391
+ freq: z.number().describe('Harmonic frequency'),
392
+ amp: z.number().describe('Amplitude'),
393
+ phase: z.number().optional().describe('Phase offset (radians)'),
394
+ });
395
+ // Chained parametric warp — displaces each sampled point by (dx, dy),
396
+ // expressions of x, y (point coords) and t (0..1 along the path).
397
+ export const EquationWarpSchema = z.object({
398
+ dx: z.string().optional().describe('x displacement expression of x, y, t (0..1)'),
399
+ dy: z.string().optional().describe('y displacement expression of x, y, t (0..1)'),
400
+ });
401
+ // The math-source fields common to both equation surfaces (no placement).
402
+ const equationSpecShape = {
403
+ kind: z.enum(['function', 'parametric', 'fourier', 'preset']).optional().default('function').describe("'function' y=f(variable) · 'parametric' x=fx(param),y=fy(param) · 'fourier' harmonics · 'preset' curated curve"),
404
+ expr: z.string().optional().describe("y = f(variable) — kind 'function', e.g. 'sin(x)*80'"),
405
+ xExpr: z.string().optional().describe("x = fx(param) — kind 'parametric'"),
406
+ yExpr: z.string().optional().describe("y = fy(param) — kind 'parametric'"),
407
+ harmonics: z.array(HarmonicSchema).optional().describe("Fourier terms — kind 'fourier'"),
408
+ preset: CurvePresetSchema.optional().describe("Curated curve — kind 'preset'"),
409
+ variable: z.string().optional().default('x').describe("Sample variable for kind 'function'"),
410
+ param: z.string().optional().default('t').describe("Sample variable for kind 'parametric'/'preset'"),
411
+ min: z.number().optional().describe('Sample range start'),
412
+ max: z.number().optional().describe('Sample range end'),
413
+ samples: z.number().optional().default(200).describe('Number of samples along the range'),
414
+ scale: z.number().optional().default(1).describe('Multiply equation coords before placing'),
415
+ flipY: z.boolean().optional().default(true).describe('Math y-up → screen y-down'),
416
+ warp: z.union([EquationWarpSchema, z.array(EquationWarpSchema)]).optional().describe('Chained parametric warp(s) applied to the sampled points'),
417
+ };
340
418
  // Self-relation: item is driven along a custom-drawn path stored in params.
341
419
  // In the editor the Relations picker offers a drag-to-draw capture mode; via
342
420
  // MCP / agent code, pass the points array explicitly.
421
+ // Equation motion source for moves_along_path (S10 B5): the item TRAVERSES a
422
+ // math curve instead of a drawn point list. Origin is cx/cy (default 0) —
423
+ // distinct from pinepaper_equation_path's x/y (canvas-center default).
424
+ // .passthrough(): preset tuning params (e.g. rose's k/r, spiral's turns) ride on
425
+ // the top-level object — FxTool does curvePreset(eq.preset, eq) — so they must
426
+ // survive validation instead of being stripped.
427
+ export const MovesAlongPathEquationSchema = z.object({
428
+ ...equationSpecShape,
429
+ cx: z.number().optional().describe('Canvas origin x for the sampled curve (default 0)'),
430
+ cy: z.number().optional().describe('Canvas origin y for the sampled curve (default 0)'),
431
+ }).passthrough();
343
432
  export const MovesAlongPathParamsSchema = z.object({
433
+ // path OR equation — one is required. equation overrides path when both are set.
344
434
  path: z.array(z.union([
345
435
  z.object({ x: z.number(), y: z.number() }),
346
436
  z.tuple([z.number(), z.number()]),
347
- ])).describe('Path points: array of {x,y} objects or [x,y] tuples'),
437
+ ])).optional().describe('Path points: array of {x,y} objects or [x,y] tuples (omit when using `equation`)'),
438
+ equation: MovesAlongPathEquationSchema.optional().describe('Equation motion source (S10 B5): traverse a math curve instead of a drawn path — deterministic, sampled on the Expression IR'),
348
439
  speed: z.number().optional().default(1).describe('Speed multiplier (1 ≈ 150 px/s)'),
349
440
  closed: z.boolean().optional().default(true).describe('Loop back to start at end of path'),
350
441
  phase: z.number().optional().default(0).describe('Starting position along path (0–1)'),
351
442
  easing: z.enum(['linear', 'easeIn', 'easeOut', 'easeInOut', 'sine', 'bounce', 'pingpong']).optional().default('linear').describe('Motion curve mapping normalized time → progress along path'),
352
443
  });
444
+ // pinepaper_equation_path input — creates a path ITEM from a math equation.
445
+ // Placement is x/y (canvas center default) + style, on top of the shared spec.
446
+ // .passthrough(): preset tuning params (rose's k/r, spiral's turns, …) ride on
447
+ // the top-level object — FxTool does curvePreset(opts.preset, opts.presetParams
448
+ // || opts) — so they must survive validation.
449
+ export const EquationPathInputSchema = z.object({
450
+ ...equationSpecShape,
451
+ x: z.number().optional().describe('Canvas origin x (default: canvas center)'),
452
+ y: z.number().optional().describe('Canvas origin y (default: canvas center)'),
453
+ style: z.record(z.unknown()).optional().describe('Path style forwarded to create (strokeColor, strokeWidth, fillColor, …)'),
454
+ }).passthrough();
455
+ // Procedural / deterministic property binding (Expression IR — FxTool S10 G1).
456
+ // driven_by: source's `sourceProperty` = target's `targetProperty` * multiplier
457
+ // + offset (optionally clamped). For fillColor/strokeColor the driven value
458
+ // (clamped 0..1) interpolates colorFrom→colorTo — a relation that drives COLOR.
459
+ // signal:true evaluates via the pure IR signal interpreter (frame-deterministic,
460
+ // x/y target props track a moving target); else the per-frame compute runs.
461
+ export const DrivenByParamsSchema = z.object({
462
+ sourceProperty: z.enum(['x', 'y', 'rotation', 'opacity', 'scale', 'strokeWidth', 'fillColor', 'strokeColor']).optional().default('opacity').describe('Property on the SOURCE item to drive'),
463
+ targetProperty: z.enum(['x', 'y', 'rotation', 'opacity', 'scale']).optional().default('x').describe('Property on the TARGET item to read'),
464
+ multiplier: z.number().optional().default(1).describe('Scale factor applied to the target value'),
465
+ offset: z.number().optional().default(0).describe('Offset added after the multiplier'),
466
+ clamp: z.object({ min: z.number().optional(), max: z.number().optional() }).optional().describe('Optional {min, max} clamp on the result'),
467
+ colorFrom: z.string().optional().default('#000000').describe('Color at driven value 0 (fillColor/strokeColor sourceProperty)'),
468
+ colorTo: z.string().optional().default('#ffffff').describe('Color at driven value 1 (fillColor/strokeColor sourceProperty)'),
469
+ signal: z.boolean().optional().default(false).describe('Deterministic IR-signal mode — replay-stable; x/y target props track a moving target'),
470
+ });
471
+ // time_expression: self-relation (targetId=null). The source's `property` is
472
+ // driven by a math expression of t (scene time) and v (baseValue). signal:true
473
+ // parses the expression into the Expression IR and evaluates a pure f(t)
474
+ // (replay-stable), falling back to per-frame if it uses random()/unknown symbols.
475
+ export const TimeExpressionParamsSchema = z.object({
476
+ property: z.string().optional().default('y').describe('Property to drive: x, y, rotation, opacity, scale'),
477
+ expression: z.string().optional().default('sin(t * 2) * 50 + 300').describe('Math expression of t (time) and v (baseValue), e.g. "sin(t*2)*50 + v"'),
478
+ baseValue: z.number().optional().default(0).describe('Base value accessible as v in the expression'),
479
+ signal: z.boolean().optional().default(false).describe('Deterministic IR mode — parse expression to IR, evaluate as pure f(t)'),
480
+ });
481
+ // =============================================================================
482
+ // EVENT-DRIVEN SCENE CHAINS (FxTool S11). Source of each is a pp:event.
483
+ // =============================================================================
484
+ // on_event_fire_after (event→event): chain a delayed pulse. timeline 'canvas'
485
+ // schedules on the animation clock (pauses/seeks/loops with the timeline) — use
486
+ // it for scene chains; 'wall' is real-world setTimeout ms.
487
+ export const OnEventFireAfterParamsSchema = z.object({
488
+ delay: z.number().min(0).optional().default(1000).describe('Delay before pulsing the target event (milliseconds)'),
489
+ timeline: z.enum(['wall', 'canvas']).optional().default('wall').describe("'canvas' = animation-clock time (scrub/loop-stable) · 'wall' = real setTimeout ms"),
490
+ });
491
+ // on_event_add_relation (event→item): when the event fires, add a relation FROM
492
+ // the target item TO params.target of type params.type. The scene mutates itself.
493
+ export const OnEventAddRelationParamsSchema = z.object({
494
+ type: z.string().describe('Relation type to add (e.g. orbits, moves_along_path, driven_by)'),
495
+ target: z.string().nullish().describe('Target item id for the added relation (null for self-relations)'),
496
+ params: z.record(z.unknown()).optional().default({}).describe('Params for the added relation'),
497
+ });
498
+ // on_event_remove_relation (event→item): tear down a relation added earlier.
499
+ export const OnEventRemoveRelationParamsSchema = z.object({
500
+ type: z.string().describe('Relation type to remove'),
501
+ target: z.string().nullish().describe('Target item id (null for self-relations)'),
502
+ });
503
+ // on_event_set_color (event→item).
504
+ export const OnEventSetColorParamsSchema = z.object({
505
+ color: z.string().describe('Color to apply when the event fires'),
506
+ which: z.enum(['fill', 'stroke']).optional().default('fill').describe('Which color channel to set'),
507
+ });
508
+ // on_event_set_property (event→item): set an arbitrary property.
509
+ export const OnEventSetPropertyParamsSchema = z.object({
510
+ property: z.string().describe('Item property to set (e.g. opacity, rotation, strokeWidth)'),
511
+ value: z.unknown().describe('Value to assign'),
512
+ });
513
+ // on_event_set_visibility (event→item).
514
+ export const OnEventSetVisibilityParamsSchema = z.object({
515
+ visible: z.boolean().optional().default(true).describe('Show (true) or hide (false) the item'),
516
+ });
517
+ // pinepaper_event tool — manage the pp:event channel.
518
+ // create → app.createEvent(name, {payloadType,x,y}) → returns eventId
519
+ // pulse → app.pulseEvent(eventId, payload) → kicks off a chain
520
+ export const EventInputSchema = z.object({
521
+ action: z.enum(['create', 'pulse']).describe("'create' a pp:event · 'pulse' it to fire listeners/chains"),
522
+ name: z.string().optional().describe("Event name — required for action 'create'"),
523
+ payloadType: z.string().optional().describe("Payload type label (default 'Pulse') — action 'create'"),
524
+ x: z.number().optional().describe('Canvas x for the event marker — action create'),
525
+ y: z.number().optional().describe('Canvas y for the event marker — action create'),
526
+ eventId: z.string().optional().describe("Event id to fire — required for action 'pulse'"),
527
+ payload: z.unknown().optional().describe("Optional value forwarded to listeners — action 'pulse'"),
528
+ }).refine((v) => v.action !== 'create' || !!v.name, { message: "action 'create' requires name", path: ['name'] })
529
+ .refine((v) => v.action !== 'pulse' || !!v.eventId, { message: "action 'pulse' requires eventId", path: ['eventId'] });
530
+ // =============================================================================
531
+ // STRUCTURAL LAYOUT RELATIONS (FxTool S12-E1)
532
+ // =============================================================================
533
+ // Placement expressed as a graph edge rather than baked-in coordinates. Every
534
+ // compute reads the TARGET'S bounds and the SOURCE'S own bounds, and re-runs each
535
+ // frame — move or resize the target and the dependent follows. All six need a
536
+ // target. `signal: true` routes through the pure Expression-IR port, which FxTool
537
+ // keeps bit-identical to the live compute.
538
+ // on_top_of: source bottom edge rests on the target's top edge.
539
+ // y = T.y - target.height/2 - gap - source.height/2
540
+ export const OnTopOfParamsSchema = z.object({
541
+ gap: z.number().optional().default(0).describe('Pixels between the two edges'),
542
+ align: z.enum(['left', 'center', 'right']).optional().default('center').describe('Horizontal alignment relative to the target'),
543
+ overhang: z.number().optional().default(0).describe('Extra signed x-offset in px'),
544
+ signal: z.boolean().optional().default(false).describe('Deterministic mode — route through the pure Expression-IR port'),
545
+ });
546
+ // below: the mirror of on_top_of — source top edge rests on the target's bottom edge.
547
+ export const BelowParamsSchema = z.object({
548
+ gap: z.number().optional().default(0).describe('Pixels between the two edges'),
549
+ align: z.enum(['left', 'center', 'right']).optional().default('center').describe('Horizontal alignment relative to the target'),
550
+ overhang: z.number().optional().default(0).describe('Extra signed x-offset in px'),
551
+ signal: z.boolean().optional().default(false).describe('Deterministic mode — route through the pure Expression-IR port'),
552
+ });
553
+ // beside: source flanks the target horizontally, aligned vertically.
554
+ export const BesideParamsSchema = z.object({
555
+ side: z.enum(['left', 'right']).optional().default('right').describe('Which side of the target'),
556
+ gap: z.number().optional().default(0).describe('Pixels between the two edges'),
557
+ align: z.enum(['top', 'center', 'bottom']).optional().default('center').describe('Vertical alignment relative to the target'),
558
+ signal: z.boolean().optional().default(false).describe('Deterministic mode — route through the pure Expression-IR port'),
559
+ });
560
+ // inside: source placed fully within the target's bounds at a 9-way anchor.
561
+ export const InsideParamsSchema = z.object({
562
+ anchor: z.enum(['center', 'top-left', 'top', 'top-right', 'left', 'right', 'bottom-left', 'bottom', 'bottom-right'])
563
+ .optional().default('center').describe('Where inside the target to anchor the source'),
564
+ padding: z.number().optional().default(0).describe('Inset from the target edge in px'),
565
+ signal: z.boolean().optional().default(false).describe('Deterministic mode — route through the pure Expression-IR port'),
566
+ });
567
+ // centered_on: source center = target center + offset (concentric when offset is 0).
568
+ export const CenteredOnParamsSchema = z.object({
569
+ offsetX: z.number().optional().default(0).describe('Signed x-offset from the target center'),
570
+ offsetY: z.number().optional().default(0).describe('Signed y-offset from the target center'),
571
+ signal: z.boolean().optional().default(false).describe('Deterministic mode — route through the pure Expression-IR port'),
572
+ });
573
+ // aligned_with: ONE axis of the source center matches the target's; the other axis
574
+ // is left untouched (FxTool writes only the returned component). `axis` is required —
575
+ // FxTool's compute returns undefined (relation does nothing) without it.
576
+ export const AlignedWithParamsSchema = z.object({
577
+ axis: z.enum(['x', 'y']).describe('Which axis to align (required — the relation is inert without it)'),
578
+ offset: z.number().optional().default(0).describe('Signed offset along that axis'),
579
+ signal: z.boolean().optional().default(false).describe('Deterministic mode — route through the pure Expression-IR port'),
580
+ });
353
581
  // =============================================================================
354
582
  // CUSTOM RELATION REGISTRATION
355
583
  // =============================================================================
@@ -439,6 +667,22 @@ export const GeneratorNameSchema = z.enum([
439
667
  'drawSimulation',
440
668
  'drawSpectrumAnalyzer',
441
669
  'draw3DSurface',
670
+ // S6 seeded procedural generators (curated presets + Motion group)
671
+ 'drawTruchet',
672
+ 'drawHalftone',
673
+ 'drawRibbons',
674
+ // GPU / GLSL math-art generators
675
+ 'drawFormulaArt',
676
+ 'drawParametricCollection',
677
+ 'drawShaderArt',
678
+ 'drawYeganehMountains',
679
+ // OKLCH seeded backgrounds + 3D parametric curve
680
+ 'drawBlobs',
681
+ 'drawLowPoly',
682
+ 'drawPeaks',
683
+ 'drawScatter',
684
+ 'drawStackedWaves',
685
+ 'draw3DParametricCurve',
442
686
  ]).describe('Background generator name');
443
687
  // ---------------------------------------------------------------------------
444
688
  // Generator parameter schemas — Phase 1 (enhanced existing)
@@ -607,7 +851,12 @@ export const EffectTypeSchema = z.enum([
607
851
  'fireflies',
608
852
  'shockwave',
609
853
  'trail',
610
- ]).describe('Visual effect type');
854
+ // Shader auras (WebGL2 silhouette-clipped) — routed to ItemAuraSystem by
855
+ // the same app.applyEffect entry point as the particle types.
856
+ 'heatmap',
857
+ 'liquid_metal',
858
+ 'gem_smoke',
859
+ ]).describe('Visual effect type (particle effects + shader auras)');
611
860
  export const SparkleParamsSchema = z.object({
612
861
  color: ColorSchema.optional().default('#fbbf24').describe('Sparkle color'),
613
862
  speed: z.number().optional().default(1).describe('Animation speed'),
@@ -674,6 +923,8 @@ export const CreateItemInputSchema = z.object({
674
923
  properties: z.record(z.unknown()).optional().default({}),
675
924
  animationType: z.string().optional().describe('Inline animation to apply on creation. Loop presets (pulse, rotate, bounce, fade, wobble, slide, typewriter) or "keyframe" with keyframes array. Equivalent to a follow-up pinepaper_animate / pinepaper_keyframe_animate call.'),
676
925
  animationSpeed: z.number().optional().describe('Speed multiplier for loop animations (default: 1.0). Ignored when animationType is "keyframe".'),
926
+ animationIntensity: z.number().optional().describe('Loop animation amplitude (0.1 = ±10%, default 0.15). Drives pulse/wobble/bounce/breathe amplitude; honored by SVG/SMIL/widget export.'),
927
+ animationDelay: z.number().optional().describe('Loop animation start delay in seconds.'),
677
928
  keyframes: z.array(KeyframeSchema).optional().describe('Required when animationType is "keyframe". Inline keyframe array attached at creation.'),
678
929
  });
679
930
  // Light direction for 3D effects
@@ -719,7 +970,7 @@ export const DeleteItemInputSchema = z.object({
719
970
  // Add Relation
720
971
  export const AddRelationInputSchema = z.object({
721
972
  sourceId: z.string().describe('Registry ID of the source item'),
722
- targetId: z.string().optional().describe('Registry ID of the target item (can be null for self-animations)'),
973
+ targetId: z.string().nullish().describe('Registry ID of the target item; null/omitted for self-relations (animates, moves_along_path, construction_reveal, time_expression)'),
723
974
  relationType: RelationTypeSchema,
724
975
  params: z.record(z.unknown()).optional().default({}),
725
976
  });
@@ -740,6 +991,8 @@ export const AnimateItemInputSchema = z.object({
740
991
  itemId: z.string().describe('Registry ID of the item'),
741
992
  animationType: SimpleAnimationTypeSchema,
742
993
  speed: z.number().optional().default(1).describe('Animation speed multiplier'),
994
+ intensity: z.number().optional().describe('Animation amplitude (animationIntensity): 0.1 = ±10%, 0.2 = ±20% (default 0.15). Drives pulse/wobble/bounce/breathe amplitude and is now honored by SVG/SMIL/widget export.'),
995
+ delay: z.number().optional().describe('Animation start delay in seconds (animationDelay).'),
743
996
  });
744
997
  // Keyframe Animate
745
998
  export const KeyframeAnimateInputSchema = z.object({
@@ -748,13 +1001,27 @@ export const KeyframeAnimateInputSchema = z.object({
748
1001
  duration: z.number().optional().describe('Total animation duration'),
749
1002
  loop: z.boolean().optional().default(false),
750
1003
  timeOffset: z.number().optional().describe('Shift the whole clip in canvas time (seconds). The animation only runs while canvas time is in [timeOffset, timeOffset + (clipOutPoint - clipInPoint)].'),
1004
+ timeUnits: z.enum(['seconds', 'ms']).optional().describe("Explicit units for keyframe times/duration. 'seconds' disables the legacy >100→ms auto-detect — REQUIRED for long-form timelines with second values over 100."),
751
1005
  clipInPoint: z.number().optional().describe('Skip the first N seconds of keyframe data — clip head trim.'),
752
1006
  clipOutPoint: z.number().optional().describe('Stop at N seconds into the keyframe data — clip tail trim. Default: lastKeyframeTime.'),
753
1007
  });
754
1008
  // Execute Generator
1009
+ //
1010
+ // Region (added FxTool c81781c): when set, the generator draws into a clipped
1011
+ // sub-group translated to (x,y) sized (width × height). Region runs imply
1012
+ // preserve (won't wipe the canvas) and re-running the same generator replaces
1013
+ // its prior region. Different generators' regions coexist. Invalid/zero-size
1014
+ // regions fall back to full-canvas FxTool-side.
1015
+ export const GeneratorRegionSchema = z.object({
1016
+ x: z.number().describe('Region top-left X in canvas coords'),
1017
+ y: z.number().describe('Region top-left Y in canvas coords'),
1018
+ width: z.number().positive().describe('Region width in pixels'),
1019
+ height: z.number().positive().describe('Region height in pixels'),
1020
+ }).describe('Optional sub-region {x, y, width, height} the generator draws into.');
755
1021
  export const ExecuteGeneratorInputSchema = z.object({
756
1022
  generatorName: GeneratorNameSchema,
757
1023
  params: z.record(z.unknown()).optional().default({}),
1024
+ region: GeneratorRegionSchema.optional(),
758
1025
  });
759
1026
  // Apply Effect
760
1027
  export const ApplyEffectInputSchema = z.object({
@@ -777,6 +1044,10 @@ export const PlayTimelineInputSchema = z.object({
777
1044
  duration: z.number().optional().describe('Duration for play action'),
778
1045
  loop: z.boolean().optional(),
779
1046
  time: z.number().optional().describe('Time to seek to'),
1047
+ deterministic: z
1048
+ .boolean()
1049
+ .optional()
1050
+ .describe('For seek: evaluate the scene at the exact time via app.sceneAt(t) — ticks keyframes + relations + generators deterministically (same t → same frame), not just the keyframe state. Use before a screenshot for a reproducible frame.'),
780
1051
  });
781
1052
  // Canvas Control
782
1053
  export const SetBackgroundColorInputSchema = z.object({
@@ -1333,7 +1604,7 @@ export const AgentResetInputSchema = z.object({
1333
1604
  */
1334
1605
  export const AgentBatchOperationTypeSchema = z.enum([
1335
1606
  'set_canvas_size', 'set_background', 'execute_generator',
1336
- 'create', 'modify', 'delete',
1607
+ 'create', 'modify', 'delete', 'group',
1337
1608
  'animate', 'keyframe_animate', 'relation',
1338
1609
  'apply_mask', 'apply_effect',
1339
1610
  'play_timeline',
@@ -1352,6 +1623,9 @@ export const AgentBatchOperationSchema = z.object({
1352
1623
  properties: z.record(z.unknown()).optional().describe('Properties for create/modify operations'),
1353
1624
  // Modify/Animate/Delete/Keyframe/Mask/Effect target
1354
1625
  itemId: z.string().optional().describe('Target item ID or $N reference'),
1626
+ // Group operation fields
1627
+ itemIds: z.array(z.string()).optional().describe('Item IDs or $N references to combine for a "group" operation (the parts of a composite object).'),
1628
+ groupName: z.string().optional().describe('Optional name for the group created by a "group" operation (e.g. "car").'),
1355
1629
  // Animate operation fields
1356
1630
  animationType: z.string().optional().describe('Animation type for animate operations'),
1357
1631
  animationOptions: z.record(z.unknown()).optional().describe('Animation options'),
@@ -1365,6 +1639,7 @@ export const AgentBatchOperationSchema = z.object({
1365
1639
  // Execute generator fields
1366
1640
  generatorName: z.string().optional().describe('Generator name for execute_generator'),
1367
1641
  generatorParams: z.record(z.unknown()).optional().describe('Generator parameters'),
1642
+ generatorRegion: GeneratorRegionSchema.optional().describe('Optional sub-region {x, y, width, height} for execute_generator'),
1368
1643
  // Set canvas size fields
1369
1644
  width: z.number().min(100).max(4096).optional().describe('Canvas width for set_canvas_size (100-4096)'),
1370
1645
  height: z.number().min(100).max(4096).optional().describe('Canvas height for set_canvas_size (100-4096)'),
@@ -1388,6 +1663,57 @@ export const AgentBatchOperationSchema = z.object({
1388
1663
  action: z.string().optional().describe('Timeline action for play_timeline (play, stop, seek)'),
1389
1664
  time: z.number().optional().describe('Seek time for play_timeline'),
1390
1665
  }).describe('Single batch operation');
1666
+ // Group / ungroup — combine the parts of a composite object into ONE draggable entity
1667
+ // (a car's body + wheels + windows), or break a group back into loose items.
1668
+ export const GroupInputSchema = z.object({
1669
+ action: z.enum(['group', 'ungroup', 'break_apart']).describe('group: combine itemIds into one entity. ungroup: dissolve a group back into loose items. break_apart: split an imported SVG / group / compound-path into individually movable parts (re-grouped as one).'),
1670
+ itemIds: z.array(z.string()).optional().describe('For group: registry IDs of the items to combine.'),
1671
+ itemId: z.string().optional().describe('For break_apart: registry ID of the imported SVG / group / compound-path to decompose into parts.'),
1672
+ groupName: z.string().optional().describe('For group: optional name (e.g. "car", "robot").'),
1673
+ groupId: z.string().optional().describe('For ungroup: registry ID of the group to dissolve.'),
1674
+ });
1675
+ // Camera director — compile a high-level SHOT LIST into one camera_animates walkthrough
1676
+ // (FxTool's DirectorCompiler). 'auto' derives a shot per item; 'shots' takes an explicit list.
1677
+ export const CameraDirectorInputSchema = z.object({
1678
+ action: z.enum(['auto', 'shots']).describe("auto: generate a default walkthrough (one shot per item). shots: apply an explicit shot list."),
1679
+ shots: z
1680
+ .array(z.object({
1681
+ subjects: z.union([z.literal('everything'), z.array(z.string())]).describe("'everything' or item registry IDs the shot frames"),
1682
+ framing: z.union([z.enum(['tight', 'medium', 'wide']), z.number()]).optional().describe('Framing tightness (or an explicit zoom number). Default medium.'),
1683
+ hold: z.number().optional().describe('Seconds to dwell on the shot (default 1.2).'),
1684
+ moveIn: z.string().optional().describe("Move style, e.g. 'push-in' | 'pan'."),
1685
+ ease: z.enum(['smooth', 'linear', 'settle', 'snap']).optional().describe('Easing between shots (default smooth).'),
1686
+ transition: z.number().optional().describe('Seconds to travel into this shot (default 1.0).'),
1687
+ }))
1688
+ .optional()
1689
+ .describe('For action "shots": ordered shot list. Each shot frames its subjects at the given tightness.'),
1690
+ order: z.enum(['reading', 'reverse', 'creation']).optional().describe('For action "auto": item visiting order (default reading).'),
1691
+ hold: z.number().optional().describe('For action "auto": per-shot dwell seconds.'),
1692
+ establishing: z.boolean().optional().describe('For action "auto": open with a wide establishing shot of everything.'),
1693
+ loop: z.boolean().optional().describe('Loop the camera walkthrough.'),
1694
+ });
1695
+ // On-device object detection (FxTool ImageWorkflow / DETR·YOLO). Find objects in an
1696
+ // imported image; optionally promote each detection to a typed, image-anchored design
1697
+ // node (pp:Detected*, Wikidata-aliased) so relations can bind a shape to it.
1698
+ export const DetectObjectsInputSchema = z.object({
1699
+ itemId: z.string().optional().describe('Registry ID of the image to scan (default: the most recent image on the canvas).'),
1700
+ threshold: z.number().min(0).max(1).optional().describe('Confidence threshold 0-1 (default ~0.35 for DETR, ~0.1 for open-vocabulary).'),
1701
+ asNodes: z.boolean().optional().describe('true → promote each detection to a typed, image-anchored design NODE (a relation target: pp:Detected* aliased to a Wikidata entity) instead of drawing a labeled box. Enables relational compositing (bind a shape on/around/at the object; it tracks the image).'),
1702
+ queries: z.union([z.array(z.string()), z.string()]).optional().describe('OPEN-VOCABULARY detection (OWL-ViT, zero-shot): a list (or comma-separated string) of things to find by description — e.g. ["red umbrella","traffic light"]. Finds whatever you describe instead of the fixed 80 COCO classes. Omit to use the default DETR detector. The OWL-ViT model is a large one-time on-device download.'),
1703
+ });
1704
+ // Extract the best-matching detected region from an image as a NEW item.
1705
+ export const ExtractObjectInputSchema = z.object({
1706
+ label: z.string().optional().describe('What to extract (e.g. "cat", "person"). Picks the best-matching detection; default = highest-confidence.'),
1707
+ itemId: z.string().optional().describe('Registry ID of the source image (default: most recent image).'),
1708
+ x: z.number().optional().describe('Optional hint X (canvas coords) to disambiguate which detection to extract.'),
1709
+ y: z.number().optional().describe('Optional hint Y (canvas coords).'),
1710
+ threshold: z.number().min(0).max(1).optional().describe('Confidence threshold 0-1.'),
1711
+ });
1712
+ // Z-order / arrange — move an item up or down the stacking order.
1713
+ export const ArrangeInputSchema = z.object({
1714
+ itemId: z.string().describe('Registry ID of the item to reorder.'),
1715
+ action: z.enum(['front', 'back', 'forward', 'backward']).describe('front: bring to top of the stack. back: send to bottom. forward: move up one. backward: move down one.'),
1716
+ });
1391
1717
  /**
1392
1718
  * Batch execute input schema
1393
1719
  */
@@ -1405,6 +1731,7 @@ export const AgentExportInputSchema = z.object({
1405
1731
  quality: z.enum(['draft', 'standard', 'high']).optional().default('standard').describe('Export quality level'),
1406
1732
  includeRecommendations: z.boolean().optional().default(true).describe('Include alternative format recommendations'),
1407
1733
  framing: z.enum(['canvas', 'camera']).optional().default('canvas').describe('Output framing: "canvas" (full canvas, default) or "camera" (camera_animates first-keyframe viewport — fails if no walkthrough exists). Camera animation still drives motion within the fixed output frame.'),
1734
+ duration: z.number().min(0.5).max(60).optional().default(5).describe('Video duration in seconds for animated formats (mp4/webm/gif). Default 5. Max 60. Static formats (png/svg/pdf) ignore this.'),
1408
1735
  }).describe('Smart export options');
1409
1736
  /**
1410
1737
  * Analyze content input schema
@@ -1477,35 +1804,6 @@ export const TriggerActionSchema = z.object({
1477
1804
  points: z.number().optional().describe('Points to add'),
1478
1805
  delay: z.number().optional().default(0).describe('Delay before action in ms'),
1479
1806
  }).describe('Action to execute');
1480
- /**
1481
- * Add trigger input
1482
- */
1483
- export const AddTriggerInputSchema = z.object({
1484
- itemId: z.string().describe('Registry ID of the item'),
1485
- event: TriggerEventSchema,
1486
- time: z.number().optional().describe('Time in seconds for timeline triggers'),
1487
- timelineOffset: z.number().optional().describe('Time offset in ms for timeline triggers'),
1488
- conditions: z.array(TriggerConditionSchema).optional().describe('Conditions that must be met'),
1489
- condition: z.string().optional().describe('Optional condition expression (e.g., "$score > 10")'),
1490
- actions: z.array(TriggerActionSchema).min(1).describe('Actions to execute'),
1491
- once: z.boolean().optional().default(false).describe('Fire only once'),
1492
- }).describe('Add trigger to item');
1493
- /**
1494
- * Remove trigger input
1495
- */
1496
- export const RemoveTriggerInputSchema = z.object({
1497
- itemId: z.string().describe('Registry ID of the item'),
1498
- triggerId: z.string().optional().describe('Specific trigger ID to remove'),
1499
- event: TriggerEventSchema.optional().describe('Remove all triggers of this event type'),
1500
- removeAll: z.boolean().optional().describe('Remove all triggers from item'),
1501
- }).describe('Remove triggers from item');
1502
- /**
1503
- * Query triggers input
1504
- */
1505
- export const QueryTriggersInputSchema = z.object({
1506
- itemId: z.string().optional().describe('Filter by item ID'),
1507
- event: TriggerEventSchema.optional().describe('Filter by event type'),
1508
- }).describe('Query triggers');
1509
1807
  // =============================================================================
1510
1808
  // QUIZ / LMS SCHEMAS
1511
1809
  // =============================================================================
@@ -1563,33 +1861,6 @@ export const QuizQuestionSchema = z.object({
1563
1861
  onCorrectActions: z.array(TriggerActionSchema).optional(),
1564
1862
  onIncorrectActions: z.array(TriggerActionSchema).optional(),
1565
1863
  }).describe('Quiz question');
1566
- /**
1567
- * Create quiz input
1568
- */
1569
- export const CreateQuizInputSchema = z.object({
1570
- id: z.string().optional().describe('Quiz ID'),
1571
- title: z.string().optional().describe('Quiz title'),
1572
- questions: z.array(QuizQuestionSchema).min(1),
1573
- shuffleQuestions: z.boolean().optional().default(false),
1574
- shuffleOptions: z.boolean().optional().default(false),
1575
- showFeedback: z.enum(['immediate', 'after-submit', 'never']).optional().default('immediate'),
1576
- showScore: z.boolean().optional().default(true).describe('Display score during quiz'),
1577
- passingScore: z.number().optional().describe('Minimum score to pass (percentage)'),
1578
- allowRetry: z.boolean().optional().default(true),
1579
- trackProgress: z.boolean().optional().default(true),
1580
- }).describe('Create quiz input');
1581
- /**
1582
- * Get quiz state input
1583
- */
1584
- export const GetQuizStateInputSchema = z.object({
1585
- quizId: z.string().optional().describe('Quiz ID (uses active quiz if not specified)'),
1586
- }).describe('Get quiz state');
1587
- /**
1588
- * Reset quiz input
1589
- */
1590
- export const ResetQuizInputSchema = z.object({
1591
- quizId: z.string().optional().describe('Quiz ID to reset (uses active quiz if not specified)'),
1592
- }).describe('Reset quiz');
1593
1864
  // =============================================================================
1594
1865
  // LETTER COLLAGE SCHEMAS
1595
1866
  // =============================================================================
@@ -1600,6 +1871,7 @@ export const LetterCollageStyleSchema = z.enum([
1600
1871
  'fold',
1601
1872
  'gradient',
1602
1873
  'image',
1874
+ 'particle',
1603
1875
  ]).describe('Style type for letter collage');
1604
1876
  export const TilePaletteSchema = z.enum([
1605
1877
  // Game
@@ -1640,6 +1912,8 @@ export const CreateLetterCollageInputSchema = z.object({
1640
1912
  gradientDirection: GradientDirectionSchema.optional().default('vertical').describe('Gradient direction (for style="gradient")'),
1641
1913
  cornerRadius: z.number().optional().default(4).describe('Corner radius for tile backgrounds'),
1642
1914
  shadowEnabled: z.boolean().optional().default(true).describe('Enable drop shadows'),
1915
+ particleSpacing: z.number().optional().describe('Dot spacing for style="particle" (smaller = denser cloud; default ~fontSize*0.11)'),
1916
+ particleDotSize: z.number().optional().describe('Base dot radius for style="particle" (default ~particleSpacing*0.44)'),
1643
1917
  }).describe('Create letter collage input');
1644
1918
  export const AnimateLetterCollageInputSchema = z.object({
1645
1919
  collageId: z.string().describe('Collage ID from create_letter_collage'),
@@ -1647,11 +1921,6 @@ export const AnimateLetterCollageInputSchema = z.object({
1647
1921
  staggerDelay: z.number().optional().default(0.1).describe('Delay between each letter animation start (seconds)'),
1648
1922
  animationSpeed: z.number().optional().default(1).describe('Animation speed multiplier'),
1649
1923
  }).describe('Animate letter collage input');
1650
- export const GetLetterCollageOptionsInputSchema = z.object({}).describe('Get letter collage options input (no parameters)');
1651
- // =============================================================================
1652
- // CANVAS PRESETS SCHEMA
1653
- // =============================================================================
1654
- export const GetCanvasPresetsInputSchema = z.object({}).describe('Get canvas presets input (no parameters)');
1655
1924
  // =============================================================================
1656
1925
  // MAP SCHEMAS
1657
1926
  // =============================================================================
@@ -1800,6 +2069,54 @@ export const DeselectMapRegionsInputSchema = z.object({
1800
2069
  }).describe('Deselect map regions input');
1801
2070
  export const GetHighlightedMapRegionsInputSchema = z.object({}).describe('Get highlighted map regions input (no parameters)');
1802
2071
  // =============================================================================
2072
+ // GLOBE + WORLD TOUR SCHEMAS
2073
+ // =============================================================================
2074
+ export const GlobeEnableInputSchema = z.object({
2075
+ rotation: z.tuple([z.number(), z.number(), z.number()]).optional().describe('Initial rotation angles [lambda, phi, gamma]'),
2076
+ momentum: z.boolean().optional().describe('Enable momentum/inertia on drag-spin'),
2077
+ showOcean: z.boolean().optional().describe('Render the ocean sphere backdrop'),
2078
+ }).describe('Enable globe (orthographic) mode input');
2079
+ export const GlobeRotateToInputSchema = z.object({
2080
+ lon: z.number().describe('Target longitude to rotate to face the viewer'),
2081
+ lat: z.number().describe('Target latitude to rotate to face the viewer'),
2082
+ duration: z.number().optional().describe('Animation duration in seconds (versor slerp)'),
2083
+ }).describe('Rotate globe to coordinate input');
2084
+ export const GlobeSpinInputSchema = z.object({
2085
+ speed: z.number().optional().describe('Rotation speed (degrees per second)'),
2086
+ axis: z.enum(['longitude', 'latitude']).optional().describe('Axis to spin around'),
2087
+ duration: z.number().optional().describe('Spin duration in seconds (omit for continuous)'),
2088
+ }).describe('Continuous globe spin input');
2089
+ export const WorldTourInputSchema = z.object({
2090
+ regions: z.array(z.string()).optional().describe('Region IDs to visit in order'),
2091
+ coords: z.array(z.tuple([z.number(), z.number()])).optional().describe('Coordinate stops [lon, lat][] to visit in order'),
2092
+ dwell: z.number().optional().describe('Seconds to dwell at each stop'),
2093
+ travel: z.number().optional().describe('Seconds to travel between stops'),
2094
+ tilt: z.number().optional().describe('Camera tilt applied at each stop'),
2095
+ loop: z.boolean().optional().describe('Loop the tour continuously'),
2096
+ easing: z.string().optional().describe('Easing curve for the swing between stops'),
2097
+ highlightColors: z.array(z.string()).optional().describe('Highlight colors cycled per stop'),
2098
+ showLabels: z.boolean().optional().describe('Show a label at each stop'),
2099
+ labelColor: z.string().optional().describe('Label text color'),
2100
+ }).describe('World tour input (requires regions or coords)');
2101
+ export const StopWorldTourInputSchema = z.object({
2102
+ id: z.string().optional().describe('Tour id to stop (omit to stop all tours)'),
2103
+ }).describe('Stop world tour input');
2104
+ export const PinToGlobeInputSchema = z.object({
2105
+ itemId: z.string().describe('ID of the canvas item to pin to the globe surface'),
2106
+ lon: z.number().describe('Longitude to pin the item at'),
2107
+ lat: z.number().describe('Latitude to pin the item at'),
2108
+ hideOnFarSide: z.boolean().optional().describe('Hide the item when its coordinate rotates to the far side'),
2109
+ }).describe('Pin item to globe input');
2110
+ export const TourItemInputSchema = z.object({
2111
+ itemId: z.string().describe('ID of the canvas item to move along the tour'),
2112
+ regions: z.array(z.string()).optional().describe('Region IDs to tour the item across'),
2113
+ coords: z.array(z.tuple([z.number(), z.number()])).optional().describe('Coordinate stops [lon, lat][] to move the item along'),
2114
+ dwell: z.number().optional().describe('Seconds to dwell at each stop'),
2115
+ travel: z.number().optional().describe('Seconds to travel between stops'),
2116
+ loop: z.boolean().optional().describe('Loop the item tour continuously'),
2117
+ orient: z.boolean().optional().describe('Orient the item along its direction of travel'),
2118
+ }).describe('Tour item along coords/regions input (requires regions or coords)');
2119
+ // =============================================================================
1803
2120
  // TEMPLATE SCHEMAS
1804
2121
  // =============================================================================
1805
2122
  export const TemplateCategorySchema = z.enum([
@@ -1832,6 +2149,23 @@ export const AnalyzeDesignInputSchema = z.object({
1832
2149
  export const ValidateDesignInputSchema = z.object({
1833
2150
  definition: z.object({}).passthrough().describe('A template or scene definition object to validate and score'),
1834
2151
  }).describe('Validate design input');
2152
+ // Semantic scene/op validator (OntologyValidator in FxTool) — distinct from
2153
+ // ValidateDesign (which scores a template definition server-side). Runs in the
2154
+ // browser against the live scene and returns structured diagnostics.
2155
+ export const ValidateInputSchema = z.object({
2156
+ mode: z.enum(['scene', 'op']).default('scene')
2157
+ .describe("'scene' audits the whole live canvas; 'op' validates a proposed mutation before applying it"),
2158
+ op: z.object({
2159
+ kind: z.enum(['addRelation', 'create', 'modify']).describe('The kind of mutation being validated'),
2160
+ from: z.string().optional().describe('Source item id (addRelation)'),
2161
+ to: z.string().optional().describe('Target item id (addRelation)'),
2162
+ relation: z.string().optional().describe('Relation type (addRelation)'),
2163
+ params: z.record(z.unknown()).optional().describe('Relation params (addRelation)'),
2164
+ type: z.string().optional().describe('Item type (create)'),
2165
+ id: z.string().optional().describe('Item id (modify)'),
2166
+ changes: z.record(z.unknown()).optional().describe('Property changes (modify)'),
2167
+ }).optional().describe("Required when mode='op': the proposed mutation to validate"),
2168
+ }).describe('Semantic validation input');
1835
2169
  export const QueryOntologyInputSchema = z.object({
1836
2170
  query: z.enum([
1837
2171
  'list_types', 'list_edges', 'list_generators', 'list_effects',
@@ -1924,11 +2258,6 @@ export const PrecompInputSchema = z.object({
1924
2258
  precompId: z.string().optional(),
1925
2259
  itemId: z.string().optional(),
1926
2260
  });
1927
- export const ViewInputSchema = z.object({
1928
- action: z.enum(['fit', 'get_state']),
1929
- mode: z.enum(['content', 'canvas']).optional(),
1930
- padding: z.number().optional(),
1931
- });
1932
2261
  export const BackgroundInputSchema = z.object({
1933
2262
  action: z.enum(['set', 'clear', 'get']),
1934
2263
  mode: z.enum(['color', 'pattern', 'generator']).optional(),
@@ -2003,15 +2332,6 @@ export const SpriteSheetInputSchema = z.object({
2003
2332
  format: z.enum(['png', 'webp']).optional(),
2004
2333
  });
2005
2334
  // =============================================================================
2006
- // STORAGE TOOLS
2007
- // =============================================================================
2008
- export const StorageInputSchema = z.object({
2009
- action: z.enum(['save', 'load', 'list', 'delete']),
2010
- projectId: z.string().optional(),
2011
- name: z.string().optional(),
2012
- thumbnail: z.boolean().optional(),
2013
- });
2014
- // =============================================================================
2015
2335
  // INTERACTION TOOLS
2016
2336
  // =============================================================================
2017
2337
  export const InteractionInputSchema = z.object({
@@ -2090,4 +2410,200 @@ export const MeasurementInputSchema = z.object({
2090
2410
  enabled: z.boolean().optional(),
2091
2411
  itemId: z.string().optional(),
2092
2412
  });
2413
+ // Geometry construction helpers (Layer 1) — pure functions exposed by FxTool's
2414
+ // app.geometry (GeometryConstruction). The server computes a construction and
2415
+ // optionally creates an item from the result, mirroring:
2416
+ // app.create('polygon', { points: app.geometry.regularPolygon(cx, cy, r, 5) });
2417
+ export const GEOMETRY_OPERATIONS = [
2418
+ // points
2419
+ 'point', 'distance', 'midpoint', 'lerpPoint', 'centroid',
2420
+ 'translatePoint', 'rotatePoint', 'scalePoint',
2421
+ // polygons
2422
+ 'regularPolygon', 'star', 'polygonFromVertices',
2423
+ // lines
2424
+ 'lineThrough', 'lineDirection', 'lineIntersection', 'projectPointToLine',
2425
+ 'perpendicular', 'parallel', 'perpendicularBisector', 'reflectPoint',
2426
+ // angles
2427
+ 'angle', 'angleBisector',
2428
+ // circles
2429
+ 'circle', 'pointOnCircle', 'circumcenter', 'circleThrough', 'incenter',
2430
+ 'tangentPointsFromExternal',
2431
+ ];
2432
+ export const GeometryInputSchema = z.object({
2433
+ operation: z.enum(GEOMETRY_OPERATIONS).describe('Which app.geometry.* construction helper to run'),
2434
+ args: z.array(z.any()).default([]).describe('Positional arguments for the operation, in order. Points are {x,y} objects (or [x,y] arrays where a vertex list is accepted); lines are {p1,p2}; circles are {center,radius}. Angles are in radians. e.g. regularPolygon → [cx, cy, radius, sides, rotation?]'),
2435
+ createAs: z
2436
+ .object({
2437
+ itemType: z.string().optional().describe('Override item type for point results (default "circle").'),
2438
+ color: z.string().optional(),
2439
+ fillColor: z.string().optional(),
2440
+ strokeColor: z.string().optional(),
2441
+ strokeWidth: z.number().optional(),
2442
+ radius: z.number().optional().describe('Marker radius for point results (default 6).'),
2443
+ })
2444
+ .passthrough()
2445
+ .optional()
2446
+ .describe('When provided, also create a canvas item from the result: a vertex-list result → polygon, a circle result → circle, a point result → a small marker. Extra keys pass through to app.create. Omit for compute-only.'),
2447
+ });
2448
+ // Construction sequences (Layer 3) — step-by-step reveal of a geometric figure,
2449
+ // wrapping FxTool's app.constructionSequence. Each step hides its items and attaches
2450
+ // a timeline-driven construction_reveal relation, so playback reveals the figure one
2451
+ // step at a time (replayable, scrubbable, editable as relation graph data).
2452
+ export const ConstructionSequenceInputSchema = z.object({
2453
+ action: z.enum(['build', 'play', 'clear', 'list']).describe('build a sequence, play it on the timeline, clear (restore items), or list sequences'),
2454
+ steps: z
2455
+ .array(z.union([
2456
+ z.array(z.string()),
2457
+ z.object({ items: z.array(z.string()), label: z.string().optional() }),
2458
+ ]))
2459
+ .optional()
2460
+ .describe('For build: ordered steps; each step is an array of item ids (or {items, label}) revealed together. Step i reveals at i * stepDuration seconds.'),
2461
+ stepDuration: z.number().positive().optional().describe('For build: seconds between steps (default 1).'),
2462
+ fadeIn: z.number().min(0).optional().describe('For build: per-step fade-in duration in seconds (default 0.3).'),
2463
+ sequenceId: z.string().optional().describe('For play/clear: the cseq_ id returned by build. Defaults to the most recently built sequence.'),
2464
+ loop: z.boolean().optional().describe('For play: loop the timeline (default false).'),
2465
+ duration: z.number().positive().optional().describe('For play: override total play duration in seconds (default = sequence total).'),
2466
+ });
2467
+ // Live-scene semantic validation (OntologyValidator / S2) — structured diagnostics
2468
+ // over the actual canvas (dangling refs, unknown types/props, keyframe issues, cycles).
2469
+ export const ValidateSceneInputSchema = z.object({
2470
+ ops: z
2471
+ .array(z.record(z.string(), z.any()))
2472
+ .optional()
2473
+ .describe('Optional: validate a batch of PROPOSED ops ({ kind: "addRelation"|"create"|"modify"|"delete", ... }) against the live scene before applying them, instead of auditing the current scene. Ids created earlier in the batch count as existing for later ops.'),
2474
+ });
2475
+ // Deterministic headless frame capture (S3) — evaluate the scene at each time via
2476
+ // sceneAt(t) with Math.random seeded once around the whole sequence, then snapshot.
2477
+ export const CaptureFramesInputSchema = z.object({
2478
+ times: z.array(z.number()).min(1).describe('Capture times in seconds, rendered in order.'),
2479
+ seed: z.number().optional().describe('Seed Math.random once around the whole sequence so random generators/particles are reproducible (default 0).'),
2480
+ includeDataUrls: z.boolean().optional().describe('Include each frame as a PNG data URL (large — token-heavy). Default false → returns a cheap per-frame hash + byte size only.'),
2481
+ });
2482
+ // Ontology→scene compiler (S12-E3). Compiles a pp:-namespaced JSON-LD design graph
2483
+ // (typed nodes + structural edges like pp:onTopOf) into a real scene. Compilation
2484
+ // runs server-side (deterministic, testable); only the resulting create/addRelation
2485
+ // ops are emitted. Only ROOT nodes get coordinates — the rest are placed at runtime
2486
+ // by their structural relations.
2487
+ export const InstantiateOntologyInputSchema = z.object({
2488
+ doc: z.record(z.string(), z.unknown()).describe('The design graph: pp:nodes / pp:edges (DesignGraph.toJsonLd shape), or { nodes, edges }, or nodes carrying inline pp: relation properties.'),
2489
+ canvas: z.object({
2490
+ width: z.number().positive(),
2491
+ height: z.number().positive(),
2492
+ }).optional().describe('Canvas size used to tile component roots (default 1080×1080).'),
2493
+ defaultGeometry: z.record(z.string(), z.object({
2494
+ width: z.number(),
2495
+ height: z.number(),
2496
+ })).optional().describe('Per-type default { width, height } overrides (e.g. { text: { width: 300, height: 60 } }).'),
2497
+ });
2498
+ // Relational-density lint of the LIVE scene (S12-E2). Reports how much of the scene
2499
+ // participates in the relation graph and suggests structural relations for unrelated
2500
+ // items already sitting in a structural configuration. Advisory (nothing applied).
2501
+ export const LintSceneInputSchema = z.object({
2502
+ eps: z.number().positive().optional().describe('Bounds-match tolerance in px for structural-configuration detection (default 4).'),
2503
+ cap: z.number().int().positive().optional().describe('Max number of suggestions to return, ranked by confidence (default 20).'),
2504
+ });
2505
+ // Media (video/audio) via window.PinePaperAgent. Agent-facing surface is URL-based
2506
+ // (the agent can't hand over a File). Uploaded media are first-class canvas items.
2507
+ export const MediaInputSchema = z.object({
2508
+ action: z.enum(['upload_video', 'upload_audio', 'list', 'remove', 'set_playback_rate', 'set_clip'])
2509
+ .describe("'upload_video' / 'upload_audio' (from a URL) · 'list' media · 'remove' by id · 'set_playback_rate' · 'set_clip' (re-trim an existing clip)"),
2510
+ url: z.string().url().optional().describe('Media URL — required for upload_video / upload_audio (fetched then imported).'),
2511
+ id: z.string().optional().describe('Media id — required for remove / set_playback_rate / set_clip.'),
2512
+ rate: z.number().min(0.25).max(4).optional().describe('Playback rate 0.25–4 — required for set_playback_rate.'),
2513
+ inPoint: z.number().min(0).optional().describe('Clip in-point in media-time seconds — required for set_clip.'),
2514
+ outPoint: z.number().min(0).optional().describe('Clip out-point in media-time seconds — required for set_clip.'),
2515
+ // upload_video placement
2516
+ position: PositionSchema.optional().describe('Canvas position — upload_video.'),
2517
+ scale: z.number().positive().optional().describe('Scale factor — upload_video.'),
2518
+ timeOffset: z.number().optional().describe('Start offset on the timeline in seconds — upload_video / upload_audio.'),
2519
+ clipInPoint: z.number().optional().describe('Trim in-point in seconds — upload_video.'),
2520
+ clipOutPoint: z.number().optional().describe('Trim out-point in seconds — upload_video.'),
2521
+ // upload_audio
2522
+ volume: z.number().min(0).max(1).optional().describe('Volume 0–1 (default 1) — upload_audio.'),
2523
+ loop: z.boolean().optional().describe('Loop playback (default true) — upload_audio.'),
2524
+ muted: z.boolean().optional().describe('Start muted (default false) — upload_audio.'),
2525
+ })
2526
+ .refine((v) => !(v.action === 'upload_video' || v.action === 'upload_audio') || !!v.url, { message: 'upload requires url', path: ['url'] })
2527
+ .refine((v) => !(v.action === 'remove' || v.action === 'set_playback_rate' || v.action === 'set_clip') || !!v.id, { message: 'this action requires id', path: ['id'] })
2528
+ .refine((v) => v.action !== 'set_playback_rate' || v.rate !== undefined, { message: 'set_playback_rate requires rate', path: ['rate'] })
2529
+ .refine((v) => v.action !== 'set_clip' || (v.inPoint !== undefined && v.outPoint !== undefined && v.outPoint > v.inPoint), { message: 'set_clip requires inPoint and outPoint with outPoint > inPoint', path: ['outPoint'] });
2530
+ // =============================================================================
2531
+ // ONE-SHOT IMAGE OPS (crop / chroma key — Track A agent parity, 2026-07-31)
2532
+ // =============================================================================
2533
+ export const CropImageInputSchema = z.object({
2534
+ itemId: z.string().describe('Registry id of the raster item (or a group containing one).'),
2535
+ rect: z.object({
2536
+ x: z.number().describe('Left edge, canvas coordinates'),
2537
+ y: z.number().describe('Top edge, canvas coordinates'),
2538
+ width: z.number().positive().describe('Crop width in canvas units'),
2539
+ height: z.number().positive().describe('Crop height in canvas units'),
2540
+ }).describe('Crop rectangle in project (canvas) coordinates — clamped into the image bounds.'),
2541
+ aspectRatio: z.enum(['free', '1:1', '16:9', '9:16', '4:3', '3:4', '3:2', '2:3']).optional()
2542
+ .describe('Optional aspect-ratio constraint applied to the rect.'),
2543
+ });
2544
+ export const ChromaKeyInputSchema = z.object({
2545
+ itemId: z.string().describe('Registry id of the raster item (or a group containing one).'),
2546
+ color: z.string().optional()
2547
+ .describe("Key color: '#rrggbb' or preset name (green, chromaGreen, blue, chromaBlue, magenta). Default: pure green."),
2548
+ threshold: z.number().min(0).max(255).optional()
2549
+ .describe('Color-distance threshold for full transparency. Omit to auto-estimate from the image.'),
2550
+ smoothing: z.number().min(0).max(100).optional()
2551
+ .describe('Edge-smoothing range past the threshold. Omit to auto-estimate.'),
2552
+ });
2553
+ // Rigging (skeletons, bones, IK, breakdown-pose keyframes) via app.riggingSystem.
2554
+ // One consolidated tool: build a skeleton, then pose/animate it. The S12 breakdown-pose
2555
+ // options (favor/breakdown/curve/boneOffsets/movingHold/holdDrift) ride add_pose_keyframe.
2556
+ export const RiggingInputSchema = z.object({
2557
+ action: z.enum([
2558
+ 'create_skeleton', 'add_bone', 'attach_item', 'create_ik_chain',
2559
+ 'add_pose_keyframe', 'set_target_path', 'save_pose', 'save_shape_key',
2560
+ ]).describe('Rigging operation'),
2561
+ // identifiers
2562
+ skeletonId: z.string().optional().describe('Skeleton id — required for every action except create_skeleton.'),
2563
+ boneId: z.string().optional().describe('Bone id — attach_item.'),
2564
+ itemId: z.string().optional().describe('Canvas item id — attach_item.'),
2565
+ chainId: z.string().optional().describe('IK chain id — set_target_path.'),
2566
+ name: z.string().optional().describe('Name — create_skeleton / add_bone / create_ik_chain / save_pose / save_shape_key.'),
2567
+ // create_skeleton
2568
+ rootPosition: PositionSchema.optional().describe('Skeleton root position (default {x:400,y:300}) — create_skeleton.'),
2569
+ // add_bone
2570
+ parentBoneId: z.string().optional().describe('Parent bone id (omit for root) — add_bone.'),
2571
+ length: z.number().positive().optional().describe('Bone length px (default 80) — add_bone.'),
2572
+ angle: z.number().optional().describe('Bone rest angle in degrees — add_bone.'),
2573
+ flexibility: z.number().min(0).max(1).optional().describe('0 rigid … 1 fully flexible — add_bone.'),
2574
+ segments: z.number().int().min(0).optional().describe('Curve segments (0 = auto) — add_bone.'),
2575
+ // attach_item
2576
+ attachPoint: z.number().min(0).max(1).optional().describe('Position along bone 0=joint…1=tip (default 0.5) — attach_item.'),
2577
+ // create_ik_chain
2578
+ boneIds: z.array(z.string()).min(2).optional().describe('Ordered bone ids (≥2) — create_ik_chain.'),
2579
+ solverType: z.enum(['fabrik', 'two_bone', 'ccd']).optional().describe('IK solver (default fabrik) — create_ik_chain.'),
2580
+ iterations: z.number().int().positive().optional().describe('Solver iterations (default 10) — create_ik_chain.'),
2581
+ tolerance: z.number().positive().optional().describe('Solver tolerance px (default 0.5) — create_ik_chain.'),
2582
+ strength: z.number().optional().describe('Chain strength 0..1 (default 1) — create_ik_chain.'),
2583
+ poleVector: PositionSchema.optional().describe('Pole vector hint — create_ik_chain.'),
2584
+ // add_pose_keyframe
2585
+ time: z.number().optional().describe('Keyframe time in seconds — add_pose_keyframe.'),
2586
+ pose: z.union([z.string(), z.record(z.string(), z.number())]).optional().describe('A saved pose id, or a { boneId: angleDeg } map — add_pose_keyframe.'),
2587
+ easing: z.string().optional().describe('Named easing (default linear) — add_pose_keyframe.'),
2588
+ favor: z.number().min(-1).max(1).optional().describe('Breakdown spacing bias −1..1 (− favors prev key, + favors next) — add_pose_keyframe.'),
2589
+ breakdown: z.boolean().optional().describe('Mark as a breakdown pose (vs a storytelling key) — add_pose_keyframe.'),
2590
+ curve: z.array(z.number()).optional().describe('Per-segment cubic-bezier ease [x1,y1,x2,y2] (overrides named easing) — add_pose_keyframe.'),
2591
+ boneOffsets: z.record(z.string(), z.number()).optional().describe('Per-bone lag { boneId: 0..0.95 } for overlap/follow-through — add_pose_keyframe.'),
2592
+ movingHold: z.boolean().optional().describe('Hold drifts toward the next key instead of dead-stopping — add_pose_keyframe.'),
2593
+ holdDrift: z.number().optional().describe('Fraction toward the next key the moving hold drifts (default 0.06) — add_pose_keyframe.'),
2594
+ // set_target_path
2595
+ waypoints: z.array(z.object({
2596
+ x: z.number(), y: z.number(),
2597
+ out: PositionSchema.optional(),
2598
+ in: PositionSchema.optional(),
2599
+ })).optional().describe('IK effector path waypoints with optional bezier tangents — set_target_path.'),
2600
+ duration: z.number().positive().optional().describe('Seconds to traverse the path (default 1) — set_target_path.'),
2601
+ loop: z.boolean().optional().describe('Cycle the path — set_target_path.'),
2602
+ })
2603
+ .refine((v) => v.action === 'create_skeleton' || !!v.skeletonId, { message: 'this action requires skeletonId', path: ['skeletonId'] })
2604
+ .refine((v) => v.action !== 'add_bone' || v.skeletonId != null, { message: 'add_bone requires skeletonId', path: ['skeletonId'] })
2605
+ .refine((v) => v.action !== 'attach_item' || (!!v.boneId && !!v.itemId), { message: 'attach_item requires boneId and itemId', path: ['boneId'] })
2606
+ .refine((v) => v.action !== 'create_ik_chain' || (!!v.boneIds && v.boneIds.length >= 2), { message: 'create_ik_chain requires boneIds (≥2)', path: ['boneIds'] })
2607
+ .refine((v) => v.action !== 'add_pose_keyframe' || (v.time !== undefined && v.pose !== undefined), { message: 'add_pose_keyframe requires time and pose', path: ['pose'] })
2608
+ .refine((v) => v.action !== 'set_target_path' || (!!v.chainId && !!v.waypoints), { message: 'set_target_path requires chainId and waypoints', path: ['waypoints'] });
2093
2609
  //# sourceMappingURL=schemas.js.map