@pixerate/editor 0.227.1 → 0.228.1

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 (35) hide show
  1. package/dist/agent/index.cjs +6 -6
  2. package/dist/agent/index.js +4 -4
  3. package/dist/bridge/cli.cjs +3 -3
  4. package/dist/bridge/cli.js +2 -2
  5. package/dist/bridge/index.cjs +3 -3
  6. package/dist/bridge/index.js +2 -2
  7. package/dist/canvas/drawController.d.cts +6 -0
  8. package/dist/canvas/drawController.d.ts +6 -0
  9. package/dist/canvas/gestures.d.cts +87 -0
  10. package/dist/canvas/gestures.d.ts +87 -0
  11. package/dist/canvas/index.cjs +30 -2
  12. package/dist/canvas/index.d.cts +1 -0
  13. package/dist/canvas/index.d.ts +1 -0
  14. package/dist/canvas/index.js +30 -2
  15. package/dist/{chunk-VXLYEYP4.cjs → chunk-4I76OCVD.cjs} +2 -2
  16. package/dist/{chunk-SAN4SBBS.js → chunk-ABHOYBZD.js} +39 -14
  17. package/dist/{chunk-53GOGBTM.cjs → chunk-CHAWTXBE.cjs} +243 -39
  18. package/dist/{chunk-OJTZKDLJ.cjs → chunk-KBNCEZZB.cjs} +7 -7
  19. package/dist/{chunk-7KZ2HKKC.js → chunk-LPFQHVCO.js} +244 -1
  20. package/dist/{chunk-H73MSHYZ.js → chunk-QTMQ5TGA.js} +1 -1
  21. package/dist/{chunk-Z7BVK32C.cjs → chunk-RTPBM46F.cjs} +39 -14
  22. package/dist/{chunk-67JAEU53.js → chunk-SPPDMY7T.js} +1 -1
  23. package/dist/{chunk-4EFNS2HV.cjs → chunk-T7QSEKDY.cjs} +248 -5
  24. package/dist/{chunk-ZPJEH456.js → chunk-XVIK73LS.js} +206 -2
  25. package/dist/commands/agentTools.d.cts +14 -0
  26. package/dist/commands/agentTools.d.ts +14 -0
  27. package/dist/commands/builtins.d.cts +11 -1
  28. package/dist/commands/builtins.d.ts +11 -1
  29. package/dist/commands/index.cjs +23 -9
  30. package/dist/commands/index.js +17 -3
  31. package/dist/tools/index.cjs +3 -3
  32. package/dist/tools/index.js +2 -2
  33. package/package.json +1 -1
  34. package/skills/pixerate-agent-tools/SKILL.md +5 -0
  35. package/skills/pixerate-canvas/SKILL.md +41 -2
@@ -2,8 +2,8 @@
2
2
  var PLATFORMS = ["mac", "windows", "linux"];
3
3
  function detectPlatform() {
4
4
  const nav = typeof navigator !== "undefined" ? navigator : void 0;
5
- const text = `${_nullishCoalesce(_optionalChain([nav, 'optionalAccess', _ => _.platform]), () => ( ""))} ${_nullishCoalesce(_optionalChain([nav, 'optionalAccess', _2 => _2.userAgent]), () => ( ""))}`;
6
- if (/Mac|iPod|iPhone|iPad/.test(_optionalChain([nav, 'optionalAccess', _3 => _3.platform]) || _optionalChain([nav, 'optionalAccess', _4 => _4.userAgent]) || "")) return "mac";
5
+ const text = `${_nullishCoalesce(_optionalChain([nav, 'optionalAccess', _2 => _2.platform]), () => ( ""))} ${_nullishCoalesce(_optionalChain([nav, 'optionalAccess', _3 => _3.userAgent]), () => ( ""))}`;
6
+ if (/Mac|iPod|iPhone|iPad/.test(_optionalChain([nav, 'optionalAccess', _4 => _4.platform]) || _optionalChain([nav, 'optionalAccess', _5 => _5.userAgent]) || "")) return "mac";
7
7
  if (/Win/.test(text)) return "windows";
8
8
  return "linux";
9
9
  }
@@ -222,7 +222,7 @@ function isEditableEventTarget(event) {
222
222
  if (!target) target = typeof document !== "undefined" ? document.activeElement : null;
223
223
  if (!target || typeof target.tagName !== "string") return false;
224
224
  const tag = target.tagName.toUpperCase();
225
- return !!(tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || target.isContentEditable || _optionalChain([target, 'access', _5 => _5.getAttribute, 'optionalCall', _6 => _6("contenteditable")]) === "true" || _optionalChain([target, 'access', _7 => _7.closest, 'optionalCall', _8 => _8('[contenteditable="true"]')]) || _optionalChain([target, 'access', _9 => _9.closest, 'optionalCall', _10 => _10(".ProseMirror")]));
225
+ return !!(tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || target.isContentEditable || _optionalChain([target, 'access', _6 => _6.getAttribute, 'optionalCall', _7 => _7("contenteditable")]) === "true" || _optionalChain([target, 'access', _8 => _8.closest, 'optionalCall', _9 => _9('[contenteditable="true"]')]) || _optionalChain([target, 'access', _10 => _10.closest, 'optionalCall', _11 => _11(".ProseMirror")]));
226
226
  }
227
227
 
228
228
  // src/commands/shortcuts.ts
@@ -427,9 +427,252 @@ function arrangeShortcutMap(bindings) {
427
427
  return map;
428
428
  }
