@trohde/agentic-canvas 0.1.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 (162) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +160 -0
  4. package/SECURITY.md +19 -0
  5. package/dist/cli/index.js +1873 -0
  6. package/dist/cli/index.js.map +1 -0
  7. package/dist/web/assets/Assistant-Bold-gm-uSS1B.woff2 +0 -0
  8. package/dist/web/assets/Assistant-Medium-DrcxCXg3.woff2 +0 -0
  9. package/dist/web/assets/Assistant-Regular-DVxZuzxb.woff2 +0 -0
  10. package/dist/web/assets/Assistant-SemiBold-SCI4bEL9.woff2 +0 -0
  11. package/dist/web/assets/ar-SA-G6X2FPQ2-Bx_JGCzc.js +10 -0
  12. package/dist/web/assets/arc-CZEYm-X2.js +1 -0
  13. package/dist/web/assets/architecture-7EHR7CIX-BW08P3jh.js +1 -0
  14. package/dist/web/assets/architectureDiagram-3BPJPVTR-BX4UqKAe.js +36 -0
  15. package/dist/web/assets/array-BifhSqXX.js +1 -0
  16. package/dist/web/assets/az-AZ-76LH7QW2-CQnzCEm_.js +1 -0
  17. package/dist/web/assets/bg-BG-XCXSNQG7-B6OhJNg1.js +5 -0
  18. package/dist/web/assets/blockDiagram-GPEHLZMM-DOaLZNH2.js +132 -0
  19. package/dist/web/assets/bn-BD-2XOGV67Q-CL8DmeD-.js +5 -0
  20. package/dist/web/assets/c4Diagram-AAUBKEIU-BOYbD4o9.js +10 -0
  21. package/dist/web/assets/ca-ES-6MX7JW3Y-DqXuGb3N.js +8 -0
  22. package/dist/web/assets/channel-jlcmNkDM.js +1 -0
  23. package/dist/web/assets/chunk-2J33WTMH-9pRDhFmL.js +1 -0
  24. package/dist/web/assets/chunk-3OPIFGDE-Cxtej-eZ.js +62 -0
  25. package/dist/web/assets/chunk-4BX2VUAB-WGnRPgB4.js +1 -0
  26. package/dist/web/assets/chunk-55IACEB6-By23tM0L.js +1 -0
  27. package/dist/web/assets/chunk-5ZQYHXKU-BbvpmZDT.js +2 -0
  28. package/dist/web/assets/chunk-727SXJPM-DeN-6nx2.js +206 -0
  29. package/dist/web/assets/chunk-AQP2D5EJ-3vlT2osY.js +231 -0
  30. package/dist/web/assets/chunk-BSJP7CBP-ggZwEsYs.js +1 -0
  31. package/dist/web/assets/chunk-CSCIHK7Q-CkCt7BGB.js +124 -0
  32. package/dist/web/assets/chunk-EIO257PC-DhAXVvAc.js +22 -0
  33. package/dist/web/assets/chunk-FMBD7UC4-BWW1dpup.js +15 -0
  34. package/dist/web/assets/chunk-KSCS5N6A-ByGpruIk.js +10 -0
  35. package/dist/web/assets/chunk-L5ZTLDWV-Brj903IS.js +1 -0
  36. package/dist/web/assets/chunk-LZXEDZCA-B0E6SbHa.js +2 -0
  37. package/dist/web/assets/chunk-ND2GUHAM-cgO1vAyy.js +1 -0
  38. package/dist/web/assets/chunk-NNHCCRGN-DlpIbxXb.js +159 -0
  39. package/dist/web/assets/chunk-NZK2D7GU-rJf6zpwf.js +1 -0
  40. package/dist/web/assets/chunk-O5CBEL6O-DZpUP7B-.js +70 -0
  41. package/dist/web/assets/chunk-QZHKN3VN-B2hsWqvV.js +1 -0
  42. package/dist/web/assets/chunk-WU5MYG2G-ad-_f9bA.js +1 -0
  43. package/dist/web/assets/chunk-XPW4576I-DRG_5uRX.js +32 -0
  44. package/dist/web/assets/classDiagram-4FO5ZUOK-pgvxJ5K2.js +1 -0
  45. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-pgvxJ5K2.js +1 -0
  46. package/dist/web/assets/cose-bilkent-S5V4N54A-Dzdtq3ax.js +1 -0
  47. package/dist/web/assets/cs-CZ-2BRQDIVT-C9Hi60ft.js +11 -0
  48. package/dist/web/assets/cytoscape.esm-h6BdjjI9.js +321 -0
  49. package/dist/web/assets/da-DK-5WZEPLOC-DuscK2dU.js +5 -0
  50. package/dist/web/assets/dagre-BM42HDAG-DG-1WRTH.js +4 -0
  51. package/dist/web/assets/dagre-Bx709z4p.js +1 -0
  52. package/dist/web/assets/de-DE-XR44H4JA-BUknVi9m.js +8 -0
  53. package/dist/web/assets/defaultLocale-C8Fc0cco.js +1 -0
  54. package/dist/web/assets/diagram-2AECGRRQ-C01yy19V.js +43 -0
  55. package/dist/web/assets/diagram-5GNKFQAL-9Ly54gPf.js +10 -0
  56. package/dist/web/assets/diagram-KO2AKTUF-Bnzn_Fk2.js +3 -0
  57. package/dist/web/assets/diagram-LMA3HP47-CkQJH7qS.js +24 -0
  58. package/dist/web/assets/diagram-OG6HWLK6-sYTSt9S6.js +24 -0
  59. package/dist/web/assets/directory-open-01563666-D4xXyWb_.js +1 -0
  60. package/dist/web/assets/directory-open-4ed118d0-1i309Asm.js +1 -0
  61. package/dist/web/assets/dist--k9jX8ko.js +27 -0
  62. package/dist/web/assets/dist-CHteZrRU.js +1 -0
  63. package/dist/web/assets/el-GR-BZB4AONW-CBx8IJCe.js +10 -0
  64. package/dist/web/assets/en-B4ZKOASM-D04FpeSQ.js +1 -0
  65. package/dist/web/assets/erDiagram-TEJ5UH35-kBTcPhaQ.js +85 -0
  66. package/dist/web/assets/es-ES-U4NZUMDT-nQPOkQIb.js +9 -0
  67. package/dist/web/assets/eu-ES-A7QVB2H4-OVL5j0Bz.js +11 -0
  68. package/dist/web/assets/eventmodeling-FCH6USID-3Z5bJ280.js +1 -0
  69. package/dist/web/assets/fa-IR-HGAKTJCU-BXJbxYFk.js +8 -0
  70. package/dist/web/assets/fi-FI-Z5N7JZ37-DoZfr4yv.js +6 -0
  71. package/dist/web/assets/file-open-002ab408-BHUWm0Sh.js +1 -0
  72. package/dist/web/assets/file-open-7c801643-DZtJT5zp.js +1 -0
  73. package/dist/web/assets/file-save-3189631c-CO9S4HFW.js +1 -0
  74. package/dist/web/assets/file-save-745eba88-Bwdfz6OZ.js +1 -0
  75. package/dist/web/assets/flowDiagram-I6XJVG4X-CwQyslgb.js +162 -0
  76. package/dist/web/assets/fr-FR-RHASNOE6-CXLv0m_p.js +9 -0
  77. package/dist/web/assets/ganttDiagram-6RSMTGT7-B-1gME9q.js +292 -0
  78. package/dist/web/assets/gitGraph-WXDBUCRP-BC3bsb5A.js +1 -0
  79. package/dist/web/assets/gitGraphDiagram-PVQCEYII-DUh_E2g-.js +106 -0
  80. package/dist/web/assets/gl-ES-HMX3MZ6V-DMYtQjyy.js +10 -0
  81. package/dist/web/assets/graphlib-B8gBHxth.js +1 -0
  82. package/dist/web/assets/he-IL-6SHJWFNN-BXviMgxV.js +10 -0
  83. package/dist/web/assets/hi-IN-IWLTKZ5I-B6s0F0bv.js +4 -0
  84. package/dist/web/assets/hu-HU-A5ZG7DT2-DZY88thP.js +7 -0
  85. package/dist/web/assets/id-ID-SAP4L64H-CsRPE_UY.js +10 -0
  86. package/dist/web/assets/image-GAAHSSAO-Ufj_572K.js +1 -0
  87. package/dist/web/assets/image-blob-reduce.esm-NzmDxm1v.js +2 -0
  88. package/dist/web/assets/index-DedR31Vl.css +1 -0
  89. package/dist/web/assets/index-Do_WCY2x.js +207 -0
  90. package/dist/web/assets/info-J43DQDTF-DgcAwO7L.js +1 -0
  91. package/dist/web/assets/infoDiagram-5YYISTIA-BVoRZtf6.js +2 -0
  92. package/dist/web/assets/init-D6jRqBbL.js +1 -0
  93. package/dist/web/assets/ishikawaDiagram-YF4QCWOH-Pc0lSZjn.js +70 -0
  94. package/dist/web/assets/it-IT-JPQ66NNP-D6QIIUKx.js +11 -0
  95. package/dist/web/assets/ja-JP-DBVTYXUO-BKIOpiiN.js +8 -0
  96. package/dist/web/assets/journeyDiagram-JHISSGLW-CUTIkP_3.js +139 -0
  97. package/dist/web/assets/kaa-6HZHGXH3-Ck2PqClI.js +1 -0
  98. package/dist/web/assets/kab-KAB-ZGHBKWFO-9067fQ1h.js +8 -0
  99. package/dist/web/assets/kanban-definition-UN3LZRKU-f9h-Kl6S.js +89 -0
  100. package/dist/web/assets/katex-Vhh-h91d.js +257 -0
  101. package/dist/web/assets/kk-KZ-P5N5QNE5-Ds5pD5Rs.js +1 -0
  102. package/dist/web/assets/km-KH-HSX4SM5Z-BvgABkMn.js +11 -0
  103. package/dist/web/assets/ko-KR-MTYHY66A-BI-VJ_qS.js +9 -0
  104. package/dist/web/assets/ku-TR-6OUDTVRD-CzO0QxpT.js +9 -0
  105. package/dist/web/assets/line-BM7n-WSY.js +1 -0
  106. package/dist/web/assets/linear-CoV0e-iv.js +1 -0
  107. package/dist/web/assets/lt-LT-XHIRWOB4-Wdr8437y.js +3 -0
  108. package/dist/web/assets/lv-LV-5QDEKY6T-Baqs6ETz.js +7 -0
  109. package/dist/web/assets/mermaid-parser.core-BxU7L1C-.js +4 -0
  110. package/dist/web/assets/mindmap-definition-RKZ34NQL-e2CkjxCV.js +96 -0
  111. package/dist/web/assets/mr-IN-CRQNXWMA-_P3j2iZu.js +13 -0
  112. package/dist/web/assets/my-MM-5M5IBNSE-u-YXjqOx.js +1 -0
  113. package/dist/web/assets/nb-NO-T6EIAALU-BVTkGOfM.js +10 -0
  114. package/dist/web/assets/nl-NL-IS3SIHDZ-Dz45KRHt.js +8 -0
  115. package/dist/web/assets/nn-NO-6E72VCQL-Bv1T99Os.js +8 -0
  116. package/dist/web/assets/oc-FR-POXYY2M6-CWPx0BPy.js +8 -0
  117. package/dist/web/assets/ordinal-hYBb2elL.js +1 -0
  118. package/dist/web/assets/pa-IN-N4M65BXN-CUAufnLD.js +4 -0
  119. package/dist/web/assets/packet-YPE3B663-DSUdqwD6.js +1 -0
  120. package/dist/web/assets/path-BWPyau1x.js +1 -0
  121. package/dist/web/assets/percentages-BXMCSKIN-BRi-lUYV.js +1 -0
  122. package/dist/web/assets/pica-lghYzniR.js +2 -0
  123. package/dist/web/assets/pie-LRSECV5Y-CtaLKxkL.js +1 -0
  124. package/dist/web/assets/pieDiagram-4H26LBE5-KQxiMI7y.js +30 -0
  125. package/dist/web/assets/pl-PL-T2D74RX3-BYDGKTrw.js +9 -0
  126. package/dist/web/assets/pt-BR-5N22H2LF-DiLHA1Fv.js +9 -0
  127. package/dist/web/assets/pt-PT-UZXXM6DQ-DO4nqrjh.js +9 -0
  128. package/dist/web/assets/quadrantDiagram-W4KKPZXB-DYnhMBii.js +7 -0
  129. package/dist/web/assets/radar-GUYGQ44K-BWVL_5jd.js +1 -0
  130. package/dist/web/assets/requirementDiagram-4Y6WPE33-CLSyCDmb.js +84 -0
  131. package/dist/web/assets/ro-RO-JPDTUUEW-CkArAo01.js +11 -0
  132. package/dist/web/assets/rough.esm-CSKSodPl.js +1 -0
  133. package/dist/web/assets/roundRect-D01gJrlt.js +1 -0
  134. package/dist/web/assets/ru-RU-B4JR7IUQ-CF7qZR2L.js +9 -0
  135. package/dist/web/assets/sankeyDiagram-5OEKKPKP-fCrXqVCu.js +40 -0
  136. package/dist/web/assets/sequenceDiagram-3UESZ5HK-Ibg1UsPm.js +162 -0
  137. package/dist/web/assets/si-LK-N5RQ5JYF-SlyZ_B_5.js +1 -0
  138. package/dist/web/assets/sk-SK-C5VTKIMK-B59JZXsV.js +6 -0
  139. package/dist/web/assets/sl-SI-NN7IZMDC-CkVIpRnH.js +6 -0
  140. package/dist/web/assets/src-Dc-yDLup.js +1 -0
  141. package/dist/web/assets/stateDiagram-AJRCARHV-Cg6ervwo.js +1 -0
  142. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-Z9e_4KHc.js +1 -0
  143. package/dist/web/assets/subset-shared.chunk-CnjPFGnW.js +1 -0
  144. package/dist/web/assets/subset-worker.chunk-DN4FBbFb.js +1 -0
  145. package/dist/web/assets/sv-SE-XGPEYMSR-BwnaFVSC.js +10 -0
  146. package/dist/web/assets/ta-IN-2NMHFXQM-Digwj7d-.js +9 -0
  147. package/dist/web/assets/th-TH-HPSO5L25-Ck3FgDpQ.js +2 -0
  148. package/dist/web/assets/timeline-definition-PNZ67QCA-Sxfm8qnw.js +120 -0
  149. package/dist/web/assets/tr-TR-DEFEU3FU-CZntbglt.js +7 -0
  150. package/dist/web/assets/treeView-BLDUP644-Cx8t5kj4.js +1 -0
  151. package/dist/web/assets/treemap-LRROVOQU-Dfh_IQDp.js +1 -0
  152. package/dist/web/assets/uk-UA-QMV73CPH-B0wjKowt.js +6 -0
  153. package/dist/web/assets/vennDiagram-CIIHVFJN-BMEOJ0gl.js +34 -0
  154. package/dist/web/assets/vi-VN-M7AON7JQ-Dj0BUYxm.js +5 -0
  155. package/dist/web/assets/wardley-L42UT6IY-EJTnFNq7.js +1 -0
  156. package/dist/web/assets/wardleyDiagram-YWT4CUSO-BJjdV56L.js +78 -0
  157. package/dist/web/assets/xychartDiagram-2RQKCTM6-CBQ88pV0.js +7 -0
  158. package/dist/web/assets/zh-CN-LNUGB5OW-K1_YaWy1.js +10 -0
  159. package/dist/web/assets/zh-HK-E62DVLB3-DCU_gXiJ.js +1 -0
  160. package/dist/web/assets/zh-TW-RAJ6MFWO-1ApXhCfP.js +9 -0
  161. package/dist/web/index.html +13 -0
  162. package/package.json +96 -0