429
429
  function matchArrangeShortcut(event, bindings = DEFAULT_ARRANGE_SHORTCUTS) {
430
- return _nullishCoalesce(_optionalChain([arrangeShortcutMap, 'call', _11 => _11(bindings), 'access', _12 => _12.resolve, 'call', _13 => _13(event), 'optionalAccess', _14 => _14.command]), () => ( null));
430
+ return _nullishCoalesce(_optionalChain([arrangeShortcutMap, 'call', _12 => _12(bindings), 'access', _13 => _13.resolve, 'call', _14 => _14(event), 'optionalAccess', _15 => _15.command]), () => ( null));
431
431
  }
432
432
 
433
+ // src/canvas/gestures.ts
434
+ function gestureRandom(seed) {
435
+ let a = seed >>> 0;
436
+ return () => {
437
+ a = a + 1831565813 >>> 0;
438
+ let t = a;
439
+ t = Math.imul(t ^ t >>> 15, t | 1);
440
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
441
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
442
+ };
443
+ }
444
+ function gestureSeed(value) {
445
+ const text = _nullishCoalesce(JSON.stringify(value), () => ( ""));
446
+ let h = 2166136261;
447
+ for (let i = 0; i < text.length; i++) {
448
+ h ^= text.charCodeAt(i);
449
+ h = Math.imul(h, 16777619);
450
+ }
451
+ return h >>> 0;
452
+ }
453
+ function gestureUnion(rects) {
454
+ if (!rects.length) throw new Error("Pass at least one rect.");
455
+ const x = Math.min(...rects.map((r) => r.x));
456
+ const y = Math.min(...rects.map((r) => r.y));
457
+ const right = Math.max(...rects.map((r) => r.x + r.width));
458
+ const bottom = Math.max(...rects.map((r) => r.y + r.height));
459
+ return { x, y, width: right - x, height: bottom - y };
460
+ }
461
+ var clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
462
+ var centre = (r) => [r.x + r.width / 2, r.y + r.height / 2];
463
+ function smoothNoise(rand, octaves = 3) {
464
+ const waves = Array.from({ length: octaves }, (_, i) => ({ f: (i + 1) * (0.6 + rand() * 0.8), phase: rand() * Math.PI * 2, amp: 1 / (i + 1) }));
465
+ const total = waves.reduce((sum, w) => sum + w.amp, 0);
466
+ return (t) => waves.reduce((sum, w) => sum + w.amp * Math.sin(2 * Math.PI * w.f * t + w.phase), 0) / total;
467
+ }
468
+ function handPressure(rand) {
469
+ const r = (lo, hi) => Math.round((lo + rand() * (hi - lo)) * 100) / 100;
470
+ return [r(0.35, 0.55), r(0.8, 0.95), r(0.9, 1), r(0.75, 0.95), r(0.4, 0.65)];
471
+ }
472
+ var round = (p) => [Math.round(p[0] * 100) / 100, Math.round(p[1] * 100) / 100];
473
+ function wobblyLine(a, b, rand, amp, bow = 0) {
474
+ const dx = b[0] - a[0];
475
+ const dy = b[1] - a[1];
476
+ const len = Math.hypot(dx, dy);
477
+ if (len === 0) return [a];
478
+ const nx = -dy / len;
479
+ const ny = dx / len;
480
+ const noise = smoothNoise(rand);
481
+ const n = clamp(Math.ceil(len / 4), 8, 200);
482
+ return Array.from({ length: n + 1 }, (_, i) => {
483
+ const t = i / n;
484
+ const off = bow * 4 * t * (1 - t) + amp * noise(t) * Math.sqrt(Math.sin(Math.PI * t));
485
+ return [a[0] + dx * t + nx * off, a[1] + dy * t + ny * off];
486
+ });
487
+ }
488
+ function circleGesture(rect, seed, options = {}) {
489
+ const rand = gestureRandom(seed);
490
+ const pad = Math.max(0, _nullishCoalesce(options.padding, () => ( 8)));
491
+ const [cx, cy] = centre(rect);
492
+ const tilt = (rand() - 0.5) * 0.08;
493
+ const c = Math.abs(Math.cos(tilt));
494
+ const s = Math.abs(Math.sin(tilt));
495
+ const a = (rect.width / 2 * c + rect.height / 2 * s) * Math.SQRT2 + pad;
496
+ const b = (rect.width / 2 * s + rect.height / 2 * c) * Math.SQRT2 + pad;
497
+ const dir = rand() < 0.7 ? -1 : 1;
498
+ const start = -Math.PI * 0.75 + (rand() - 0.5) * 0.6;
499
+ const sweep = Math.PI * 2 + Math.PI * (0.1 + rand() * 0.12);
500
+ const jitter = 0.03 + rand() * 0.03;
501
+ const drift = 0.04 + rand() * 0.05;
502
+ const noise = smoothNoise(rand, 4);
503
+ const perimeter = Math.PI * (3 * (a + b) - Math.sqrt((3 * a + b) * (a + 3 * b)));
504
+ const n = clamp(Math.ceil(perimeter * (sweep / (Math.PI * 2)) / 5), 48, 360);
505
+ const points = Array.from({ length: n + 1 }, (_, i) => {
506
+ const t = i / n;
507
+ const angle = start + dir * sweep * t;
508
+ const grow = 1 + jitter * (0.5 + 0.5 * noise(t)) + drift * t * t;
509
+ const ex = a * grow * Math.cos(angle);
510
+ const ey = b * grow * Math.sin(angle);
511
+ return round([cx + ex * Math.cos(tilt) - ey * Math.sin(tilt), cy + ex * Math.sin(tilt) + ey * Math.cos(tilt)]);
512
+ });
513
+ return { points, pressure: handPressure(rand) };
514
+ }
515
+ function underlineGesture(rect, seed, options = {}) {
516
+ const rand = gestureRandom(seed);
517
+ const offset = Math.max(0, _nullishCoalesce(options.offset, () => ( 4)));
518
+ const amp = options.wave ? clamp(rect.width * 0.02, 2, 4) : 0;
519
+ const shake = Math.min(1.5, offset * 0.4);
520
+ const base = rect.y + rect.height + offset + amp + shake;
521
+ const slope = Math.min(6, rect.width * 0.03);
522
+ const x0 = rect.x - rect.width * (0.01 + rand() * 0.04);
523
+ const x1 = rect.x + rect.width * (0.96 + rand() * 0.08);
524
+ const line = wobblyLine([x0, base + rand() * slope], [x1, base + rand() * slope], rand, shake, (rand() - 0.5) * 2 * shake);
525
+ const wavelength = clamp(rect.width / 8, 12, 28);
526
+ const phase = rand() * Math.PI * 2;
527
+ const points = amp ? line.map(([x, y]) => [x, y + amp * Math.sin(2 * Math.PI * (x - x0) / wavelength + phase)]) : line;
528
+ return { points: points.map(round), pressure: handPressure(rand) };
529
+ }
530
+ function gestureEdgePoint(rect, toward, gap = 0) {
531
+ const [cx, cy] = centre(rect);
532
+ const dx = toward[0] - cx;
533
+ const dy = toward[1] - cy;
534
+ const len = Math.hypot(dx, dy);
535
+ if (len === 0) return [cx, cy];
536
+ const t = Math.min(dx ? rect.width / 2 / Math.abs(dx) : Infinity, dy ? rect.height / 2 / Math.abs(dy) : Infinity);
537
+ return [cx + dx * t + dx / len * gap, cy + dy * t + dy / len * gap];
538
+ }
539
+ var isRect = (end) => !Array.isArray(end);
540
+ function arrowGesture(from, to, seed, options = {}) {
541
+ const rand = gestureRandom(seed);
542
+ const gap = Math.max(0, _nullishCoalesce(options.gap, () => ( 6)));
543
+ const fromCentre = isRect(from) ? centre(from) : from;
544
+ const toCentre = isRect(to) ? centre(to) : to;
545
+ const start = isRect(from) ? gestureEdgePoint(from, toCentre, gap) : from;
546
+ const tip = isRect(to) ? gestureEdgePoint(to, fromCentre, gap) : to;
547
+ const dx = tip[0] - start[0];
548
+ const dy = tip[1] - start[1];
549
+ const len = Math.hypot(dx, dy);
550
+ if (len < 4) throw new Error("The arrow's ends are too close together (or overlap): pass ends at least a few canvas units apart.");
551
+ const curve = clamp(_nullishCoalesce(options.curve, () => ( (0.08 + rand() * 0.08) * (rand() < 0.5 ? -1 : 1))), -1, 1);
552
+ const nx = -dy / len;
553
+ const ny = dx / len;
554
+ const control = [(start[0] + tip[0]) / 2 + nx * curve * len, (start[1] + tip[1]) / 2 + ny * curve * len];
555
+ const noise = smoothNoise(rand);
556
+ const amp = Math.min(2, len * 0.01);
557
+ const n = clamp(Math.ceil(len / 4), 12, 200);
558
+ const shaft = Array.from({ length: n + 1 }, (_, i) => {
559
+ const t = i / n;
560
+ const u = 1 - t;
561
+ const x = u * u * start[0] + 2 * u * t * control[0] + t * t * tip[0];
562
+ const y = u * u * start[1] + 2 * u * t * control[1] + t * t * tip[1];
563
+ const off = amp * noise(t) * Math.sqrt(Math.sin(Math.PI * t));
564
+ return [x + nx * off, y + ny * off];
565
+ });
566
+ const tx = tip[0] - control[0];
567
+ const ty = tip[1] - control[1];
568
+ const tl = Math.hypot(tx, ty) || 1;
569
+ const head = clamp(len * 0.15, 8, 22);
570
+ const barb = (angle2, length) => {
571
+ const c = Math.cos(angle2);
572
+ const s = Math.sin(angle2);
573
+ const bx = -(tx / tl) * c + ty / tl * s;
574
+ const by = -(ty / tl) * c - tx / tl * s;
575
+ return [1 / 3, 2 / 3, 1].map((f) => [tip[0] + bx * length * f, tip[1] + by * length * f]);
576
+ };
577
+ const clear = (pts) => !isRect(to) || pts.every(([x, y]) => x <= to.x || x >= to.x + to.width || y <= to.y || y >= to.y + to.height);
578
+ const fit = (angle2) => {
579
+ const length = head * (0.85 + rand() * 0.3);
580
+ for (let k = 1; k > 0.05; k -= 0.15) {
581
+ const pts = barb(angle2 * k, length);
582
+ if (clear(pts)) return pts;
583
+ }
584
+ return barb(0, length);
585
+ };
586
+ const angle = () => (25 + rand() * 8) * Math.PI / 180;
587
+ const first = fit(angle());
588
+ const second = fit(-angle());
589
+ const points = [...shaft, ...first, ...first.slice(0, -1).reverse(), tip, ...second];
590
+ return { points: points.map(round), pressure: handPressure(rand) };
591
+ }
592
+ function bracketGesture(rect, side, seed, options = {}) {
593
+ const rand = gestureRandom(seed);
594
+ const pad = Math.max(0, _nullishCoalesce(options.padding, () => ( 8)));
595
+ const vertical = side === "left" || side === "right";
596
+ const span = vertical ? rect.height : rect.width;
597
+ const depth = Math.max(1, _nullishCoalesce(options.depth, () => ( clamp(span * 0.08, 6, 20))));
598
+ const from = vertical ? rect.y : rect.x;
599
+ const edge = side === "left" ? rect.x - pad : side === "right" ? rect.x + rect.width + pad : side === "top" ? rect.y - pad : rect.y + rect.height + pad;
600
+ const out = side === "left" || side === "top" ? -1 : 1;
601
+ const at = (s, d) => vertical ? [edge + out * d, from + s * span] : [from + s * span, edge + out * d];
602
+ const noise = smoothNoise(rand);
603
+ const shake = Math.min(1, depth * 0.06);
604
+ const n = clamp(Math.ceil(span / 4), 24, 240);
605
+ let points;
606
+ if (options.shape === "square") {
607
+ const arm = depth * (0.8 + rand() * 0.2);
608
+ const spine = Array.from({ length: n + 1 }, (_, i) => at(i / n, depth + shake * (1 + noise(i / n))));
609
+ points = [at(0, depth - arm), at(0, depth * 0.5), ...spine, at(1, depth * 0.5), at(1, depth - arm * (0.85 + rand() * 0.15))];
610
+ } else {
611
+ const e = clamp(depth / Math.max(span, 1), 0.04, 0.2);
612
+ const brace = (s) => {
613
+ const u = s <= 0.5 ? s : 1 - s;
614
+ if (u < e) return depth / 2 * Math.sin(Math.PI / 2 * u / e);
615
+ if (u < 0.5 - e) return depth / 2;
616
+ return depth / 2 + depth / 2 * (1 - Math.cos(Math.PI / 2 * (u - (0.5 - e)) / e));
617
+ };
618
+ points = Array.from({ length: n + 1 }, (_, i) => {
619
+ const s = i / n;
620
+ return at(s, brace(s) + shake * (1 + noise(s)) * Math.sin(Math.PI * s));
621
+ });
622
+ }
623
+ return { points: points.map(round), pressure: handPressure(rand) };
624
+ }
625
+ function strikeGesture(rect, seed) {
626
+ const rand = gestureRandom(seed);
627
+ const [, cy] = centre(rect);
628
+ const slope = rect.height * 0.08;
629
+ const x0 = rect.x - rect.width * (0.01 + rand() * 0.03);
630
+ const x1 = rect.x + rect.width * (1.01 + rand() * 0.03);
631
+ const y0 = cy + (rand() - 0.5) * rect.height * 0.1 + slope * (rand() - 0.5);
632
+ const y1 = cy + (rand() - 0.5) * rect.height * 0.1 + slope * (rand() - 0.5);
633
+ const points = wobblyLine([x0, y0], [x1, y1], rand, Math.min(1.5, rect.height * 0.05));
634
+ return { points: points.map(round), pressure: handPressure(rand) };
635
+ }
636
+ function crossGesture(rect, seed) {
637
+ const rand = gestureRandom(seed);
638
+ const over = () => 0.02 + rand() * 0.04;
639
+ const left = () => rect.x - rect.width * over();
640
+ const right = () => rect.x + rect.width * (1 + over());
641
+ const top = () => rect.y - rect.height * over();
642
+ const bottom = () => rect.y + rect.height * (1 + over());
643
+ const amp = Math.min(1.5, Math.min(rect.width, rect.height) * 0.03);
644
+ const first = wobblyLine([left(), top()], [right(), bottom()], rand, amp);
645
+ const second = wobblyLine([right(), top()], [left(), bottom()], rand, amp);
646
+ const mid1 = Math.floor(first.length / 2);
647
+ const mid2 = Math.floor(second.length / 2);
648
+ const points = [...first, ...first.slice(mid1, -1).reverse(), ...second.slice(1, mid2 + 1).reverse(), ...second];
649
+ return { points: points.map(round), pressure: handPressure(rand) };
650
+ }
651
+ function checkGesture(at, seed, options = {}) {
652
+ const rand = gestureRandom(seed);
653
+ const [cx, cy] = isRect(at) ? centre(at) : at;
654
+ const s = Math.max(4, _nullishCoalesce(options.size, () => ( (isRect(at) ? clamp(Math.min(at.width, at.height) * 0.6, 12, 48) : 20))));
655
+ const turn = (rand() - 0.5) * 12 * Math.PI / 180;
656
+ const place = ([x, y]) => [cx + s * (x * Math.cos(turn) - y * Math.sin(turn)), cy + s * (x * Math.sin(turn) + y * Math.cos(turn))];
657
+ const p0 = [-0.42 + rand() * 0.08, -0.05 + rand() * 0.08];
658
+ const p1 = [-0.12 + rand() * 0.06, 0.38 + rand() * 0.06];
659
+ const p2 = [0.48 + rand() * 0.06, -0.5 + rand() * 0.06];
660
+ const short = wobblyLine(place(p0), place(p1), rand, 0, -s * 0.03);
661
+ const long = wobblyLine(place(p1), place(p2), rand, Math.min(1, s * 0.02), s * 0.06);
662
+ return { points: [...short, ...long.slice(1)].map(round), pressure: [0.5, 0.95, 0.9, 0.7, 0.35] };
663
+ }
664
+
665
+
666
+
667
+
668
+
669
+
670
+
671
+
672
+
673
+
674
+
675
+
433
676
 