@@ -0,0 +1,1873 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/index.ts
4
+ import { parseArgs } from "util";
5
+ import open from "open";
6
+
7
+ // src/server/httpServer.ts
8
+ import { existsSync } from "fs";
9
+ import { createServer } from "http";
10
+ import net from "net";
11
+ import path4 from "path";
12
+ import { fileURLToPath as fileURLToPath2 } from "url";
13
+
14
+ // src/plugins/excalidraw/index.ts
15
+ import { randomUUID as randomUUID2 } from "crypto";
16
+
17
+ // src/core/scene.ts
18
+ function isElementEndpoint(endpoint) {
19
+ return "elementId" in endpoint;
20
+ }
21
+ function cloneScene(scene) {
22
+ return {
23
+ elements: scene.elements.map((element) => ({ ...element })),
24
+ appState: { ...scene.appState },
25
+ files: { ...scene.files },
26
+ version: scene.version
27
+ };
28
+ }
29
+
30
+ // src/plugins/excalidraw/adapter.ts
31
+ var SUPPORTED_TYPES = /* @__PURE__ */ new Set([
32
+ "rectangle",
33
+ "ellipse",
34
+ "diamond",
35
+ "line",
36
+ "arrow",
37
+ "text",
38
+ "frame"
39
+ ]);
40
+ function isSupportedElement(element) {
41
+ return SUPPORTED_TYPES.has(element.type) && !element.isDeleted;
42
+ }
43
+ function toCanvasObject(element) {
44
+ if (!isSupportedElement(element)) {
45
+ return void 0;
46
+ }
47
+ return {
48
+ id: element.id,
49
+ type: element.type,
50
+ x: element.x,
51
+ y: element.y,
52
+ width: element.width,
53
+ height: element.height,
54
+ text: element.text,
55
+ points: element.points,
56
+ style: {
57
+ strokeColor: element.strokeColor,
58
+ backgroundColor: element.backgroundColor,
59
+ fillStyle: normalizeFillStyle(element.fillStyle),
60
+ strokeWidth: normalizeStrokeWidth(element.strokeWidth),
61
+ strokeStyle: element.strokeStyle,
62
+ roughness: normalizeRoughness(element.roughness),
63
+ opacity: element.opacity,
64
+ fontSize: element.fontSize,
65
+ textAlign: element.textAlign
66
+ },
67
+ containerId: element.containerId ?? void 0,
68
+ groupIds: element.groupIds,
69
+ frameId: element.frameId,
70
+ raw: element
71
+ };
72
+ }
73
+ function toCanvasObjectSummary(element) {
74
+ const object = toCanvasObject(element);
75
+ if (!object) {
76
+ return void 0;
77
+ }
78
+ return {
79
+ id: object.id,
80
+ type: object.type,
81
+ x: object.x,
82
+ y: object.y,
83
+ width: object.width,
84
+ height: object.height,
85
+ text: object.text
86
+ };
87
+ }
88
+ function normalizeStrokeWidth(width) {
89
+ if (width === 1 || width === 4) {
90
+ return width;
91
+ }
92
+ return 2;
93
+ }
94
+ function normalizeFillStyle(fillStyle) {
95
+ if (fillStyle === "hachure" || fillStyle === "cross-hatch") {
96
+ return fillStyle;
97
+ }
98
+ return "solid";
99
+ }
100
+ function normalizeRoughness(roughness) {
101
+ if (roughness === 0 || roughness === 2) {
102
+ return roughness;
103
+ }
104
+ return 1;
105
+ }
106
+
107
+ // src/plugins/excalidraw/elements.ts
108
+ import { randomUUID } from "crypto";
109
+ var DEFAULT_WIDTH = 160;
110
+ var DEFAULT_HEIGHT = 80;
111
+ var ORDER_KEY_DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
112
+ var elementIndexCounter = 0;
113
+ function buildElement(spec, options = {}) {
114
+ const type = spec.type;
115
+ const width = spec.width ?? defaultWidth(type);
116
+ const height = spec.height ?? defaultHeight(type);
117
+ const points = spec.points ?? defaultPoints(type, width, height);
118
+ const base = {
119
+ id: options.id ?? randomUUID(),
120
+ index: nextElementIndex(),
121
+ type,
122
+ x: spec.x,
123
+ y: spec.y,
124
+ width,
125
+ height,
126
+ angle: 0,
127
+ strokeColor: spec.style?.strokeColor ?? "#1e1e1e",
128
+ backgroundColor: spec.style?.backgroundColor ?? "transparent",
129
+ fillStyle: spec.style?.fillStyle ?? "solid",
130
+ strokeWidth: spec.style?.strokeWidth ?? 2,
131
+ strokeStyle: spec.style?.strokeStyle ?? "solid",
132
+ roughness: spec.style?.roughness ?? 1,
133
+ opacity: spec.style?.opacity ?? 100,
134
+ groupIds: options.groupIds ?? spec.groupIds ?? [],
135
+ frameId: options.frameId ?? null,
136
+ roundness: roundnessFor(type),
137
+ seed: randomInt31(),
138
+ version: 1,
139
+ versionNonce: randomInt31(),
140
+ isDeleted: false,
141
+ boundElements: options.boundElements ?? null,
142
+ updated: Date.now(),
143
+ link: null,
144
+ locked: false
145
+ };
146
+ if (type === "text") {
147
+ return {
148
+ ...base,
149
+ width: spec.width ?? Math.max(40, (options.text ?? spec.text ?? "").length * 10),
150
+ height: spec.height ?? spec.style?.fontSize ?? 24,
151
+ text: options.text ?? spec.text ?? "",
152
+ originalText: options.text ?? spec.text ?? "",
153
+ fontSize: spec.style?.fontSize ?? 20,
154
+ fontFamily: 1,
155
+ textAlign: spec.style?.textAlign ?? "center",
156
+ verticalAlign: "middle",
157
+ containerId: options.containerId ?? spec.containerId ?? null,
158
+ lineHeight: 1.25,
159
+ autoResize: true
160
+ };
161
+ }
162
+ if (type === "line" || type === "arrow") {
163
+ return {
164
+ ...base,
165
+ points,
166
+ width: linearWidth(points),
167
+ height: linearHeight(points),
168
+ lastCommittedPoint: null,
169
+ startBinding: options.startBinding ?? null,
170
+ endBinding: options.endBinding ?? null,
171
+ startArrowhead: null,
172
+ endArrowhead: type === "arrow" ? "arrow" : null
173
+ };
174
+ }
175
+ if (type === "frame") {
176
+ return {
177
+ ...base,
178
+ backgroundColor: spec.style?.backgroundColor ?? "transparent",
179
+ name: options.name ?? spec.text ?? null
180
+ };
181
+ }
182
+ return base;
183
+ }
184
+ function makeBinding(elementId) {
185
+ return { elementId, focus: 0, gap: 0 };
186
+ }
187
+ function addBoundElement(target, bound) {
188
+ const current = target.boundElements ?? [];
189
+ if (!current.some((entry) => entry.id === bound.id)) {
190
+ target.boundElements = [...current, bound];
191
+ touchElement(target);
192
+ }
193
+ }
194
+ function touchElement(element) {
195
+ element.version += 1;
196
+ element.versionNonce = randomInt31();
197
+ element.updated = Date.now();
198
+ }
199
+ function defaultWidth(type) {
200
+ return type === "text" ? 80 : DEFAULT_WIDTH;
201
+ }
202
+ function defaultHeight(type) {
203
+ return type === "text" ? 24 : DEFAULT_HEIGHT;
204
+ }
205
+ function defaultPoints(type, width, height) {
206
+ if (type === "line" || type === "arrow") {
207
+ return [
208
+ [0, 0],
209
+ [width, height]
210
+ ];
211
+ }
212
+ return [];
213
+ }
214
+ function linearWidth(points) {
215
+ const xs = points.map(([x]) => x);
216
+ return Math.max(...xs) - Math.min(...xs);
217
+ }
218
+ function linearHeight(points) {
219
+ const ys = points.map(([, y]) => y);
220
+ return Math.max(...ys) - Math.min(...ys);
221
+ }
222
+ function roundnessFor(type) {
223
+ if (type === "rectangle" || type === "diamond") {
224
+ return { type: 3 };
225
+ }
226
+ return null;
227
+ }
228
+ function randomInt31() {
229
+ return Math.floor(Math.random() * 2147483647);
230
+ }
231
+ function nextElementIndex() {
232
+ return orderKeyForInteger(elementIndexCounter++);
233
+ }
234
+ function orderKeyForInteger(value) {
235
+ let remaining = value;
236
+ for (let width = 1; width <= 26; width += 1) {
237
+ const capacity = ORDER_KEY_DIGITS.length ** width;
238
+ if (remaining < capacity) {
239
+ return `${String.fromCharCode("a".charCodeAt(0) + width - 1)}${encodeOrderDigits(
240
+ remaining,
241
+ width
242
+ )}`;
243
+ }
244
+ remaining -= capacity;
245
+ }
246
+ throw new Error("Element order key range exhausted");
247
+ }
248
+ function encodeOrderDigits(value, width) {
249
+ let encoded = "";
250
+ let remaining = value;
251
+ for (let index = 0; index < width; index += 1) {
252
+ encoded = ORDER_KEY_DIGITS[remaining % ORDER_KEY_DIGITS.length] + encoded;
253
+ remaining = Math.floor(remaining / ORDER_KEY_DIGITS.length);
254
+ }
255
+ return encoded;
256
+ }
257
+
258
+ // src/plugins/excalidraw/format.ts
259
+ function serializeScene(scene) {
260
+ return {
261
+ type: "excalidraw",
262
+ version: 2,
263
+ source: "agentic-canvas",
264
+ elements: scene.elements,
265
+ appState: {
266
+ viewBackgroundColor: scene.appState.viewBackgroundColor
267
+ },
268
+ files: scene.files
269
+ };
270
+ }
271
+ function deserializeScene(raw) {
272
+ const parsed = JSON.parse(raw);
273
+ if (parsed.type !== "excalidraw" || !Array.isArray(parsed.elements)) {
274
+ throw new Error("Invalid .excalidraw file");
275
+ }
276
+ return {
277
+ elements: parsed.elements,
278
+ appState: {
279
+ viewBackgroundColor: parsed.appState?.viewBackgroundColor ?? "#ffffff"
280
+ },
281
+ files: parsed.files ?? {},
282
+ version: 0
283
+ };
284
+ }
285
+
286
+ // src/plugins/excalidraw/geometry.ts
287
+ function centerPoint(element) {
288
+ return {
289
+ x: element.x + element.width / 2,
290
+ y: element.y + element.height / 2
291
+ };
292
+ }
293
+ function edgePoint(element, towardX, towardY) {
294
+ const center = centerPoint(element);
295
+ const dx = towardX - center.x;
296
+ const dy = towardY - center.y;
297
+ if (dx === 0 && dy === 0) {
298
+ return center;
299
+ }
300
+ const halfWidth = element.width / 2;
301
+ const halfHeight = element.height / 2;
302
+ if (halfWidth <= 0 || halfHeight <= 0) {
303
+ return center;
304
+ }
305
+ if (element.type === "ellipse") {
306
+ const scale2 = 1 / Math.sqrt(dx * dx / (halfWidth * halfWidth) + dy * dy / (halfHeight * halfHeight));
307
+ return {
308
+ x: center.x + dx * scale2,
309
+ y: center.y + dy * scale2
310
+ };
311
+ }
312
+ if (element.type === "diamond") {
313
+ const scale2 = 1 / (Math.abs(dx) / halfWidth + Math.abs(dy) / halfHeight);
314
+ return {
315
+ x: center.x + dx * scale2,
316
+ y: center.y + dy * scale2
317
+ };
318
+ }
319
+ const xScale = dx === 0 ? Number.POSITIVE_INFINITY : halfWidth / Math.abs(dx);
320
+ const yScale = dy === 0 ? Number.POSITIVE_INFINITY : halfHeight / Math.abs(dy);
321
+ const scale = Math.min(xScale, yScale);
322
+ return {
323
+ x: center.x + dx * scale,
324
+ y: center.y + dy * scale
325
+ };
326
+ }
327
+
328
+ // src/plugins/excalidraw/tools.ts
329
+ import { z as z2 } from "zod";
330
+
331
+ // src/mcp/schemas.ts
332
+ import { z } from "zod";
333
+ var canvasObjectTypeSchema = z.enum([
334
+ "rectangle",
335
+ "ellipse",
336
+ "diamond",
337
+ "line",
338
+ "arrow",
339
+ "text",
340
+ "frame"
341
+ ]);
342
+ var styleSchema = z.object({
343
+ strokeColor: z.string().optional(),
344
+ backgroundColor: z.string().optional(),
345
+ fillStyle: z.enum(["hachure", "cross-hatch", "solid"]).optional(),
346
+ strokeWidth: z.union([z.literal(1), z.literal(2), z.literal(4)]).optional(),
347
+ strokeStyle: z.enum(["solid", "dashed", "dotted"]).optional(),
348
+ roughness: z.union([z.literal(0), z.literal(1), z.literal(2)]).optional(),
349
+ opacity: z.number().min(0).max(100).optional(),
350
+ fontSize: z.number().positive().optional(),
351
+ textAlign: z.enum(["left", "center", "right"]).optional()
352
+ });
353
+ var endpointSchema = z.union([
354
+ z.object({ elementId: z.string() }),
355
+ z.object({ x: z.number(), y: z.number() })
356
+ ]);
357
+ var pointSchema = z.array(z.number()).length(2);
358
+ var pointsSchema = z.array(pointSchema);
359
+ var createObjectShape = {
360
+ type: canvasObjectTypeSchema,
361
+ x: z.number(),
362
+ y: z.number(),
363
+ width: z.number().optional(),
364
+ height: z.number().optional(),
365
+ text: z.string().optional(),
366
+ points: pointsSchema.optional(),
367
+ style: styleSchema.optional(),
368
+ start: endpointSchema.optional(),
369
+ end: endpointSchema.optional(),
370
+ containerId: z.string().optional(),
371
+ groupIds: z.array(z.string()).optional()
372
+ };
373
+ var updateObjectShape = {
374
+ id: z.string(),
375
+ x: z.number().optional(),
376
+ y: z.number().optional(),
377
+ width: z.number().optional(),
378
+ height: z.number().optional(),
379
+ text: z.string().optional(),
380
+ points: pointsSchema.optional(),
381
+ style: styleSchema.optional(),
382
+ start: endpointSchema.optional(),
383
+ end: endpointSchema.optional(),
384
+ containerId: z.string().optional(),
385
+ groupIds: z.array(z.string()).optional()
386
+ };
387
+
388
+ // src/plugins/excalidraw/flowchart.ts
389
+ function planFlowchart(input) {
390
+ const direction = input.direction ?? "LR";
391
+ const spacingX = input.spacingX ?? 220;
392
+ const spacingY = input.spacingY ?? 140;
393
+ const levels = assignLevels(input);
394
+ const levelGroups = groupLevels(input.nodes, levels);
395
+ return {
396
+ nodes: input.nodes.map((node, index) => {
397
+ const level = levels.get(node.id) ?? index;
398
+ const peers = levelGroups.get(level) ?? [node.id];
399
+ const peerIndex = Math.max(0, peers.indexOf(node.id));
400
+ const crossAxisOffset = peerOffset(
401
+ peerIndex,
402
+ peers.length,
403
+ direction === "LR" ? spacingY : spacingX
404
+ );
405
+ return {
406
+ key: node.id,
407
+ spec: {
408
+ type: node.shape ?? "rectangle",
409
+ x: node.x ?? (direction === "LR" ? level * spacingX : crossAxisOffset),
410
+ y: node.y ?? (direction === "TB" ? level * spacingY : crossAxisOffset),
411
+ width: 160,
412
+ height: 60,
413
+ text: node.label
414
+ }
415
+ };
416
+ }),
417
+ edges: input.edges
418
+ };
419
+ }
420
+ function assignLevels(input) {
421
+ const nodeIds = new Set(input.nodes.map((node) => node.id));
422
+ const indegrees = new Map(input.nodes.map((node) => [node.id, 0]));
423
+ for (const edge of input.edges) {
424
+ if (nodeIds.has(edge.to)) {
425
+ indegrees.set(edge.to, (indegrees.get(edge.to) ?? 0) + 1);
426
+ }
427
+ }
428
+ const roots = input.nodes.filter((node) => (indegrees.get(node.id) ?? 0) === 0);
429
+ const levels = /* @__PURE__ */ new Map();
430
+ for (const root of roots.length > 0 ? roots : input.nodes.slice(0, 1)) {
431
+ levels.set(root.id, 0);
432
+ }
433
+ for (let pass = 0; pass < input.nodes.length; pass += 1) {
434
+ let changed = false;
435
+ for (const edge of input.edges) {
436
+ const fromLevel = levels.get(edge.from);
437
+ if (fromLevel === void 0 || !nodeIds.has(edge.to)) {
438
+ continue;
439
+ }
440
+ const nextLevel = fromLevel + 1;
441
+ if ((levels.get(edge.to) ?? -1) < nextLevel) {
442
+ levels.set(edge.to, nextLevel);
443
+ changed = true;
444
+ }
445
+ }
446
+ if (!changed) {
447
+ break;
448
+ }
449
+ }
450
+ for (const [index, node] of input.nodes.entries()) {
451
+ if (!levels.has(node.id)) {
452
+ levels.set(node.id, index);
453
+ }
454
+ }
455
+ return levels;
456
+ }
457
+ function groupLevels(nodes, levels) {
458
+ const groups = /* @__PURE__ */ new Map();
459
+ for (const node of nodes) {
460
+ const level = levels.get(node.id) ?? 0;
461
+ groups.set(level, [...groups.get(level) ?? [], node.id]);
462
+ }
463
+ return groups;
464
+ }
465
+ function peerOffset(index, count, spacing) {
466
+ return (index - (count - 1) / 2) * spacing;
467
+ }
468
+
469
+ // src/plugins/excalidraw/tools.ts
470
+ var shapeInput = {
471
+ x: z2.number(),
472
+ y: z2.number(),
473
+ width: z2.number(),
474
+ height: z2.number(),
475
+ text: z2.string().optional(),
476
+ style: styleSchema.optional()
477
+ };
478
+ function registerExcalidrawTools(server, context) {
479
+ registerShapeTool(server, context, "draw_rectangle", "rectangle");
480
+ registerShapeTool(server, context, "draw_ellipse", "ellipse");
481
+ registerShapeTool(server, context, "draw_diamond", "diamond");
482
+ server.registerTool(
483
+ "draw_line",
484
+ {
485
+ description: "Draw an Excalidraw polyline.",
486
+ inputSchema: {
487
+ x: z2.number(),
488
+ y: z2.number(),
489
+ points: pointsSchema.min(2),
490
+ style: styleSchema.optional()
491
+ }
492
+ },
493
+ async ({ x, y, points, style }) => {
494
+ const object = context.controller.createObject({
495
+ type: "line",
496
+ x,
497
+ y,
498
+ points,
499
+ style
500
+ });
501
+ return textResult({ id: object.id });
502
+ }
503
+ );
504
+ server.registerTool(
505
+ "draw_arrow",
506
+ {
507
+ description: "Draw an Excalidraw arrow, optionally bound to existing elements.",
508
+ inputSchema: {
509
+ start: endpointSchema,
510
+ end: endpointSchema,
511
+ text: z2.string().optional(),
512
+ style: styleSchema.optional()
513
+ }
514
+ },
515
+ async ({ start, end, text, style }) => {
516
+ try {
517
+ const id = context.controller.transaction(
518
+ () => context.controller.createObject({
519
+ type: "arrow",
520
+ x: "x" in start ? start.x : 0,
521
+ y: "y" in start ? start.y : 0,
522
+ start,
523
+ end,
524
+ text,
525
+ style
526
+ }).id
527
+ );
528
+ return textResult({ id });
529
+ } catch (error) {
530
+ return errorResult(error);
531
+ }
532
+ }
533
+ );
534
+ server.registerTool(
535
+ "add_text",
536
+ {
537
+ description: "Add standalone text or a label bound to a container element.",
538
+ inputSchema: {
539
+ x: z2.number().optional(),
540
+ y: z2.number().optional(),
541
+ text: z2.string(),
542
+ containerId: z2.string().optional(),
543
+ style: styleSchema.optional()
544
+ }
545
+ },
546
+ async ({ x, y, text, containerId, style }) => {
547
+ const container = containerId ? context.controller.getObject(containerId) : void 0;
548
+ if (containerId && !container) {
549
+ return errorResult(`Object not found: ${containerId}`);
550
+ }
551
+ const object = context.controller.createObject({
552
+ type: "text",
553
+ x: x ?? (container ? container.x + 12 : 0),
554
+ y: y ?? (container ? container.y + container.height / 2 - 12 : 0),
555
+ width: container ? Math.max(40, container.width - 24) : void 0,
556
+ text,
557
+ containerId,
558
+ style
559
+ });
560
+ return textResult({ id: object.id });
561
+ }
562
+ );
563
+ server.registerTool(
564
+ "create_frame",
565
+ {
566
+ description: "Create a frame and optionally assign existing elements to it.",
567
+ inputSchema: {
568
+ x: z2.number(),
569
+ y: z2.number(),
570
+ width: z2.number(),
571
+ height: z2.number(),
572
+ name: z2.string().optional(),
573
+ childIds: z2.array(z2.string()).optional()
574
+ }
575
+ },
576
+ async ({ x, y, width, height, name, childIds }) => {
577
+ const result = context.controller.transaction(() => {
578
+ const frame = context.controller.createObject({
579
+ type: "frame",
580
+ x,
581
+ y,
582
+ width,
583
+ height,
584
+ text: name
585
+ });
586
+ const updated = context.controller.mutateScene(
587
+ (scene) => setFrameOnChildren(scene, childIds ?? [], frame.id)
588
+ );
589
+ return { id: frame.id, childIds: updated };
590
+ });
591
+ return textResult(result);
592
+ }
593
+ );
594
+ server.registerTool(
595
+ "group_objects",
596
+ {
597
+ description: "Group existing Excalidraw elements.",
598
+ inputSchema: {
599
+ ids: z2.array(z2.string()).min(1)
600
+ }
601
+ },
602
+ async ({ ids }) => {
603
+ const groupId = context.controller.mutateScene((scene) => groupElements(scene, ids));
604
+ return textResult({ groupId });
605
+ }
606
+ );
607
+ server.registerTool(
608
+ "create_flowchart",
609
+ {
610
+ description: "Create a simple deterministic flowchart.",
611
+ inputSchema: {
612
+ nodes: z2.array(
613
+ z2.object({
614
+ id: z2.string(),
615
+ label: z2.string(),
616
+ shape: z2.enum(["rectangle", "ellipse", "diamond"]).optional(),
617
+ x: z2.number().optional(),
618
+ y: z2.number().optional()
619
+ })
620
+ ).min(1),
621
+ edges: z2.array(
622
+ z2.object({ from: z2.string(), to: z2.string(), label: z2.string().optional() })
623
+ ),
624
+ direction: z2.enum(["TB", "LR"]).optional(),
625
+ spacingX: z2.number().positive().optional(),
626
+ spacingY: z2.number().positive().optional()
627
+ }
628
+ },
629
+ async (input) => {
630
+ try {
631
+ const plan = planFlowchart(input);
632
+ const result = context.controller.transaction(() => {
633
+ const nodeIds = {};
634
+ for (const node of plan.nodes) {
635
+ const object = context.controller.createObject(node.spec);
636
+ nodeIds[node.key] = object.id;
637
+ }
638
+ const arrowIds = [];
639
+ for (const edge of plan.edges) {
640
+ const from = nodeIds[edge.from];
641
+ const to = nodeIds[edge.to];
642
+ if (!from || !to) {
643
+ throw new Error(`Flowchart edge references missing node: ${edge.from} -> ${edge.to}`);
644
+ }
645
+ const arrow = context.controller.createObject({
646
+ type: "arrow",
647
+ x: 0,
648
+ y: 0,
649
+ start: { elementId: from },
650
+ end: { elementId: to },
651
+ text: edge.label
652
+ });
653
+ arrowIds.push(arrow.id);
654
+ }
655
+ return { nodeIds, arrowIds };
656
+ });
657
+ return textResult(result);
658
+ } catch (error) {
659
+ return errorResult(error);
660
+ }
661
+ }
662
+ );
663
+ }
664
+ function registerShapeTool(server, context, name, type) {
665
+ server.registerTool(
666
+ name,
667
+ {
668
+ description: `Draw an Excalidraw ${type}.`,
669
+ inputSchema: shapeInput
670
+ },
671
+ async ({ x, y, width, height, text, style }) => {
672
+ const object = context.controller.createObject({ type, x, y, width, height, text, style });
673
+ return textResult({ id: object.id });
674
+ }
675
+ );
676
+ }
677
+ function textResult(value) {
678
+ return {
679
+ content: [{ type: "text", text: JSON.stringify(value) }]
680
+ };
681
+ }
682
+ function errorResult(error) {
683
+ const message = error instanceof Error ? error.message : String(error);
684
+ return {
685
+ isError: true,
686
+ content: [{ type: "text", text: message }]
687
+ };
688
+ }
689
+
690
+ // src/plugins/excalidraw/index.ts
691
+ function createExcalidrawPlugin() {
692
+ return {
693
+ name: "excalidraw",
694
+ createInitialScene,
695
+ getMetadata,
696
+ listObjects,
697
+ getObject,
698
+ createObject,
699
+ updateObject,
700
+ deleteObjects,
701
+ clear,
702
+ serialize,
703
+ deserialize,
704
+ registerTools
705
+ };
706
+ }
707
+ function createInitialScene() {
708
+ return {
709
+ elements: [],
710
+ appState: {
711
+ viewBackgroundColor: "#ffffff"
712
+ },
713
+ files: {},
714
+ version: 0
715
+ };
716
+ }
717
+ function getMetadata(scene) {
718
+ return {
719
+ canvas: "excalidraw",
720
+ version: scene.version,
721
+ objectCount: listObjects(scene).length,
722
+ viewBackgroundColor: scene.appState.viewBackgroundColor
723
+ };
724
+ }
725
+ function listObjects(scene, type) {
726
+ return scene.elements.map(toCanvasObjectSummary).filter((object) => Boolean(object)).filter((object) => !type || object.type === type);
727
+ }
728
+ function getObject(scene, id) {
729
+ const element = findElement(scene, id);
730
+ return element ? toCanvasObject(element) : void 0;
731
+ }
732
+ function createObject(scene, spec) {
733
+ const element = buildElementWithBindings(scene, spec);
734
+ scene.elements.push(element);
735
+ if (spec.text && canCreateBoundLabel(spec.type)) {
736
+ const label = createBoundLabel(element, spec.text, spec.style);
737
+ scene.elements.push(label);
738
+ }
739
+ const object = toCanvasObject(element);
740
+ if (!object) {
741
+ throw new Error(`Unsupported object type: ${spec.type}`);
742
+ }
743
+ return object;
744
+ }
745
+ function updateObject(scene, id, patch) {
746
+ const element = findElement(scene, id);
747
+ if (!element) {
748
+ return void 0;
749
+ }
750
+ if (patch.x !== void 0) {
751
+ element.x = patch.x;
752
+ }
753
+ if (patch.y !== void 0) {
754
+ element.y = patch.y;
755
+ }
756
+ if (patch.width !== void 0) {
757
+ element.width = patch.width;
758
+ }
759
+ if (patch.height !== void 0) {
760
+ element.height = patch.height;
761
+ }
762
+ if (patch.points !== void 0) {
763
+ element.points = patch.points;
764
+ element.width = linearWidth2(patch.points);
765
+ element.height = linearHeight2(patch.points);
766
+ }
767
+ if (patch.groupIds !== void 0) {
768
+ element.groupIds = patch.groupIds;
769
+ }
770
+ if (patch.containerId !== void 0) {
771
+ element.containerId = patch.containerId;
772
+ }
773
+ if (patch.style) {
774
+ applyStyle(element, patch.style);
775
+ }
776
+ if (patch.text !== void 0) {
777
+ if (element.type === "text") {
778
+ element.text = patch.text;
779
+ element.originalText = patch.text;
780
+ } else {
781
+ upsertContainerLabel(scene, element, patch.text, patch.style);
782
+ }
783
+ }
784
+ touchElement(element);
785
+ return toCanvasObject(element);
786
+ }
787
+ function deleteObjects(scene, ids) {
788
+ const idSet = new Set(ids);
789
+ const before = scene.elements.length;
790
+ scene.elements = scene.elements.filter(
791
+ (element) => !idSet.has(element.id) && !idSet.has(element.containerId ?? "")
792
+ );
793
+ return before === scene.elements.length ? [] : ids.filter((id) => !findElement(scene, id));
794
+ }
795
+ function clear(scene) {
796
+ scene.elements = [];
797
+ scene.files = {};
798
+ }
799
+ function serialize(scene) {
800
+ return serializeScene(scene);
801
+ }
802
+ function deserialize(raw) {
803
+ return deserializeScene(raw);
804
+ }
805
+ function registerTools(server, context) {
806
+ registerExcalidrawTools(server, context);
807
+ }
808
+ function buildElementWithBindings(scene, spec) {
809
+ if (spec.type !== "arrow" && spec.type !== "line") {
810
+ return buildElement(spec);
811
+ }
812
+ const start = spec.start ? resolveEndpoint(scene, spec.start) : void 0;
813
+ const end = spec.end ? resolveEndpoint(scene, spec.end) : void 0;
814
+ const defaultStart = { x: spec.x, y: spec.y };
815
+ const defaultEnd = {
816
+ x: spec.x + (spec.width ?? 160),
817
+ y: spec.y + (spec.height ?? 80)
818
+ };
819
+ const startCenter = start?.point ?? defaultStart;
820
+ const endCenter = end?.point ?? defaultEnd;
821
+ const startPoint = start?.element ? edgePoint(start.element, endCenter.x, endCenter.y) : startCenter;
822
+ const endPoint = end?.element ? edgePoint(end.element, startCenter.x, startCenter.y) : endCenter;
823
+ const points = spec.points ?? [
824
+ [0, 0],
825
+ [endPoint.x - startPoint.x, endPoint.y - startPoint.y]
826
+ ];
827
+ const element = buildElement(
828
+ {
829
+ ...spec,
830
+ x: startPoint.x,
831
+ y: startPoint.y,
832
+ points
833
+ },
834
+ {
835
+ startBinding: start?.element ? makeBinding(start.element.id) : null,
836
+ endBinding: end?.element ? makeBinding(end.element.id) : null
837
+ }
838
+ );
839
+ if (spec.type === "arrow") {
840
+ if (start?.element) {
841
+ addBoundElementById(scene, start.element.id, { id: element.id, type: "arrow" });
842
+ }
843
+ if (end?.element) {
844
+ addBoundElementById(scene, end.element.id, { id: element.id, type: "arrow" });
845
+ }
846
+ }
847
+ return element;
848
+ }
849
+ function resolveEndpoint(scene, endpoint) {
850
+ if (isElementEndpoint(endpoint)) {
851
+ const element = findElement(scene, endpoint.elementId);
852
+ if (!element) {
853
+ throw new Error(`Object not found: ${endpoint.elementId}`);
854
+ }
855
+ return {
856
+ point: centerPoint(element),
857
+ element
858
+ };
859
+ }
860
+ return { point: endpoint };
861
+ }
862
+ function upsertContainerLabel(scene, container, text, style) {
863
+ const existing = scene.elements.find(
864
+ (element) => element.type === "text" && element.containerId === container.id
865
+ );
866
+ if (existing) {
867
+ existing.text = text;
868
+ existing.originalText = text;
869
+ if (style) {
870
+ applyStyle(existing, style);
871
+ }
872
+ touchElement(existing);
873
+ return;
874
+ }
875
+ scene.elements.push(createBoundLabel(container, text, style));
876
+ }
877
+ function createBoundLabel(container, text, style) {
878
+ const labelStyle = { ...style, textAlign: style?.textAlign ?? "center" };
879
+ const position = labelPosition(container, text, labelStyle);
880
+ const label = buildElement(
881
+ {
882
+ type: "text",
883
+ x: position.x,
884
+ y: position.y,
885
+ width: position.width,
886
+ height: position.height,
887
+ text,
888
+ style: labelStyle
889
+ },
890
+ {
891
+ containerId: container.id
892
+ }
893
+ );
894
+ label.autoResize = false;
895
+ addBoundElement(container, { id: label.id, type: "text" });
896
+ return label;
897
+ }
898
+ function labelPosition(container, text, style) {
899
+ const fontSize = style.fontSize ?? 20;
900
+ const lineHeight = fontSize * 1.25;
901
+ if (isLinearElement(container)) {
902
+ const midpoint = linearMidpoint(container);
903
+ const width = Math.max(40, text.length * fontSize * 0.6);
904
+ const height = lineHeight;
905
+ return {
906
+ x: midpoint.x - width / 2,
907
+ y: midpoint.y - height / 2,
908
+ width,
909
+ height
910
+ };
911
+ }
912
+ return {
913
+ x: container.x,
914
+ y: container.y + container.height / 2 - lineHeight / 2,
915
+ width: Math.max(40, container.width),
916
+ height: lineHeight
917
+ };
918
+ }
919
+ function isLinearElement(element) {
920
+ return element.type === "line" || element.type === "arrow";
921
+ }
922
+ function canCreateBoundLabel(type) {
923
+ return type !== "text" && type !== "frame" && type !== "line";
924
+ }
925
+ function linearMidpoint(element) {
926
+ const points = element.points ?? [
927
+ [0, 0],
928
+ [element.width, element.height]
929
+ ];
930
+ const first = points[0] ?? [0, 0];
931
+ const last = points[points.length - 1] ?? first;
932
+ return {
933
+ x: element.x + (first[0] + last[0]) / 2,
934
+ y: element.y + (first[1] + last[1]) / 2
935
+ };
936
+ }
937
+ function addBoundElementById(scene, elementId, bound) {
938
+ const element = findElement(scene, elementId);
939
+ if (element) {
940
+ addBoundElement(element, bound);
941
+ }
942
+ }
943
+ function findElement(scene, id) {
944
+ return scene.elements.find((element) => element.id === id && !element.isDeleted);
945
+ }
946
+ function setFrameOnChildren(scene, childIds, frameId) {
947
+ const updated = [];
948
+ for (const childId of childIds) {
949
+ const child = findElement(scene, childId);
950
+ if (child) {
951
+ child.frameId = frameId;
952
+ touchElement(child);
953
+ updated.push(child.id);
954
+ }
955
+ }
956
+ return updated;
957
+ }
958
+ function groupElements(scene, ids) {
959
+ const groupId = randomUUID2();
960
+ for (const id of ids) {
961
+ const element = findElement(scene, id);
962
+ if (element && !element.groupIds.includes(groupId)) {
963
+ element.groupIds = [...element.groupIds, groupId];
964
+ touchElement(element);
965
+ }
966
+ }
967
+ return groupId;
968
+ }
969
+ function applyStyle(element, style) {
970
+ if (style.strokeColor !== void 0) {
971
+ element.strokeColor = style.strokeColor;
972
+ }
973
+ if (style.backgroundColor !== void 0) {
974
+ element.backgroundColor = style.backgroundColor;
975
+ }
976
+ if (style.fillStyle !== void 0) {
977
+ element.fillStyle = style.fillStyle;
978
+ }
979
+ if (style.strokeWidth !== void 0) {
980
+ element.strokeWidth = style.strokeWidth;
981
+ }
982
+ if (style.strokeStyle !== void 0) {
983
+ element.strokeStyle = style.strokeStyle;
984
+ }
985
+ if (style.roughness !== void 0) {
986
+ element.roughness = style.roughness;
987
+ }
988
+ if (style.opacity !== void 0) {
989
+ element.opacity = style.opacity;
990
+ }
991
+ if (style.fontSize !== void 0) {
992
+ element.fontSize = style.fontSize;
993
+ element.height = style.fontSize * 1.25;
994
+ }
995
+ if (style.textAlign !== void 0) {
996
+ element.textAlign = style.textAlign;
997
+ }
998
+ }
999
+ function linearWidth2(points) {
1000
+ const xs = points.map(([x]) => x);
1001
+ return Math.max(...xs) - Math.min(...xs);
1002
+ }
1003
+ function linearHeight2(points) {
1004
+ const ys = points.map(([, y]) => y);
1005
+ return Math.max(...ys) - Math.min(...ys);
1006
+ }
1007
+
1008
+ // src/server/app.ts
1009
+ import path2 from "path";
1010
+ import express from "express";
1011
+
1012
+ // src/mcp/buildServer.ts
1013
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1014
+
1015
+ // src/shared/packageInfo.ts
1016
+ import { readFileSync } from "fs";
1017
+ import path from "path";
1018
+ import { fileURLToPath } from "url";
1019
+ var CLI_NAME = "agentic-canvas";
1020
+ var MCP_SERVER_NAME = "agentic-canvas";
1021
+ var PACKAGE_NAME = "@trohde/agentic-canvas";
1022
+ function readPackageInfo() {
1023
+ const packageJsonPath = path.resolve(
1024
+ path.dirname(fileURLToPath(import.meta.url)),
1025
+ "../../package.json"
1026
+ );
1027
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
1028
+ return {
1029
+ name: typeof parsed.name === "string" ? parsed.name : PACKAGE_NAME,
1030
+ version: typeof parsed.version === "string" ? parsed.version : "0.0.0"
1031
+ };
1032
+ }
1033
+
1034
+ // src/mcp/baselineTools.ts
1035
+ import { z as z3 } from "zod";
1036
+ function registerBaselineTools(server, context) {
1037
+ server.registerTool(
1038
+ "get_canvas_state",
1039
+ {
1040
+ description: "Get canvas metadata and current scene state summary.",
1041
+ inputSchema: {}
1042
+ },
1043
+ async () => textResult2(context.controller.getMetadata(context.clientsConnected()))
1044
+ );
1045
+ server.registerTool(
1046
+ "list_objects",
1047
+ {
1048
+ description: "List normalized canvas objects.",
1049
+ inputSchema: {
1050
+ type: canvasObjectTypeSchema.optional()
1051
+ }
1052
+ },
1053
+ async ({ type }) => textResult2(context.controller.listObjects(type))
1054
+ );
1055
+ server.registerTool(
1056
+ "get_object",
1057
+ {
1058
+ description: "Get one normalized canvas object by id.",
1059
+ inputSchema: {
1060
+ id: z3.string()
1061
+ }
1062
+ },
1063
+ async ({ id }) => {
1064
+ const object = context.controller.getObject(id);
1065
+ return object ? textResult2(object) : errorResult2(`Object not found: ${id}`);
1066
+ }
1067
+ );
1068
+ server.registerTool(
1069
+ "create_object",
1070
+ {
1071
+ description: "Create a normalized canvas object.",
1072
+ inputSchema: createObjectShape
1073
+ },
1074
+ async (input) => {
1075
+ try {
1076
+ const object = context.controller.createObject(input);
1077
+ return textResult2({ id: object.id });
1078
+ } catch (error) {
1079
+ return errorResult2(error);
1080
+ }
1081
+ }
1082
+ );
1083
+ server.registerTool(
1084
+ "update_object",
1085
+ {
1086
+ description: "Patch a normalized canvas object.",
1087
+ inputSchema: updateObjectShape
1088
+ },
1089
+ async ({ id, ...patch }) => {
1090
+ const object = context.controller.updateObject(id, patch);
1091
+ return object ? textResult2({ id: object.id }) : errorResult2(`Object not found: ${id}`);
1092
+ }
1093
+ );
1094
+ server.registerTool(
1095
+ "delete_object",
1096
+ {
1097
+ description: "Delete one or more canvas objects.",
1098
+ inputSchema: {
1099
+ ids: z3.array(z3.string()).min(1)
1100
+ }
1101
+ },
1102
+ async ({ ids }) => textResult2({ deleted: context.controller.deleteObjects(ids) })
1103
+ );
1104
+ server.registerTool(
1105
+ "clear_canvas",
1106
+ {
1107
+ description: "Clear all canvas objects.",
1108
+ inputSchema: {}
1109
+ },
1110
+ async () => {
1111
+ context.controller.clear();
1112
+ return textResult2({ cleared: true });
1113
+ }
1114
+ );
1115
+ server.registerTool(
1116
+ "save_canvas",
1117
+ {
1118
+ description: "Save the current canvas to a .excalidraw file inside the workspace.",
1119
+ inputSchema: {
1120
+ path: z3.string().optional()
1121
+ }
1122
+ },
1123
+ async ({ path: path5 }) => {
1124
+ try {
1125
+ const written = await context.workspace.writeText(
1126
+ path5 ?? "canvas.excalidraw",
1127
+ context.controller.serialize()
1128
+ );
1129
+ return textResult2({ path: written });
1130
+ } catch (error) {
1131
+ return errorResult2(error);
1132
+ }
1133
+ }
1134
+ );
1135
+ server.registerTool(
1136
+ "open_canvas",
1137
+ {
1138
+ description: "Open a .excalidraw file from inside the workspace.",
1139
+ inputSchema: {
1140
+ path: z3.string()
1141
+ }
1142
+ },
1143
+ async ({ path: path5 }) => {
1144
+ try {
1145
+ const file = await context.workspace.readText(path5);
1146
+ context.controller.deserialize(file.text);
1147
+ return textResult2({
1148
+ path: file.path,
1149
+ objectCount: context.controller.listObjects().length
1150
+ });
1151
+ } catch (error) {
1152
+ return errorResult2(friendlyOpenError(error, path5));
1153
+ }
1154
+ }
1155
+ );
1156
+ server.registerTool(
1157
+ "screenshot",
1158
+ {
1159
+ description: "Export a PNG screenshot through a connected browser.",
1160
+ inputSchema: {
1161
+ path: z3.string().optional(),
1162
+ exportPadding: z3.number().min(0).max(200).optional()
1163
+ }
1164
+ },
1165
+ async ({ path: path5, exportPadding }) => {
1166
+ try {
1167
+ const exported = await context.requestExport({ exportPadding });
1168
+ const content = [{ type: "image", data: exported.base64, mimeType: exported.mimeType }];
1169
+ if (path5) {
1170
+ const written = await context.workspace.writeBinary(
1171
+ path5,
1172
+ Buffer.from(exported.base64, "base64")
1173
+ );
1174
+ content.push({ type: "text", text: JSON.stringify({ path: written }) });
1175
+ }
1176
+ return { content };
1177
+ } catch (error) {
1178
+ return errorResult2(error);
1179
+ }
1180
+ }
1181
+ );
1182
+ server.registerTool(
1183
+ "get_selected_objects",
1184
+ {
1185
+ description: "Return normalized objects currently selected in the connected browser.",
1186
+ inputSchema: {}
1187
+ },
1188
+ async () => {
1189
+ try {
1190
+ const selection = await context.requestSelection();
1191
+ const objects = [];
1192
+ const missingIds = [];
1193
+ for (const id of selection.selectedIds) {
1194
+ const object = context.controller.getObject(id);
1195
+ if (object) {
1196
+ objects.push(object);
1197
+ } else {
1198
+ missingIds.push(id);
1199
+ }
1200
+ }
1201
+ return textResult2({
1202
+ version: context.controller.currentVersion(),
1203
+ selectedIds: selection.selectedIds,
1204
+ objects,
1205
+ missingIds
1206
+ });
1207
+ } catch (error) {
1208
+ return errorResult2(error);
1209
+ }
1210
+ }
1211
+ );
1212
+ }
1213
+ function textResult2(value) {
1214
+ return {
1215
+ content: [{ type: "text", text: JSON.stringify(value) }]
1216
+ };
1217
+ }
1218
+ function errorResult2(error) {
1219
+ const message = error instanceof Error ? error.message : String(error);
1220
+ return {
1221
+ isError: true,
1222
+ content: [{ type: "text", text: message }]
1223
+ };
1224
+ }
1225
+ function friendlyOpenError(error, requestedPath) {
1226
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
1227
+ return `No such canvas file: ${requestedPath}`;
1228
+ }
1229
+ return error;
1230
+ }
1231
+
1232
+ // src/mcp/buildServer.ts
1233
+ function buildMcpServer(options) {
1234
+ const packageInfo = readPackageInfo();
1235
+ const server = new McpServer({
1236
+ name: MCP_SERVER_NAME,
1237
+ version: packageInfo.version
1238
+ });
1239
+ registerBaselineTools(server, {
1240
+ controller: options.controller,
1241
+ workspace: options.workspace,
1242
+ clientsConnected: options.clientsConnected,
1243
+ requestExport: options.requestExport,
1244
+ requestSelection: options.requestSelection
1245
+ });
1246
+ options.plugin.registerTools(server, {
1247
+ controller: options.controller
1248
+ });
1249
+ return server;
1250
+ }
1251
+
1252
+ // src/server/mcpHttp.ts
1253
+ import { randomUUID as randomUUID3 } from "crypto";
1254
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
1255
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
1256
+ function mountMcpHttp(router, createServer2, allowedHosts) {
1257
+ const transports = /* @__PURE__ */ new Map();
1258
+ router.post("/mcp", async (req, res) => {
1259
+ try {
1260
+ const sessionId = headerValue(req, "mcp-session-id");
1261
+ let transport = sessionId ? transports.get(sessionId) : void 0;
1262
+ if (!transport) {
1263
+ if (!isInitialize(req.body)) {
1264
+ res.status(400).json({ error: "Missing or invalid MCP session" });
1265
+ return;
1266
+ }
1267
+ transport = new StreamableHTTPServerTransport({
1268
+ sessionIdGenerator: () => randomUUID3(),
1269
+ enableDnsRebindingProtection: true,
1270
+ allowedHosts,
1271
+ onsessioninitialized: (newSessionId) => {
1272
+ transports.set(newSessionId, transport);
1273
+ }
1274
+ });
1275
+ transport.onclose = () => {
1276
+ const currentSessionId = transport?.sessionId;
1277
+ if (currentSessionId) {
1278
+ transports.delete(currentSessionId);
1279
+ }
1280
+ };
1281
+ await createServer2().connect(transport);
1282
+ }
1283
+ await transport.handleRequest(req, res, req.body);
1284
+ } catch (error) {
1285
+ sendTransportError(res, error);
1286
+ }
1287
+ });
1288
+ router.get("/mcp", async (req, res) => {
1289
+ await handleSessionRequest(req, res, transports);
1290
+ });
1291
+ router.delete("/mcp", async (req, res) => {
1292
+ await handleSessionRequest(req, res, transports);
1293
+ });
1294
+ }
1295
+ async function handleSessionRequest(req, res, transports) {
1296
+ const sessionId = headerValue(req, "mcp-session-id");
1297
+ const transport = sessionId ? transports.get(sessionId) : void 0;
1298
+ if (!transport) {
1299
+ res.status(400).json({ error: "Missing or invalid MCP session" });
1300
+ return;
1301
+ }
1302
+ try {
1303
+ await transport.handleRequest(req, res);
1304
+ } catch (error) {
1305
+ sendTransportError(res, error);
1306
+ }
1307
+ }
1308
+ function isInitialize(body) {
1309
+ const messages = Array.isArray(body) ? body : [body];
1310
+ return messages.some((message) => isInitializeRequest(message));
1311
+ }
1312
+ function headerValue(req, name) {
1313
+ const value = req.headers[name];
1314
+ return Array.isArray(value) ? value[0] : value;
1315
+ }
1316
+ function sendTransportError(res, error) {
1317
+ if (res.headersSent) {
1318
+ return;
1319
+ }
1320
+ const message = error instanceof Error ? error.message : String(error);
1321
+ res.status(500).json({ error: message });
1322
+ }
1323
+
1324
+ // src/server/app.ts
1325
+ function createApp(options) {
1326
+ const app = express();
1327
+ app.use(express.json({ limit: "10mb" }));
1328
+ app.get("/healthz", (_req, res) => {
1329
+ res.json({
1330
+ status: "ok",
1331
+ canvas: options.plugin.name,
1332
+ version: options.controller.getSnapshot().version
1333
+ });
1334
+ });
1335
+ mountMcpHttp(app, () => buildMcpServer(options), options.allowedHosts);
1336
+ app.use(express.static(options.webDistDir));
1337
+ app.get("*", (_req, res) => {
1338
+ res.sendFile(path2.join(options.webDistDir, "index.html"), (error) => {
1339
+ if (error) {
1340
+ res.status(404).send("Agentic Canvas web build not found. Run npm run build first.");
1341
+ }
1342
+ });
1343
+ });
1344
+ return app;
1345
+ }
1346
+
1347
+ // src/server/canvasController.ts
1348
+ var CanvasController = class {
1349
+ constructor(plugin) {
1350
+ this.plugin = plugin;
1351
+ this.scene = plugin.createInitialScene();
1352
+ }
1353
+ plugin;
1354
+ scene;
1355
+ listener;
1356
+ txDepth = 0;
1357
+ txDirty = false;
1358
+ txOrigin;
1359
+ get canvasName() {
1360
+ return this.plugin.name;
1361
+ }
1362
+ setChangeListener(listener) {
1363
+ this.listener = listener;
1364
+ }
1365
+ getScene() {
1366
+ return cloneScene(this.scene);
1367
+ }
1368
+ getSnapshot() {
1369
+ return {
1370
+ version: this.scene.version,
1371
+ elements: this.scene.elements,
1372
+ appState: this.scene.appState,
1373
+ files: this.scene.files
1374
+ };
1375
+ }
1376
+ currentVersion() {
1377
+ return this.scene.version;
1378
+ }
1379
+ getMetadata(clientsConnected = 0) {
1380
+ return {
1381
+ ...this.plugin.getMetadata(this.scene),
1382
+ clientsConnected
1383
+ };
1384
+ }
1385
+ listObjects(type) {
1386
+ return this.plugin.listObjects(this.scene, type);
1387
+ }
1388
+ getObject(id) {
1389
+ return this.plugin.getObject(this.scene, id);
1390
+ }
1391
+ createObject(spec) {
1392
+ return this.mutateScene((scene) => this.plugin.createObject(scene, spec));
1393
+ }
1394
+ updateObject(id, patch) {
1395
+ return this.mutateScene((scene) => this.plugin.updateObject(scene, id, patch));
1396
+ }
1397
+ deleteObjects(ids) {
1398
+ return this.mutateScene((scene) => this.plugin.deleteObjects(scene, ids));
1399
+ }
1400
+ clear() {
1401
+ this.mutateScene((scene) => this.plugin.clear(scene));
1402
+ }
1403
+ serialize() {
1404
+ return JSON.stringify(this.plugin.serialize(this.scene), null, 2);
1405
+ }
1406
+ deserialize(raw) {
1407
+ const currentVersion = this.scene.version;
1408
+ this.scene = this.plugin.deserialize(raw);
1409
+ this.scene.version = currentVersion;
1410
+ this.bumpAndNotify();
1411
+ }
1412
+ replaceFromBrowser(elements, appState, files, origin) {
1413
+ this.scene.elements = elements;
1414
+ this.scene.appState = {
1415
+ viewBackgroundColor: appState?.viewBackgroundColor ?? this.scene.appState.viewBackgroundColor
1416
+ };
1417
+ this.scene.files = files ?? this.scene.files;
1418
+ this.bumpAndNotify(origin);
1419
+ }
1420
+ mutateScene(mutator, origin) {
1421
+ const result = mutator(this.scene);
1422
+ this.bumpAndNotify(origin);
1423
+ return result;
1424
+ }
1425
+ transaction(fn, origin) {
1426
+ this.txDepth += 1;
1427
+ try {
1428
+ return fn();
1429
+ } finally {
1430
+ this.txDepth -= 1;
1431
+ if (this.txDepth === 0 && this.txDirty) {
1432
+ const txOrigin = this.txOrigin;
1433
+ this.txDirty = false;
1434
+ this.txOrigin = void 0;
1435
+ this.commit(txOrigin ?? origin);
1436
+ }
1437
+ }
1438
+ }
1439
+ bumpAndNotify(origin) {
1440
+ if (this.txDepth > 0) {
1441
+ this.txDirty = true;
1442
+ this.txOrigin = origin ?? this.txOrigin;
1443
+ return;
1444
+ }
1445
+ this.commit(origin);
1446
+ }
1447
+ commit(origin) {
1448
+ this.scene.version += 1;
1449
+ this.listener?.(this.getSnapshot(), origin);
1450
+ }
1451
+ };
1452
+
1453
+ // src/server/workspace.ts
1454
+ import { mkdir, readFile, writeFile } from "fs/promises";
1455
+ import path3 from "path";
1456
+ var Workspace = class {
1457
+ root;
1458
+ constructor(root) {
1459
+ this.root = path3.resolve(root);
1460
+ }
1461
+ async ensure() {
1462
+ await mkdir(this.root, { recursive: true });
1463
+ }
1464
+ resolveInWorkspace(userPath) {
1465
+ if (!userPath || userPath.trim().length === 0) {
1466
+ throw new Error("Path is required");
1467
+ }
1468
+ const resolved = path3.isAbsolute(userPath) ? path3.resolve(userPath) : path3.resolve(this.root, userPath);
1469
+ const relative = path3.relative(this.root, resolved);
1470
+ const staysInside = relative === "" || !relative.startsWith("..") && !path3.isAbsolute(relative);
1471
+ if (!staysInside) {
1472
+ throw new Error(`Path is outside workspace: ${userPath}`);
1473
+ }
1474
+ return resolved;
1475
+ }
1476
+ async readText(userPath) {
1477
+ const resolved = this.resolveInWorkspace(userPath);
1478
+ return {
1479
+ path: resolved,
1480
+ text: await readFile(resolved, "utf8")
1481
+ };
1482
+ }
1483
+ async writeText(userPath, text) {
1484
+ const resolved = this.resolveInWorkspace(userPath);
1485
+ await mkdir(path3.dirname(resolved), { recursive: true });
1486
+ await writeFile(resolved, text, "utf8");
1487
+ return resolved;
1488
+ }
1489
+ async writeBinary(userPath, data) {
1490
+ const resolved = this.resolveInWorkspace(userPath);
1491
+ await mkdir(path3.dirname(resolved), { recursive: true });
1492
+ await writeFile(resolved, data);
1493
+ return resolved;
1494
+ }
1495
+ };
1496
+
1497
+ // src/server/wsBridge.ts
1498
+ import { randomUUID as randomUUID4 } from "crypto";
1499
+ import { WebSocket, WebSocketServer } from "ws";
1500
+
1501
+ // src/shared/protocol.ts
1502
+ function parseBrowserMessage(data) {
1503
+ try {
1504
+ const parsed = JSON.parse(data);
1505
+ if (typeof parsed === "object" && parsed !== null && "type" in parsed) {
1506
+ return parsed;
1507
+ }
1508
+ } catch {
1509
+ return void 0;
1510
+ }
1511
+ return void 0;
1512
+ }
1513
+
1514
+ // src/server/wsBridge.ts
1515
+ var WsBridge = class {
1516
+ constructor(controller) {
1517
+ this.controller = controller;
1518
+ }
1519
+ controller;
1520
+ clients = /* @__PURE__ */ new Map();
1521
+ pendingExports = /* @__PURE__ */ new Map();
1522
+ pendingSelections = /* @__PURE__ */ new Map();
1523
+ syncOrder = 0;
1524
+ wss;
1525
+ attach(server, path5 = "/ws") {
1526
+ this.wss = new WebSocketServer({ noServer: true });
1527
+ this.wss.on("connection", (socket) => this.handleConnection(socket));
1528
+ server.on("upgrade", (request, socket, head) => {
1529
+ const url = new URL(request.url ?? "/", "http://127.0.0.1");
1530
+ if (url.pathname !== path5 || !this.wss) {
1531
+ return;
1532
+ }
1533
+ this.wss.handleUpgrade(request, socket, head, (ws) => {
1534
+ this.wss?.emit("connection", ws, request);
1535
+ });
1536
+ });
1537
+ }
1538
+ connectedClientCount() {
1539
+ return this.clients.size;
1540
+ }
1541
+ broadcastScene(snapshot, origin) {
1542
+ for (const { socket } of this.clients.values()) {
1543
+ if (socket !== origin) {
1544
+ this.sendScene(socket, snapshot);
1545
+ }
1546
+ }
1547
+ if (origin instanceof WebSocket) {
1548
+ this.markClientSynced(origin, snapshot.version);
1549
+ }
1550
+ }
1551
+ requestExport(options = {}) {
1552
+ const client = this.mostRecentlySyncedClient();
1553
+ if (!client) {
1554
+ return Promise.reject(new Error("No browser canvas client is connected"));
1555
+ }
1556
+ const id = randomUUID4();
1557
+ const request = {
1558
+ type: "export:request",
1559
+ id,
1560
+ mimeType: "image/png",
1561
+ exportPadding: options.exportPadding
1562
+ };
1563
+ return new Promise((resolve, reject) => {
1564
+ const timer = setTimeout(() => {
1565
+ this.pendingExports.delete(id);
1566
+ reject(new Error("Screenshot export timed out"));
1567
+ }, options.timeoutMs ?? 5e3);
1568
+ this.pendingExports.set(id, { resolve, reject, timer });
1569
+ client.socket.send(JSON.stringify(request));
1570
+ });
1571
+ }
1572
+ requestSelection(options = {}) {
1573
+ const client = this.mostRecentlySyncedClient();
1574
+ if (!client) {
1575
+ return Promise.reject(new Error("No browser canvas client is connected"));
1576
+ }
1577
+ const id = randomUUID4();
1578
+ const request = {
1579
+ type: "selection:request",
1580
+ id
1581
+ };
1582
+ return new Promise((resolve, reject) => {
1583
+ const timer = setTimeout(() => {
1584
+ this.pendingSelections.delete(id);
1585
+ reject(new Error("Selection request timed out"));
1586
+ }, options.timeoutMs ?? 5e3);
1587
+ this.pendingSelections.set(id, { resolve, reject, timer });
1588
+ client.socket.send(JSON.stringify(request));
1589
+ });
1590
+ }
1591
+ close() {
1592
+ for (const { socket } of this.clients.values()) {
1593
+ socket.close();
1594
+ }
1595
+ this.wss?.close();
1596
+ }
1597
+ handleConnection(socket) {
1598
+ this.clients.set(socket, {
1599
+ socket,
1600
+ lastSyncedOrder: 0,
1601
+ lastSyncedVersion: -1
1602
+ });
1603
+ socket.on("message", (data) => this.handleMessage(socket, data.toString()));
1604
+ socket.on("close", () => {
1605
+ this.clients.delete(socket);
1606
+ });
1607
+ this.sendScene(socket, this.controller.getSnapshot());
1608
+ }
1609
+ handleMessage(socket, data) {
1610
+ const message = parseBrowserMessage(data);
1611
+ if (!message) {
1612
+ return;
1613
+ }
1614
+ if (message.type === "hello") {
1615
+ this.sendScene(socket, this.controller.getSnapshot());
1616
+ return;
1617
+ }
1618
+ if (message.type === "scene:changed") {
1619
+ if (message.baseVersion < this.controller.currentVersion()) {
1620
+ this.sendScene(socket, this.controller.getSnapshot());
1621
+ return;
1622
+ }
1623
+ this.controller.replaceFromBrowser(
1624
+ message.elements,
1625
+ message.appState,
1626
+ message.files,
1627
+ socket
1628
+ );
1629
+ this.markClientSynced(socket, this.controller.currentVersion());
1630
+ return;
1631
+ }
1632
+ if (message.type === "export:result" || message.type === "export:error") {
1633
+ const pending = this.pendingExports.get(message.id);
1634
+ if (!pending) {
1635
+ return;
1636
+ }
1637
+ clearTimeout(pending.timer);
1638
+ this.pendingExports.delete(message.id);
1639
+ if (message.type === "export:result") {
1640
+ pending.resolve({ mimeType: message.mimeType, base64: message.base64 });
1641
+ } else {
1642
+ pending.reject(new Error(message.message));
1643
+ }
1644
+ return;
1645
+ }
1646
+ if (message.type === "selection:result" || message.type === "selection:error") {
1647
+ const pending = this.pendingSelections.get(message.id);
1648
+ if (!pending) {
1649
+ return;
1650
+ }
1651
+ clearTimeout(pending.timer);
1652
+ this.pendingSelections.delete(message.id);
1653
+ if (message.type === "selection:result") {
1654
+ pending.resolve({ selectedIds: message.selectedIds });
1655
+ } else {
1656
+ pending.reject(new Error(message.message));
1657
+ }
1658
+ }
1659
+ }
1660
+ sendScene(socket, snapshot) {
1661
+ if (socket.readyState !== WebSocket.OPEN) {
1662
+ return;
1663
+ }
1664
+ socket.send(
1665
+ JSON.stringify({
1666
+ type: "scene:set",
1667
+ version: snapshot.version,
1668
+ elements: snapshot.elements,
1669
+ appState: snapshot.appState,
1670
+ files: snapshot.files
1671
+ })
1672
+ );
1673
+ this.markClientSynced(socket, snapshot.version);
1674
+ }
1675
+ markClientSynced(socket, version) {
1676
+ const client = this.clients.get(socket);
1677
+ if (!client) {
1678
+ return;
1679
+ }
1680
+ client.lastSyncedOrder = ++this.syncOrder;
1681
+ client.lastSyncedVersion = version;
1682
+ }
1683
+ mostRecentlySyncedClient() {
1684
+ return [...this.clients.values()].filter((candidate) => candidate.socket.readyState === WebSocket.OPEN).sort((left, right) => right.lastSyncedOrder - left.lastSyncedOrder)[0];
1685
+ }
1686
+ };
1687
+
1688
+ // src/server/httpServer.ts
1689
+ async function startHttpServer(options) {
1690
+ const port = await findFreePort(options.host, options.port);
1691
+ const workspace = new Workspace(options.workspace);
1692
+ await workspace.ensure();
1693
+ const plugin = createExcalidrawPlugin();
1694
+ const controller = new CanvasController(plugin);
1695
+ const bridge = new WsBridge(controller);
1696
+ controller.setChangeListener((snapshot, origin) => bridge.broadcastScene(snapshot, origin));
1697
+ const app = createApp({
1698
+ plugin,
1699
+ controller,
1700
+ workspace,
1701
+ webDistDir: findWebDistDir(),
1702
+ allowedHosts: allowedHostsFor(options.host, port),
1703
+ clientsConnected: () => bridge.connectedClientCount(),
1704
+ requestExport: (exportOptions) => bridge.requestExport(exportOptions),
1705
+ requestSelection: (selectionOptions) => bridge.requestSelection(selectionOptions)
1706
+ });
1707
+ const server = createServer(app);
1708
+ bridge.attach(server);
1709
+ await new Promise((resolve, reject) => {
1710
+ server.once("error", reject);
1711
+ server.listen(port, options.host, () => {
1712
+ server.off("error", reject);
1713
+ resolve();
1714
+ });
1715
+ });
1716
+ return {
1717
+ host: options.host,
1718
+ port,
1719
+ canvasUrl: `http://${options.host}:${port}`,
1720
+ mcpUrl: `http://${options.host}:${port}/mcp`,
1721
+ server,
1722
+ controller,
1723
+ bridge,
1724
+ close: () => new Promise((resolve, reject) => {
1725
+ bridge.close();
1726
+ server.close((error) => {
1727
+ if (error) {
1728
+ reject(error);
1729
+ } else {
1730
+ resolve();
1731
+ }
1732
+ });
1733
+ })
1734
+ };
1735
+ }
1736
+ async function findFreePort(host, requestedPort) {
1737
+ for (let port = requestedPort; port < requestedPort + 20; port += 1) {
1738
+ if (await canListen(host, port)) {
1739
+ return port;
1740
+ }
1741
+ }
1742
+ throw new Error(`No free port found starting at ${requestedPort}`);
1743
+ }
1744
+ function canListen(host, port) {
1745
+ return new Promise((resolve) => {
1746
+ const probe = net.createServer();
1747
+ probe.once("error", () => resolve(false));
1748
+ probe.listen(port, host, () => {
1749
+ probe.close(() => resolve(true));
1750
+ });
1751
+ });
1752
+ }
1753
+ function allowedHostsFor(host, port) {
1754
+ const hosts = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", host]);
1755
+ for (const hostname of [...hosts]) {
1756
+ hosts.add(`${hostname}:${port}`);
1757
+ }
1758
+ return [...hosts];
1759
+ }
1760
+ function findWebDistDir() {
1761
+ const here = path4.dirname(fileURLToPath2(import.meta.url));
1762
+ const candidates = [
1763
+ path4.resolve(here, "../web"),
1764
+ path4.resolve(here, "../../dist/web"),
1765
+ path4.resolve(process.cwd(), "dist/web")
1766
+ ];
1767
+ return candidates.find((candidate) => existsSync(path4.join(candidate, "index.html"))) ?? candidates[2];
1768
+ }
1769
+
1770
+ // src/shared/logger.ts
1771
+ var levelOrder = {
1772
+ debug: 10,
1773
+ info: 20,
1774
+ warn: 30,
1775
+ error: 40
1776
+ };
1777
+ function createLogger(minLevel = "info") {
1778
+ const min = levelOrder[minLevel];
1779
+ const write = (level, message, details) => {
1780
+ if (levelOrder[level] < min) {
1781
+ return;
1782
+ }
1783
+ const suffix = details === void 0 ? "" : ` ${formatDetails(details)}`;
1784
+ console.error(`[agentic-canvas] ${level}: ${message}${suffix}`);
1785
+ };
1786
+ return {
1787
+ debug: (message, details) => write("debug", message, details),
1788
+ info: (message, details) => write("info", message, details),
1789
+ warn: (message, details) => write("warn", message, details),
1790
+ error: (message, details) => write("error", message, details)
1791
+ };
1792
+ }
1793
+ function formatDetails(details) {
1794
+ if (details instanceof Error) {
1795
+ return details.stack ?? details.message;
1796
+ }
1797
+ if (typeof details === "string") {
1798
+ return details;
1799
+ }
1800
+ try {
1801
+ return JSON.stringify(details);
1802
+ } catch {
1803
+ return String(details);
1804
+ }
1805
+ }
1806
+
1807
+ // src/cli/index.ts
1808
+ var logger = createLogger("info");
1809
+ async function main() {
1810
+ const packageInfo = readPackageInfo();
1811
+ const args = parseArgs({
1812
+ allowPositionals: false,
1813
+ allowNegative: true,
1814
+ options: {
1815
+ canvas: { type: "string", default: "excalidraw" },
1816
+ port: { type: "string", default: process.env.AGENTIC_CANVAS_PORT ?? "3333" },
1817
+ host: { type: "string", default: process.env.AGENTIC_CANVAS_HOST ?? "127.0.0.1" },
1818
+ workspace: { type: "string", default: process.env.AGENTIC_CANVAS_WORKSPACE ?? process.cwd() },
1819
+ open: { type: "boolean", default: true },
1820
+ help: { type: "boolean", short: "h" },
1821
+ version: { type: "boolean", short: "v" }
1822
+ }
1823
+ });
1824
+ if (args.values.help) {
1825
+ printHelp();
1826
+ return;
1827
+ }
1828
+ if (args.values.version) {
1829
+ console.log(packageInfo.version);
1830
+ return;
1831
+ }
1832
+ if (args.values.canvas !== "excalidraw") {
1833
+ throw new Error(`Unknown canvas "${args.values.canvas}". Available canvases: excalidraw`);
1834
+ }
1835
+ const requestedPort = Number(args.values.port);
1836
+ if (!Number.isInteger(requestedPort) || requestedPort < 1 || requestedPort > 65535) {
1837
+ throw new Error(`Invalid port: ${args.values.port}`);
1838
+ }
1839
+ const running = await startHttpServer({
1840
+ canvas: "excalidraw",
1841
+ host: String(args.values.host),
1842
+ port: requestedPort,
1843
+ workspace: String(args.values.workspace)
1844
+ });
1845
+ console.log(`Canvas: ${running.canvasUrl}`);
1846
+ console.log(`MCP: ${running.mcpUrl}`);
1847
+ if (args.values.open) {
1848
+ await open(running.canvasUrl);
1849
+ }
1850
+ }
1851
+ function printHelp() {
1852
+ console.log(`${CLI_NAME}
1853
+
1854
+ Start a local Excalidraw canvas with an MCP Streamable HTTP endpoint.
1855
+
1856
+ Usage:
1857
+ npx ${PACKAGE_NAME} --canvas excalidraw
1858
+ ${CLI_NAME} --canvas excalidraw
1859
+
1860
+ Options:
1861
+ --canvas <name> Canvas plugin, currently excalidraw (default: excalidraw)
1862
+ --port <n> Port (default: 3333 or AGENTIC_CANVAS_PORT)
1863
+ --host <host> Bind host (default: 127.0.0.1 or AGENTIC_CANVAS_HOST)
1864
+ --workspace <dir> Save/open/screenshot workspace (default: cwd)
1865
+ --open, --no-open Open browser on startup (default: open)
1866
+ -h, --help Show help
1867
+ -v, --version Show version`);
1868
+ }
1869
+ main().catch((error) => {
1870
+ logger.error(error instanceof Error ? error.message : String(error));
1871
+ process.exitCode = 1;
1872
+ });
1873
+ //# sourceMappingURL=index.js.map