434
677
 
435
678
 
@@ -449,4 +692,4 @@ function matchArrangeShortcut(event, bindings = DEFAULT_ARRANGE_SHORTCUTS) {
449
692
 
450
693
 
451
694
 
452
- exports.PLATFORMS = PLATFORMS; exports.detectPlatform = detectPlatform; exports.keyToCode = keyToCode; exports.codeToKeyText = codeToKeyText; exports.parseChord = parseChord; exports.chordToString = chordToString; exports.normalizeChord = normalizeChord; exports.effectiveChord = effectiveChord; exports.effectiveChordKey = effectiveChordKey; exports.formatShortcut = formatShortcut; exports.eventCode = eventCode; exports.shortcutCode = shortcutCode; exports.codeFromKey = codeFromKey; exports.isEditableEventTarget = isEditableEventTarget; exports.toShortcutBindings = toShortcutBindings; exports.ShortcutMap = ShortcutMap; exports.DEFAULT_ARRANGE_SHORTCUTS = DEFAULT_ARRANGE_SHORTCUTS; exports.matchArrangeShortcut = matchArrangeShortcut;
695
+ exports.PLATFORMS = PLATFORMS; exports.detectPlatform = detectPlatform; exports.keyToCode = keyToCode; exports.codeToKeyText = codeToKeyText; exports.parseChord = parseChord; exports.chordToString = chordToString; exports.normalizeChord = normalizeChord; exports.effectiveChord = effectiveChord; exports.effectiveChordKey = effectiveChordKey; exports.formatShortcut = formatShortcut; exports.eventCode = eventCode; exports.shortcutCode = shortcutCode; exports.codeFromKey = codeFromKey; exports.isEditableEventTarget = isEditableEventTarget; exports.toShortcutBindings = toShortcutBindings; exports.ShortcutMap = ShortcutMap; exports.DEFAULT_ARRANGE_SHORTCUTS = DEFAULT_ARRANGE_SHORTCUTS; exports.matchArrangeShortcut = matchArrangeShortcut; exports.gestureRandom = gestureRandom; exports.gestureSeed = gestureSeed; exports.gestureUnion = gestureUnion; exports.circleGesture = circleGesture; exports.underlineGesture = underlineGesture; exports.gestureEdgePoint = gestureEdgePoint; exports.arrowGesture = arrowGesture; exports.bracketGesture = bracketGesture; exports.strikeGesture = strikeGesture; exports.crossGesture = crossGesture; exports.checkGesture = checkGesture;
@@ -1,7 +1,16 @@
1
1
  import {
2
2
  DEFAULT_ARRANGE_SHORTCUTS,
3
- ShortcutMap
4
- } from "./chunk-7KZ2HKKC.js";
3
+ ShortcutMap,
4
+ arrowGesture,
5
+ bracketGesture,
6
+ checkGesture,
7
+ circleGesture,
8
+ crossGesture,
9
+ gestureSeed,
10
+ gestureUnion,
11
+ strikeGesture,
12
+ underlineGesture
13
+ } from "./chunk-LPFQHVCO.js";
5
14
 
6
15
  // src/commands/builtins.ts
7
16
  var DEFAULT_HISTORY_SHORTCUTS = [
@@ -370,7 +379,202 @@ function createDrawCommands(target, options = {}) {
370
379
  params: { type: "object", additionalProperties: false, properties: {} },
371
380
  readOnly: true,
372
381
  run: async () => ({ brushes: await target.listBrushes() })
382
+ },
383
+ ...createGestureCommands(target, base, prefix, options)
384
+ ];
385
+ }
386
+ var GESTURE_RECT_PARAM = {
387
+ type: "object",
388
+ additionalProperties: false,
389
+ required: ["x", "y", "width", "height"],
390
+ description: "A box in canvas units (top left x, y and its size), e.g. a node's bounds from get_editor_state or a stroke's `bounds`.",
391
+ properties: {
392
+ x: { type: "number", description: "Left edge, canvas units." },
393
+ y: { type: "number", description: "Top edge, canvas units." },
394
+ width: { type: "number", minimum: 0, description: "Width, canvas units." },
395
+ height: { type: "number", minimum: 0, description: "Height, canvas units." }
396
+ }
397
+ };
398
+ var GESTURE_NODE_PARAM = {
399
+ type: "object",
400
+ additionalProperties: false,
401
+ required: ["nodeId"],
402
+ description: "A node on the canvas by id; its current box is used, so you need not read its position first.",
403
+ properties: { nodeId: { type: "string", minLength: 1, description: "The node id (from get_editor_state or a result)." } }
404
+ };
405
+ var GESTURE_POINT_PARAM = {
406
+ type: "array",
407
+ minItems: 2,
408
+ maxItems: 2,
409
+ items: { type: "number" },
410
+ description: "A point as [x, y] in canvas units."
411
+ };
412
+ var gestureBox = (what) => ({ description: what, oneOf: [GESTURE_RECT_PARAM, GESTURE_NODE_PARAM] });
413
+ var gestureEnd = (what) => ({ description: what, oneOf: [GESTURE_RECT_PARAM, GESTURE_NODE_PARAM, GESTURE_POINT_PARAM] });
414
+ var gestureBoxes = (what) => ({
415
+ type: "array",
416
+ minItems: 1,
417
+ maxItems: 100,
418
+ items: { oneOf: [GESTURE_RECT_PARAM, GESTURE_NODE_PARAM] },
419
+ description: `${what} Each is a box { x, y, width, height } or a node { nodeId }; the gesture spans the box around all of them.`
420
+ });
421
+ var GESTURE_STYLE_PARAMS = {
422
+ tool: { type: "string", enum: ["pen", "highlighter"], description: "Pen (default: blue, width 3) or highlighter (yellow, width 24, opacity 0.35); sets the defaults below." },
423
+ color: { type: "string", description: "Stroke colour, any CSS colour (default from the tool)." },
424
+ width: { type: "number", exclusiveMinimum: 0, maximum: 2500, description: "Stroke width in canvas units (default from the tool; with a brush, the preset's own size)." },
425
+ opacity: { type: "number", minimum: 0, maximum: 1, description: "Stroke opacity 0..1 (default from the tool)." },
426
+ brush: {
427
+ description: 'Paint the mark with a dry paint brush: a preset id such as "ink-pen", "marker", "pencil-hb" or "brush-pen" (all ids: canvas.listBrushes), or tip settings. Brushed marks get an uneven hand pressure. Omit for a plain vector stroke.',
428
+ oneOf: [{ type: "string", minLength: 1 }, BRUSH_SETTINGS_PARAM]
429
+ },
430
+ seed: { type: "integer", minimum: 0, maximum: 4294967295, description: "Seed for the hand-drawn wobble (default: derived from the other params, so the same request draws the same mark). Change it for a different take." }
431
+ };
432
+ function createGestureCommands(target, base, prefix, options) {
433
+ const box = (value, name) => {
434
+ if (Array.isArray(value)) throw new Error(`${name} must be a box { x, y, width, height } or a node { nodeId }, not a point.`);
435
+ if ("nodeId" in value) {
436
+ if (!target.nodeBounds) throw new Error(`This canvas cannot look up nodes: pass ${name} as a box { x, y, width, height } (a node's bounds from get_editor_state).`);
437
+ const rect = target.nodeBounds(value.nodeId);
438
+ if (!rect) throw new Error(`Unknown node "${value.nodeId}" in ${name}: use an id from get_editor_state.`);
439
+ return rect;
373
440
  }
441
+ return value;
442
+ };
443
+ const end = (value, name) => Array.isArray(value) ? [value[0], value[1]] : box(value, name);
444
+ const boxes = (values, name) => gestureUnion(values.map((v, i) => box(v, `${name}[${i}]`)));
445
+ const gesture = (spec) => {
446
+ const seedOf = (params) => params.seed ?? gestureSeed({ gesture: spec.name, ...params });
447
+ const generate = (params) => {
448
+ const seed = seedOf(params);
449
+ return { seed, stroke: spec.make(params, seed) };
450
+ };
451
+ return {
452
+ ...base,
453
+ id: `${prefix}${spec.name}`,
454
+ label: spec.label,
455
+ keywords: ["annotate", "mark up", "hand-drawn", ...spec.keywords],
456
+ description: `${spec.description} Drawn as one hand-drawn pen or highlighter stroke node (one undo step), with a seeded wobble. Returns the new node \`id\`, its \`bounds\`, the stored \`brush\` (when brushed) and the \`seed\` used.`,
457
+ params: {
458
+ type: "object",
459
+ additionalProperties: false,
460
+ required: spec.required,
461
+ properties: { ...spec.properties, ...GESTURE_STYLE_PARAMS }
462
+ },
463
+ enabled: () => options.readOnlyReason?.() || true,
464
+ plan: (_ctx, params) => {
465
+ try {
466
+ return planFreehandPlayback(generate(params).stroke.points);
467
+ } catch {
468
+ return null;
469
+ }
470
+ },
471
+ run: async (_ctx, params) => {
472
+ const { seed, stroke } = generate(params);
473
+ const result = await target.drawStroke({
474
+ points: stroke.points,
475
+ ...params.tool ? { tool: params.tool } : {},
476
+ style: {
477
+ ...params.color !== void 0 ? { color: params.color } : {},
478
+ ...params.width !== void 0 ? { width: params.width } : {},
479
+ ...params.width !== void 0 && params.brush !== void 0 ? { brushSize: params.width } : {},
480
+ ...params.opacity !== void 0 ? { opacity: params.opacity } : {},
481
+ ...params.brush !== void 0 ? { brush: params.brush } : {}
482
+ },
483
+ ...params.brush !== void 0 ? { pressure: stroke.pressure } : {}
484
+ });
485
+ return { ...result, seed };
486
+ }
487
+ };
488
+ };
489
+ return [
490
+ gesture({
491
+ name: "circle",
492
+ label: "Circle Around",
493
+ keywords: ["circle", "ring", "loop", "highlight", "pick", "ellipse"],
494
+ description: "Circles boxes or nodes with a loose, slightly open ellipse that never cuts into them: the way to point out a pick or the thing you are talking about.",
495
+ required: ["around"],
496
+ properties: {
497
+ around: gestureBoxes("What to circle."),
498
+ padding: { type: "number", minimum: 0, maximum: 500, description: "Space between them and the loop, canvas units (default 8)." }
499
+ },
500
+ make: (p, seed) => circleGesture(boxes(p.around, "around"), seed, { ...p.padding !== void 0 ? { padding: p.padding } : {} })
501
+ }),
502
+ gesture({
503
+ name: "underline",
504
+ label: "Underline",
505
+ keywords: ["underline", "emphasis", "line under", "squiggle"],
506
+ description: "Underlines a box or node with a stroke just below its bottom edge, a little longer or shorter than it: for stressing a line of text or a value.",
507
+ required: ["under"],
508
+ properties: {
509
+ under: gestureBox("What to underline: a box { x, y, width, height } or a node { nodeId }."),
510
+ offset: { type: "number", minimum: 0, maximum: 200, description: "Gap below the bottom edge, canvas units (default 4)." },
511
+ wave: { type: "boolean", description: "A wavy (squiggly) underline instead of a straight one (default false)." }
512
+ },
513
+ make: (p, seed) => underlineGesture(box(p.under, "under"), seed, { ...p.offset !== void 0 ? { offset: p.offset } : {}, ...p.wave ? { wave: true } : {} })
514
+ }),
515
+ gesture({
516
+ name: "arrow",
517
+ label: "Arrow",
518
+ keywords: ["arrow", "point", "connect", "from", "to", "pointer"],
519
+ description: "Draws a curved arrow with a hand-drawn head from one thing to another: a box or node is reached at its nearest edge, a point exactly. For linking a swatch to where it is used, or pointing from a note to its subject.",
520
+ required: ["from", "to"],
521
+ properties: {
522
+ from: gestureEnd("Where the arrow starts: a box, a node { nodeId } or a point [x, y]."),
523
+ to: gestureEnd("Where the arrow points: a box, a node { nodeId } or a point [x, y]."),
524
+ curve: { type: "number", minimum: -1, maximum: 1, description: "Bend of the shaft \u22121..1: how far its middle bulges, as a fraction of half its length; positive bends to the left of travel, 0 is straight (default: a gentle seeded bend, about 0.1)." },
525
+ gap: { type: "number", minimum: 0, maximum: 200, description: "Space between a box end and the arrow, canvas units (default 6)." }
526
+ },
527
+ make: (p, seed) => arrowGesture(end(p.from, "from"), end(p.to, "to"), seed, { ...p.curve !== void 0 ? { curve: p.curve } : {}, ...p.gap !== void 0 ? { gap: p.gap } : {} })
528
+ }),
529
+ gesture({
530
+ name: "bracket",
531
+ label: "Bracket",
532
+ keywords: ["bracket", "brace", "group", "curly", "span"],
533
+ description: "Brackets boxes or nodes on one side with a curly or square bracket spanning them end to end: for grouping a set of items or a range of lines, usually with a note beside it.",
534
+ required: ["around", "side"],
535
+ properties: {
536
+ around: gestureBoxes("What to bracket."),
537
+ side: { type: "string", enum: ["left", "right", "top", "bottom"], description: "Which side the bracket goes on." },
538
+ shape: { type: "string", enum: ["curly", "square"], description: "Curly brace (default) or square bracket." },
539
+ padding: { type: "number", minimum: 0, maximum: 500, description: "Gap between them and the bracket, canvas units (default 8)." },
540
+ depth: { type: "number", exclusiveMinimum: 0, maximum: 500, description: "How far the bracket reaches out, canvas units (default: from the span, 6..20)." }
541
+ },
542
+ make: (p, seed) => bracketGesture(boxes(p.around, "around"), p.side, seed, {
543
+ ...p.shape ? { shape: p.shape } : {},
544
+ ...p.padding !== void 0 ? { padding: p.padding } : {},
545
+ ...p.depth !== void 0 ? { depth: p.depth } : {}
546
+ })
547
+ }),
548
+ gesture({
549
+ name: "strike",
550
+ label: "Strike Through",
551
+ keywords: ["strike", "strikethrough", "cross out", "line through", "delete"],
552
+ description: "Strikes a line through the middle of a box or node, a little past both ends: for text or a value to drop. It only marks it; nothing is deleted.",
553
+ required: ["over"],
554
+ properties: { over: gestureBox("What to strike through: a box { x, y, width, height } or a node { nodeId }.") },
555
+ make: (p, seed) => strikeGesture(box(p.over, "over"), seed)
556
+ }),
557
+ gesture({
558
+ name: "cross",
559
+ label: "Cross Out",
560
+ keywords: ["cross", "x", "reject", "no", "wrong", "cross out"],
561
+ description: "Crosses out a box or node with an X from corner to corner: for a rejected option. It only marks it; nothing is deleted. One stroke, so the pen runs back over part of the first diagonal before drawing the second.",
562
+ required: ["over"],
563
+ properties: { over: gestureBox("What to cross out: a box { x, y, width, height } or a node { nodeId }.") },
564
+ make: (p, seed) => crossGesture(box(p.over, "over"), seed)
565
+ }),
566
+ gesture({
567
+ name: "check",
568
+ label: "Check Mark",
569
+ keywords: ["check", "tick", "approve", "yes", "done", "ok"],
570
+ description: "Draws a tick mark in the middle of a box or node, or centred on a point: for an approved option or a done item.",
571
+ required: ["at"],
572
+ properties: {
573
+ at: gestureEnd("Where the tick goes: a box, a node { nodeId } (its centre) or a point [x, y]."),
574
+ size: { type: "number", exclusiveMinimum: 0, maximum: 2e3, description: "Height of the tick, canvas units (default: 60% of the box's smaller side, 12..48; 20 at a point)." }
575
+ },
576
+ make: (p, seed) => checkGesture(end(p.at, "at"), seed, { ...p.size !== void 0 ? { size: p.size } : {} })
577
+ })
374
578
  ];
375
579
  }
376
580
  function hasVariantCommands(controller) {
@@ -11,6 +11,17 @@ export interface CommandToolProviderOptions<TContext = any> {
11
11
  scope?: string | readonly string[];
12
12
  /** Commands agents may not see or run (e.g. ones that open dialogs). */
13
13
  exclude?: (id: string) => boolean;
14
+ /**
15
+ * Commands also listed as tools of their own, beside `run_command` (ids, or a filter): the
16
+ * params schema is the tool's input schema, and the label, description, read-only flag and
17
+ * playback plan carry over. Agents then see them without calling `list_commands` first.
18
+ */
19
+ tools?: readonly string[] | ((id: string) => boolean);
20
+ /**
21
+ * Name of a command's own tool (default: the id with every character other than letters,
22
+ * digits, `_` and `-` replaced by `_`, so `canvas.circle` is `canvas_circle`).
23
+ */
24
+ toolName?: (id: string) => string;
14
25
  }
15
26
  /**
16
27
  * Exposes a `CommandRegistry` to agents through a `ToolRegistry`:
@@ -26,6 +37,9 @@ export interface CommandToolProviderOptions<TContext = any> {
26
37
  * registry with `history`) runs them as one undo step named `label` and takes everything
27
38
  * back when one fails.
28
39
  *
40
+ * With `tools`, chosen commands are also tools of their own (`canvas.circle` →
41
+ * `canvas_circle`, or `toolName`), taking the command's params as arguments.
42
+ *
29
43
  * The id enum in `run_command`'s schema follows the registered commands, so the
30
44
  * `ToolRegistry` announces `listChanged` when commands are added or removed.
31
45
  */
@@ -11,6 +11,17 @@ export interface CommandToolProviderOptions<TContext = any> {
11
11
  scope?: string | readonly string[];
12
12
  /** Commands agents may not see or run (e.g. ones that open dialogs). */
13
13
  exclude?: (id: string) => boolean;
14
+ /**
15
+ * Commands also listed as tools of their own, beside `run_command` (ids, or a filter): the
16
+ * params schema is the tool's input schema, and the label, description, read-only flag and
17
+ * playback plan carry over. Agents then see them without calling `list_commands` first.
18
+ */
19
+ tools?: readonly string[] | ((id: string) => boolean);
20
+ /**
21
+ * Name of a command's own tool (default: the id with every character other than letters,
22
+ * digits, `_` and `-` replaced by `_`, so `canvas.circle` is `canvas_circle`).
23
+ */
24
+ toolName?: (id: string) => string;
14
25
  }
15
26
  /**
16
27
  * Exposes a `CommandRegistry` to agents through a `ToolRegistry`:
@@ -26,6 +37,9 @@ export interface CommandToolProviderOptions<TContext = any> {
26
37
  * registry with `history`) runs them as one undo step named `label` and takes everything
27
38
  * back when one fails.
28
39
  *
40
+ * With `tools`, chosen commands are also tools of their own (`canvas.circle` →
41
+ * `canvas_circle`, or `toolName`), taking the command's params as arguments.
42
+ *
29
43
  * The id enum in `run_command`'s schema follows the registered commands, so the
30
44
  * `ToolRegistry` announces `listChanged` when commands are added or removed.
31
45
  */
@@ -126,6 +126,13 @@ export interface DrawCommandTarget {
126
126
  }>;
127
127
  /** The dry paint presets a stroke can be brushed with (loads the paint engine). */
128
128
  listBrushes(): Promise<CanvasBrushPreset[]>;
129
+ /** A node's box in canvas coordinates, so gestures can take `{ nodeId }` targets (without it they take boxes and points only). */
130
+ nodeBounds?(id: string): {
131
+ x: number;
132
+ y: number;
133
+ width: number;
134
+ height: number;
135
+ } | undefined;
129
136
  }
130
137
  export interface DrawCommandOptions {
131
138
  /** Id prefix (default `canvas.`). */
@@ -144,7 +151,10 @@ export declare function planFreehandPlayback(points: unknown, max?: number): Pla
144
151
  /**
145
152
  * Agent / palette commands for freehand drawing: `canvas.drawFreehand` (a pen or highlighter
146
153
  * stroke through given points, optionally brushed with a dry paint preset and a pressure / tilt
147
- * profile; one undo step) and the read-only `canvas.listBrushes` (the dry presets it accepts).
154
+ * profile; one undo step), the read-only `canvas.listBrushes` (the dry presets it accepts) and
155
+ * the annotation gestures `canvas.circle`, `canvas.underline`, `canvas.arrow`, `canvas.bracket`,
156
+ * `canvas.strike`, `canvas.cross` and `canvas.check` (hand-drawn marks around boxes, points or,
157
+ * when the target has `nodeBounds`, nodes).
148
158
  */
149
159
  export declare function createDrawCommands(target: DrawCommandTarget, options?: DrawCommandOptions): CommandSpec[];
150
160
  type ComponentActionResult = {
@@ -126,6 +126,13 @@ export interface DrawCommandTarget {
126
126
  }>;
127
127
  /** The dry paint presets a stroke can be brushed with (loads the paint engine). */
128
128
  listBrushes(): Promise<CanvasBrushPreset[]>;
129
+ /** A node's box in canvas coordinates, so gestures can take `{ nodeId }` targets (without it they take boxes and points only). */
130
+ nodeBounds?(id: string): {
131
+ x: number;
132
+ y: number;
133
+ width: number;
134
+ height: number;
135
+ } | undefined;
129
136
  }
130
137
  export interface DrawCommandOptions {
131
138
  /** Id prefix (default `canvas.`). */
@@ -144,7 +151,10 @@ export declare function planFreehandPlayback(points: unknown, max?: number): Pla
144
151
  /**
145
152
  * Agent / palette commands for freehand drawing: `canvas.drawFreehand` (a pen or highlighter
146
153
  * stroke through given points, optionally brushed with a dry paint preset and a pressure / tilt
147
- * profile; one undo step) and the read-only `canvas.listBrushes` (the dry presets it accepts).
154
+ * profile; one undo step), the read-only `canvas.listBrushes` (the dry presets it accepts) and
155
+ * the annotation gestures `canvas.circle`, `canvas.underline`, `canvas.arrow`, `canvas.bracket`,
156
+ * `canvas.strike`, `canvas.cross` and `canvas.check` (hand-drawn marks around boxes, points or,
157
+ * when the target has `nodeBounds`, nodes).
148
158
  */
149
159
  export declare function createDrawCommands(target: DrawCommandTarget, options?: DrawCommandOptions): CommandSpec[];
150
160
  type ComponentActionResult = {