fluidcad 0.0.33 → 0.0.35

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 (363) hide show
  1. package/README.md +72 -2
  2. package/bin/commands/init.js +55 -0
  3. package/bin/commands/login.js +120 -0
  4. package/bin/commands/mcp.js +33 -0
  5. package/bin/commands/pack.js +49 -0
  6. package/bin/commands/publish.js +136 -0
  7. package/bin/commands/serve.js +77 -0
  8. package/bin/fluidcad.js +21 -107
  9. package/bin/lib/api-client.js +40 -0
  10. package/bin/lib/browser.js +16 -0
  11. package/bin/lib/config.js +39 -0
  12. package/bin/lib/model-config.js +38 -0
  13. package/bin/lib/workspace.js +57 -0
  14. package/lib/dist/common/scene-object.d.ts +2 -1
  15. package/lib/dist/common/scene-object.js +3 -2
  16. package/lib/dist/common/shape-factory.d.ts +2 -1
  17. package/lib/dist/common/shape-factory.js +4 -0
  18. package/lib/dist/common/transformable-primitive.d.ts +6 -5
  19. package/lib/dist/common/transformable-primitive.js +8 -7
  20. package/lib/dist/common/vertex.js +0 -1
  21. package/lib/dist/core/2d/aline.d.ts +4 -3
  22. package/lib/dist/core/2d/aline.js +3 -2
  23. package/lib/dist/core/2d/arc.d.ts +3 -2
  24. package/lib/dist/core/2d/arc.js +4 -3
  25. package/lib/dist/core/2d/bezier.d.ts +8 -6
  26. package/lib/dist/core/2d/circle.d.ts +4 -3
  27. package/lib/dist/core/2d/circle.js +3 -2
  28. package/lib/dist/core/2d/ellipse.d.ts +5 -4
  29. package/lib/dist/core/2d/ellipse.js +5 -4
  30. package/lib/dist/core/2d/hline.d.ts +4 -3
  31. package/lib/dist/core/2d/hline.js +5 -3
  32. package/lib/dist/core/2d/line.js +1 -0
  33. package/lib/dist/core/2d/offset.d.ts +3 -2
  34. package/lib/dist/core/2d/offset.js +6 -5
  35. package/lib/dist/core/2d/polygon.d.ts +5 -4
  36. package/lib/dist/core/2d/polygon.js +10 -9
  37. package/lib/dist/core/2d/rect.d.ts +4 -3
  38. package/lib/dist/core/2d/rect.js +10 -9
  39. package/lib/dist/core/2d/slot.d.ts +14 -6
  40. package/lib/dist/core/2d/slot.js +19 -8
  41. package/lib/dist/core/2d/tarc.d.ts +20 -2
  42. package/lib/dist/core/2d/tarc.js +24 -0
  43. package/lib/dist/core/2d/vline.d.ts +4 -3
  44. package/lib/dist/core/2d/vline.js +5 -3
  45. package/lib/dist/core/chamfer.d.ts +5 -4
  46. package/lib/dist/core/chamfer.js +7 -6
  47. package/lib/dist/core/color.d.ts +3 -2
  48. package/lib/dist/core/color.js +2 -1
  49. package/lib/dist/core/cut.d.ts +4 -3
  50. package/lib/dist/core/cut.js +5 -4
  51. package/lib/dist/core/cylinder.d.ts +2 -1
  52. package/lib/dist/core/cylinder.js +2 -1
  53. package/lib/dist/core/draft.d.ts +3 -2
  54. package/lib/dist/core/draft.js +3 -2
  55. package/lib/dist/core/extrude.d.ts +4 -3
  56. package/lib/dist/core/extrude.js +5 -4
  57. package/lib/dist/core/fillet.d.ts +5 -4
  58. package/lib/dist/core/fillet.js +6 -5
  59. package/lib/dist/core/index.d.ts +1 -0
  60. package/lib/dist/core/index.js +1 -0
  61. package/lib/dist/core/interfaces.d.ts +55 -25
  62. package/lib/dist/core/param.d.ts +74 -0
  63. package/lib/dist/core/param.js +147 -0
  64. package/lib/dist/core/repeat.d.ts +2 -1
  65. package/lib/dist/core/repeat.js +72 -54
  66. package/lib/dist/core/revolve.d.ts +2 -1
  67. package/lib/dist/core/revolve.js +3 -2
  68. package/lib/dist/core/rib.d.ts +3 -2
  69. package/lib/dist/core/rib.js +6 -2
  70. package/lib/dist/core/rotate.d.ts +5 -4
  71. package/lib/dist/core/rotate.js +4 -3
  72. package/lib/dist/core/shell.d.ts +3 -2
  73. package/lib/dist/core/shell.js +3 -2
  74. package/lib/dist/core/sphere.d.ts +3 -2
  75. package/lib/dist/core/sphere.js +2 -1
  76. package/lib/dist/core/translate.d.ts +7 -6
  77. package/lib/dist/core/translate.js +6 -5
  78. package/lib/dist/features/2d/arc.d.ts +8 -2
  79. package/lib/dist/features/2d/arc.js +94 -17
  80. package/lib/dist/features/2d/back.js +3 -2
  81. package/lib/dist/features/2d/bezier.js +16 -16
  82. package/lib/dist/features/2d/circle.js +4 -0
  83. package/lib/dist/features/2d/ellipse.js +4 -0
  84. package/lib/dist/features/2d/hline.d.ts +3 -0
  85. package/lib/dist/features/2d/hline.js +9 -2
  86. package/lib/dist/features/2d/line.d.ts +3 -0
  87. package/lib/dist/features/2d/line.js +11 -3
  88. package/lib/dist/features/2d/sketch.d.ts +4 -0
  89. package/lib/dist/features/2d/sketch.js +25 -0
  90. package/lib/dist/features/2d/slot.d.ts +5 -0
  91. package/lib/dist/features/2d/slot.js +52 -7
  92. package/lib/dist/features/2d/tarc-constrained.d.ts +2 -0
  93. package/lib/dist/features/2d/tarc-constrained.js +8 -0
  94. package/lib/dist/features/2d/tarc-radius-to-object.d.ts +16 -0
  95. package/lib/dist/features/2d/tarc-radius-to-object.js +58 -0
  96. package/lib/dist/features/2d/tarc-to-object.d.ts +18 -0
  97. package/lib/dist/features/2d/tarc-to-object.js +66 -0
  98. package/lib/dist/features/2d/tarc-to-point-tangent.d.ts +2 -0
  99. package/lib/dist/features/2d/tarc-to-point-tangent.js +6 -0
  100. package/lib/dist/features/2d/tarc-to-point.d.ts +2 -0
  101. package/lib/dist/features/2d/tarc-to-point.js +6 -0
  102. package/lib/dist/features/2d/tarc-with-tangent.d.ts +2 -0
  103. package/lib/dist/features/2d/tarc-with-tangent.js +6 -0
  104. package/lib/dist/features/2d/tarc.d.ts +2 -0
  105. package/lib/dist/features/2d/tarc.js +6 -0
  106. package/lib/dist/features/2d/vline.d.ts +3 -0
  107. package/lib/dist/features/2d/vline.js +9 -2
  108. package/lib/dist/features/copy-circular.d.ts +4 -3
  109. package/lib/dist/features/copy-circular.js +16 -9
  110. package/lib/dist/features/copy-circular2d.js +16 -9
  111. package/lib/dist/features/copy-linear.d.ts +4 -3
  112. package/lib/dist/features/copy-linear.js +18 -12
  113. package/lib/dist/features/copy-linear2d.js +18 -12
  114. package/lib/dist/features/extrude-base.d.ts +13 -3
  115. package/lib/dist/features/extrude-base.js +32 -3
  116. package/lib/dist/features/extrude-to-face.js +1 -5
  117. package/lib/dist/features/extrude-two-distances.js +1 -2
  118. package/lib/dist/features/extrude.js +1 -2
  119. package/lib/dist/features/mirror-feature.d.ts +3 -2
  120. package/lib/dist/features/mirror-feature.js +1 -1
  121. package/lib/dist/features/mirror-shape2d.js +2 -2
  122. package/lib/dist/features/repeat-base.d.ts +13 -0
  123. package/lib/dist/features/repeat-base.js +21 -0
  124. package/lib/dist/features/repeat-circular.d.ts +8 -7
  125. package/lib/dist/features/repeat-circular.js +7 -3
  126. package/lib/dist/features/repeat-linear.d.ts +9 -7
  127. package/lib/dist/features/repeat-linear.js +9 -3
  128. package/lib/dist/features/repeat-matrix.d.ts +3 -1
  129. package/lib/dist/features/repeat-matrix.js +7 -2
  130. package/lib/dist/features/shell.d.ts +4 -1
  131. package/lib/dist/features/shell.js +14 -3
  132. package/lib/dist/helpers/clone-transform.d.ts +2 -1
  133. package/lib/dist/index.d.ts +12 -1
  134. package/lib/dist/index.js +11 -4
  135. package/lib/dist/io/file-import.d.ts +7 -0
  136. package/lib/dist/io/file-import.js +30 -10
  137. package/lib/dist/math/lazy-matrix.d.ts +36 -0
  138. package/lib/dist/math/lazy-matrix.js +134 -0
  139. package/lib/dist/oc/boolean-ops.d.ts +2 -2
  140. package/lib/dist/oc/constraints/constraint-solver-adaptor.d.ts +5 -0
  141. package/lib/dist/oc/constraints/constraint-solver-adaptor.js +16 -0
  142. package/lib/dist/oc/constraints/constraint-solver.d.ts +4 -0
  143. package/lib/dist/oc/constraints/curve/curve-constraint-solver.d.ts +4 -0
  144. package/lib/dist/oc/constraints/curve/curve-constraint-solver.js +3 -0
  145. package/lib/dist/oc/constraints/geometric/geometric-constraint-solver.d.ts +6 -1
  146. package/lib/dist/oc/constraints/geometric/geometric-constraint-solver.js +4 -0
  147. package/lib/dist/oc/constraints/geometric/tangent-arc-from-point-tangent.d.ts +8 -0
  148. package/lib/dist/oc/constraints/geometric/tangent-arc-from-point-tangent.js +111 -0
  149. package/lib/dist/oc/constraints/geometric/tangent-arc-radius-to-object.d.ts +8 -0
  150. package/lib/dist/oc/constraints/geometric/tangent-arc-radius-to-object.js +161 -0
  151. package/lib/dist/oc/mesh.d.ts +9 -4
  152. package/lib/dist/oc/mesh.js +14 -13
  153. package/lib/dist/oc/shell-ops.d.ts +2 -1
  154. package/lib/dist/oc/shell-ops.js +5 -2
  155. package/lib/dist/param-registry.d.ts +34 -0
  156. package/lib/dist/param-registry.js +60 -0
  157. package/lib/dist/rendering/mesh-builder.d.ts +3 -0
  158. package/lib/dist/rendering/mesh-builder.js +10 -5
  159. package/lib/dist/rendering/render-edge.d.ts +2 -1
  160. package/lib/dist/rendering/render-edge.js +2 -2
  161. package/lib/dist/rendering/render-face.d.ts +2 -1
  162. package/lib/dist/rendering/render-face.js +2 -2
  163. package/lib/dist/rendering/render-solid.d.ts +2 -1
  164. package/lib/dist/rendering/render-solid.js +2 -2
  165. package/lib/dist/rendering/render-wire.d.ts +2 -1
  166. package/lib/dist/rendering/render-wire.js +2 -2
  167. package/lib/dist/rendering/render.d.ts +3 -0
  168. package/lib/dist/rendering/render.js +7 -2
  169. package/lib/dist/scene-manager.d.ts +4 -2
  170. package/lib/dist/scene-manager.js +12 -4
  171. package/lib/dist/tests/features/2d/arc.test.js +64 -0
  172. package/lib/dist/tests/features/2d/back.test.js +17 -1
  173. package/lib/dist/tests/features/2d/tarc.test.js +157 -0
  174. package/lib/dist/tests/features/copy-circular.test.js +1 -1
  175. package/lib/dist/tests/features/copy-linear.test.js +10 -10
  176. package/lib/dist/tests/features/repeat-user-repro-cache.test.d.ts +1 -0
  177. package/lib/dist/tests/features/repeat-user-repro-cache.test.js +97 -0
  178. package/lib/dist/tests/features/repeat-user-repro.test.d.ts +1 -0
  179. package/lib/dist/tests/features/repeat-user-repro.test.js +60 -0
  180. package/lib/dist/tests/features/shell.test.js +36 -0
  181. package/lib/dist/tests/global-setup.js +2 -1
  182. package/lib/dist/tests/helpers/extract-blocks.d.ts +9 -0
  183. package/lib/dist/tests/helpers/extract-blocks.js +56 -0
  184. package/lib/dist/tests/llm-docs-examples.test.d.ts +1 -0
  185. package/lib/dist/tests/llm-docs-examples.test.js +62 -0
  186. package/lib/dist/tests/setup.js +2 -1
  187. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  188. package/llm-docs/.coverage-allowlist.txt +9 -0
  189. package/llm-docs/api/arc.md +48 -0
  190. package/llm-docs/api/axis.md +42 -0
  191. package/llm-docs/api/bezier.md +41 -0
  192. package/llm-docs/api/booleans.md +44 -0
  193. package/llm-docs/api/chamfer.md +40 -0
  194. package/llm-docs/api/circle.md +36 -0
  195. package/llm-docs/api/color.md +34 -0
  196. package/llm-docs/api/connect.md +41 -0
  197. package/llm-docs/api/constraint-qualifiers.md +48 -0
  198. package/llm-docs/api/copy.md +63 -0
  199. package/llm-docs/api/cursor-lines.md +50 -0
  200. package/llm-docs/api/cursor-move.md +61 -0
  201. package/llm-docs/api/cut.md +55 -0
  202. package/llm-docs/api/draft.md +36 -0
  203. package/llm-docs/api/edge-filter.md +57 -0
  204. package/llm-docs/api/ellipse.md +34 -0
  205. package/llm-docs/api/extrude.md +74 -0
  206. package/llm-docs/api/face-filter.md +61 -0
  207. package/llm-docs/api/fillet.md +51 -0
  208. package/llm-docs/api/index.json +139 -0
  209. package/llm-docs/api/line.md +42 -0
  210. package/llm-docs/api/load.md +37 -0
  211. package/llm-docs/api/local.md +38 -0
  212. package/llm-docs/api/loft.md +37 -0
  213. package/llm-docs/api/mirror.md +44 -0
  214. package/llm-docs/api/offset.md +36 -0
  215. package/llm-docs/api/part.md +40 -0
  216. package/llm-docs/api/plane.md +44 -0
  217. package/llm-docs/api/polygon.md +37 -0
  218. package/llm-docs/api/primitive-solids.md +39 -0
  219. package/llm-docs/api/project-intersect.md +48 -0
  220. package/llm-docs/api/rect.md +48 -0
  221. package/llm-docs/api/remove.md +32 -0
  222. package/llm-docs/api/repeat.md +79 -0
  223. package/llm-docs/api/revolve.md +38 -0
  224. package/llm-docs/api/rib.md +40 -0
  225. package/llm-docs/api/rotate.md +37 -0
  226. package/llm-docs/api/select.md +41 -0
  227. package/llm-docs/api/shell.md +41 -0
  228. package/llm-docs/api/sketch.md +76 -0
  229. package/llm-docs/api/slot.md +36 -0
  230. package/llm-docs/api/split-trim.md +42 -0
  231. package/llm-docs/api/sweep.md +43 -0
  232. package/llm-docs/api/tarc.md +45 -0
  233. package/llm-docs/api/tcircle.md +38 -0
  234. package/llm-docs/api/tline.md +42 -0
  235. package/llm-docs/api/translate.md +40 -0
  236. package/llm-docs/api/types/aline.md +35 -0
  237. package/llm-docs/api/types/arc-angles.md +29 -0
  238. package/llm-docs/api/types/arc-points.md +48 -0
  239. package/llm-docs/api/types/axis-like.md +38 -0
  240. package/llm-docs/api/types/axis.md +21 -0
  241. package/llm-docs/api/types/boolean-operation.md +50 -0
  242. package/llm-docs/api/types/circular-repeat-options.md +31 -0
  243. package/llm-docs/api/types/common.md +32 -0
  244. package/llm-docs/api/types/cut.md +125 -0
  245. package/llm-docs/api/types/draft.md +21 -0
  246. package/llm-docs/api/types/extrudable-geometry.md +23 -0
  247. package/llm-docs/api/types/extrude.md +194 -0
  248. package/llm-docs/api/types/geometry.md +51 -0
  249. package/llm-docs/api/types/hline.md +35 -0
  250. package/llm-docs/api/types/linear-repeat-options.md +31 -0
  251. package/llm-docs/api/types/loft.md +154 -0
  252. package/llm-docs/api/types/mirror.md +35 -0
  253. package/llm-docs/api/types/offset.md +31 -0
  254. package/llm-docs/api/types/plane-like.md +35 -0
  255. package/llm-docs/api/types/plane-transform-options.md +29 -0
  256. package/llm-docs/api/types/plane.md +21 -0
  257. package/llm-docs/api/types/point-like.md +22 -0
  258. package/llm-docs/api/types/point2dlike.md +26 -0
  259. package/llm-docs/api/types/polygon.md +46 -0
  260. package/llm-docs/api/types/rect.md +128 -0
  261. package/llm-docs/api/types/revolve.md +102 -0
  262. package/llm-docs/api/types/rib.md +133 -0
  263. package/llm-docs/api/types/scene-object.md +33 -0
  264. package/llm-docs/api/types/select.md +21 -0
  265. package/llm-docs/api/types/shell.md +54 -0
  266. package/llm-docs/api/types/slot.md +43 -0
  267. package/llm-docs/api/types/sweep.md +189 -0
  268. package/llm-docs/api/types/tangent-arc-two-objects.md +46 -0
  269. package/llm-docs/api/types/transformable.md +93 -0
  270. package/llm-docs/api/types/trim.md +27 -0
  271. package/llm-docs/api/types/two-objects-tangent-line.md +46 -0
  272. package/llm-docs/api/types/vertex.md +17 -0
  273. package/llm-docs/api/types/vline.md +35 -0
  274. package/llm-docs/concepts/coordinate-system.md +45 -0
  275. package/llm-docs/concepts/history-and-rollback.md +40 -0
  276. package/llm-docs/concepts/last-selection.md +49 -0
  277. package/llm-docs/concepts/scene-graph.md +37 -0
  278. package/llm-docs/index.json +1750 -0
  279. package/mcp/dist/client.d.ts +65 -0
  280. package/mcp/dist/client.js +255 -0
  281. package/mcp/dist/discovery.d.ts +11 -0
  282. package/mcp/dist/discovery.js +78 -0
  283. package/mcp/dist/docs-index.d.ts +81 -0
  284. package/mcp/dist/docs-index.js +261 -0
  285. package/mcp/dist/resources.d.ts +4 -0
  286. package/mcp/dist/resources.js +115 -0
  287. package/mcp/dist/server.d.ts +12 -0
  288. package/mcp/dist/server.js +502 -0
  289. package/mcp/dist/tools/coordination.d.ts +9 -0
  290. package/mcp/dist/tools/coordination.js +46 -0
  291. package/mcp/dist/tools/docs.d.ts +66 -0
  292. package/mcp/dist/tools/docs.js +122 -0
  293. package/mcp/dist/tools/engine.d.ts +72 -0
  294. package/mcp/dist/tools/engine.js +190 -0
  295. package/mcp/dist/tools/inspection.d.ts +75 -0
  296. package/mcp/dist/tools/inspection.js +121 -0
  297. package/mcp/dist/tools/screenshot.d.ts +63 -0
  298. package/mcp/dist/tools/screenshot.js +263 -0
  299. package/mcp/dist/tools/source.d.ts +84 -0
  300. package/mcp/dist/tools/source.js +434 -0
  301. package/mcp/dist/tools/workspaces.d.ts +13 -0
  302. package/mcp/dist/tools/workspaces.js +33 -0
  303. package/mcp/dist/types.d.ts +18 -0
  304. package/mcp/dist/types.js +11 -0
  305. package/package.json +27 -7
  306. package/server/dist/api.d.ts +37 -0
  307. package/server/dist/api.js +44 -0
  308. package/server/dist/code-editor.d.ts +100 -0
  309. package/server/dist/code-editor.js +528 -2
  310. package/server/dist/fluidcad-server.d.ts +118 -1
  311. package/server/dist/fluidcad-server.js +350 -62
  312. package/server/dist/global-registry.d.ts +30 -0
  313. package/server/dist/global-registry.js +126 -0
  314. package/server/dist/host/blocked-imports.d.ts +8 -0
  315. package/server/dist/host/blocked-imports.js +30 -0
  316. package/server/dist/{vite-manager.d.ts → host/local-scene-host.d.ts} +3 -1
  317. package/server/dist/{vite-manager.js → host/local-scene-host.js} +6 -26
  318. package/server/dist/host/scene-host.d.ts +19 -0
  319. package/server/dist/host/scene-host.js +1 -0
  320. package/server/dist/index.js +175 -123
  321. package/server/dist/instance-file.d.ts +31 -0
  322. package/server/dist/instance-file.js +73 -0
  323. package/server/dist/lint-fluid-js.d.ts +15 -0
  324. package/server/dist/lint-fluid-js.js +271 -0
  325. package/server/dist/model-package/capture-params.d.ts +19 -0
  326. package/server/dist/model-package/capture-params.js +42 -0
  327. package/server/dist/model-package/pack.d.ts +23 -0
  328. package/server/dist/model-package/pack.js +229 -0
  329. package/server/dist/model-package/types.d.ts +78 -0
  330. package/server/dist/model-package/types.js +17 -0
  331. package/server/dist/routes/editor.d.ts +24 -0
  332. package/server/dist/routes/editor.js +44 -0
  333. package/server/dist/routes/export.d.ts +1 -1
  334. package/server/dist/routes/export.js +45 -8
  335. package/server/dist/routes/health.d.ts +7 -0
  336. package/server/dist/routes/health.js +14 -0
  337. package/server/dist/routes/hit-test.d.ts +3 -0
  338. package/server/dist/routes/hit-test.js +17 -0
  339. package/server/dist/routes/lint.d.ts +10 -0
  340. package/server/dist/routes/lint.js +28 -0
  341. package/server/dist/routes/pack.d.ts +10 -0
  342. package/server/dist/routes/pack.js +47 -0
  343. package/server/dist/routes/params.d.ts +3 -0
  344. package/server/dist/routes/params.js +75 -0
  345. package/server/dist/routes/render.d.ts +33 -0
  346. package/server/dist/routes/render.js +34 -0
  347. package/server/dist/routes/scene.d.ts +5 -0
  348. package/server/dist/routes/scene.js +48 -0
  349. package/server/dist/routes/screenshot.js +68 -1
  350. package/server/dist/routes/sketch-edits.d.ts +3 -0
  351. package/server/dist/routes/sketch-edits.js +542 -0
  352. package/server/dist/routes/timeline.d.ts +3 -0
  353. package/server/dist/routes/timeline.js +49 -0
  354. package/server/dist/server-core.d.ts +53 -0
  355. package/server/dist/server-core.js +147 -0
  356. package/server/dist/ws-protocol.d.ts +156 -3
  357. package/ui/dist/assets/index-CDJmUpFI.css +2 -0
  358. package/ui/dist/assets/index-MRqwG9Vh.js +5417 -0
  359. package/ui/dist/index.html +2 -2
  360. package/server/dist/routes/actions.d.ts +0 -3
  361. package/server/dist/routes/actions.js +0 -309
  362. package/ui/dist/assets/index-CFi9p7wR.js +0 -4946
  363. package/ui/dist/assets/index-DR7c2Qk9.css +0 -2
@@ -0,0 +1,24 @@
1
+ import { Router } from 'express';
2
+ export type DirtyFileEntry = {
3
+ /** Absolute, normalized path of the dirty file. */
4
+ path: string;
5
+ /** Editor-observed mtime when the file was last seen on disk, or 0 if unknown. */
6
+ lastModifiedMs: number;
7
+ };
8
+ /**
9
+ * Holds the set of files the editor currently considers dirty (unsaved
10
+ * changes). The MCP source-editing tools query this before writing so a
11
+ * remote agent never silently clobbers a user's in-flight changes.
12
+ *
13
+ * The set is volatile by design — it gets replaced wholesale on every
14
+ * `editor-dirty-state` IPC and resets when the server restarts. Editors
15
+ * resend their state on startup.
16
+ */
17
+ export declare class DirtyBufferState {
18
+ private files;
19
+ setDirtyFiles(paths: string[]): void;
20
+ list(): DirtyFileEntry[];
21
+ isDirty(absPath: string): boolean;
22
+ private readMtime;
23
+ }
24
+ export declare function createEditorRouter(state: DirtyBufferState): Router;
@@ -0,0 +1,44 @@
1
+ import fs from 'fs';
2
+ import { Router } from 'express';
3
+ import { normalizePath } from "../normalize-path.js";
4
+ /**
5
+ * Holds the set of files the editor currently considers dirty (unsaved
6
+ * changes). The MCP source-editing tools query this before writing so a
7
+ * remote agent never silently clobbers a user's in-flight changes.
8
+ *
9
+ * The set is volatile by design — it gets replaced wholesale on every
10
+ * `editor-dirty-state` IPC and resets when the server restarts. Editors
11
+ * resend their state on startup.
12
+ */
13
+ export class DirtyBufferState {
14
+ files = new Map();
15
+ setDirtyFiles(paths) {
16
+ const next = new Map();
17
+ for (const p of paths) {
18
+ const normalized = normalizePath(p);
19
+ next.set(normalized, this.readMtime(normalized));
20
+ }
21
+ this.files = next;
22
+ }
23
+ list() {
24
+ return Array.from(this.files, ([path, lastModifiedMs]) => ({ path, lastModifiedMs }));
25
+ }
26
+ isDirty(absPath) {
27
+ return this.files.has(normalizePath(absPath));
28
+ }
29
+ readMtime(absPath) {
30
+ try {
31
+ return fs.statSync(absPath).mtimeMs;
32
+ }
33
+ catch {
34
+ return 0;
35
+ }
36
+ }
37
+ }
38
+ export function createEditorRouter(state) {
39
+ const router = Router();
40
+ router.get('/editor/dirty-files', (_req, res) => {
41
+ res.json(state.list());
42
+ });
43
+ return router;
44
+ }
@@ -1,3 +1,3 @@
1
1
  import { Router } from 'express';
2
2
  import type { FluidCadServer } from '../fluidcad-server.ts';
3
- export declare function createExportRouter(fluidCadServer: FluidCadServer): Router;
3
+ export declare function createExportRouter(fluidCadServer: FluidCadServer, workspacePath: string): Router;
@@ -1,8 +1,20 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
1
3
  import { Router } from 'express';
2
- export function createExportRouter(fluidCadServer) {
4
+ export function createExportRouter(fluidCadServer, workspacePath) {
3
5
  const router = Router();
6
+ // Resolve the workspace root once so symlink checks below match the path
7
+ // we actually allow writes into.
8
+ const workspaceRoot = (() => {
9
+ try {
10
+ return fs.realpathSync(workspacePath);
11
+ }
12
+ catch {
13
+ return path.resolve(workspacePath);
14
+ }
15
+ })();
4
16
  router.post('/export', (req, res) => {
5
- const { format, shapeIds, includeColors, resolution, customAngularDeflectionDeg, customLinearDeflection } = req.body;
17
+ const { format, shapeIds, includeColors, resolution, customAngularDeflectionDeg, customLinearDeflection, saveAsPath } = req.body;
6
18
  if (format !== 'step' && format !== 'stl') {
7
19
  res.status(400).json({ error: 'Invalid format. Must be "step" or "stl".' });
8
20
  return;
@@ -24,6 +36,10 @@ export function createExportRouter(fluidCadServer) {
24
36
  }
25
37
  }
26
38
  }
39
+ if (saveAsPath !== undefined && typeof saveAsPath !== 'string') {
40
+ res.status(400).json({ error: 'saveAsPath must be a string.' });
41
+ return;
42
+ }
27
43
  try {
28
44
  const result = fluidCadServer.exportShapes(shapeIds, {
29
45
  format,
@@ -38,14 +54,35 @@ export function createExportRouter(fluidCadServer) {
38
54
  }
39
55
  const ext = format === 'step' ? '.step' : '.stl';
40
56
  const mimeType = format === 'step' ? 'application/step' : 'application/sla';
57
+ const bytes = typeof result.data === 'string'
58
+ ? Buffer.from(result.data, 'utf-8')
59
+ : Buffer.from(result.data);
60
+ if (saveAsPath) {
61
+ // Resolve against the workspace root, then verify the canonical path
62
+ // (after symlink resolution of the parent) still lives inside it.
63
+ const candidate = path.resolve(workspaceRoot, saveAsPath);
64
+ const parent = path.dirname(candidate);
65
+ let parentReal;
66
+ try {
67
+ parentReal = fs.realpathSync(parent);
68
+ }
69
+ catch {
70
+ res.status(400).json({ error: `Parent directory does not exist: ${parent}` });
71
+ return;
72
+ }
73
+ const canonical = path.join(parentReal, path.basename(candidate));
74
+ const rel = path.relative(workspaceRoot, canonical);
75
+ if (rel.startsWith('..') || path.isAbsolute(rel)) {
76
+ res.status(400).json({ error: `saveAsPath escapes workspace root: ${saveAsPath}` });
77
+ return;
78
+ }
79
+ fs.writeFileSync(canonical, bytes);
80
+ res.json({ savedTo: canonical, bytesWritten: bytes.length });
81
+ return;
82
+ }
41
83
  res.setHeader('Content-Type', mimeType);
42
84
  res.setHeader('Content-Disposition', `attachment; filename="export${ext}"`);
43
- if (typeof result.data === 'string') {
44
- res.send(Buffer.from(result.data, 'utf-8'));
45
- }
46
- else {
47
- res.send(Buffer.from(result.data));
48
- }
85
+ res.send(bytes);
49
86
  }
50
87
  catch (err) {
51
88
  res.status(500).json({ error: err.message || String(err) });
@@ -0,0 +1,7 @@
1
+ import { Router } from 'express';
2
+ export type HealthInfo = {
3
+ version: string;
4
+ workspacePath: string;
5
+ startedAt: string;
6
+ };
7
+ export declare function createHealthRouter(info: HealthInfo): Router;
@@ -0,0 +1,14 @@
1
+ import { Router } from 'express';
2
+ export function createHealthRouter(info) {
3
+ const router = Router();
4
+ router.get('/health', (_req, res) => {
5
+ res.json({
6
+ ok: true,
7
+ version: info.version,
8
+ workspacePath: info.workspacePath,
9
+ startedAt: info.startedAt,
10
+ pid: process.pid,
11
+ });
12
+ });
13
+ return router;
14
+ }
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ import type { FluidCadServer } from '../fluidcad-server.ts';
3
+ export declare function createHitTestRouter(fluidCadServer: FluidCadServer): Router;
@@ -0,0 +1,17 @@
1
+ import { Router } from 'express';
2
+ export function createHitTestRouter(fluidCadServer) {
3
+ const router = Router();
4
+ router.post('/hit-test', (req, res) => {
5
+ const { shapeId, rayOrigin, rayDir, edgeThreshold } = req.body;
6
+ if (typeof shapeId !== 'string' ||
7
+ !Array.isArray(rayOrigin) || rayOrigin.length !== 3 ||
8
+ !Array.isArray(rayDir) || rayDir.length !== 3 ||
9
+ typeof edgeThreshold !== 'number') {
10
+ res.status(400).json({ error: 'Invalid request body' });
11
+ return;
12
+ }
13
+ const result = fluidCadServer.hitTest(shapeId, rayOrigin, rayDir, edgeThreshold);
14
+ res.json(result);
15
+ });
16
+ return router;
17
+ }
@@ -0,0 +1,10 @@
1
+ import { Router } from 'express';
2
+ /**
3
+ * `POST /api/lint-fluid-js` — static import lint for a `.fluid.js` payload.
4
+ * Used by the MCP server before it writes a file so the agent learns to add
5
+ * imports on retry instead of failing at runtime with `ReferenceError`.
6
+ *
7
+ * Request body: `{ code: string }`
8
+ * Response: `{ missing: MissingImport[], suggestion: string }`
9
+ */
10
+ export declare function createLintRouter(): Router;
@@ -0,0 +1,28 @@
1
+ import { Router } from 'express';
2
+ import { lintFluidJs } from "../lint-fluid-js.js";
3
+ /**
4
+ * `POST /api/lint-fluid-js` — static import lint for a `.fluid.js` payload.
5
+ * Used by the MCP server before it writes a file so the agent learns to add
6
+ * imports on retry instead of failing at runtime with `ReferenceError`.
7
+ *
8
+ * Request body: `{ code: string }`
9
+ * Response: `{ missing: MissingImport[], suggestion: string }`
10
+ */
11
+ export function createLintRouter() {
12
+ const router = Router();
13
+ router.post('/lint-fluid-js', async (req, res) => {
14
+ const { code } = req.body ?? {};
15
+ if (typeof code !== 'string') {
16
+ res.status(400).json({ error: '`code` must be a string.' });
17
+ return;
18
+ }
19
+ try {
20
+ const result = await lintFluidJs(code);
21
+ res.json(result);
22
+ }
23
+ catch (err) {
24
+ res.status(500).json({ error: err?.message ?? String(err) });
25
+ }
26
+ });
27
+ return router;
28
+ }
@@ -0,0 +1,10 @@
1
+ import { Router } from 'express';
2
+ import type { FluidCadServer } from '../fluidcad-server.ts';
3
+ import type { CameraStateMessage } from '../ws-protocol.ts';
4
+ /**
5
+ * `POST /api/pack` — produce a `.fluidpkg` (zip) archive of the currently
6
+ * rendered file. Pulls live param overrides and the last-known camera state
7
+ * from the running server so the archive matches what the user is seeing.
8
+ * Returns the binary archive directly (application/zip).
9
+ */
10
+ export declare function createPackRouter(fluidCadServer: FluidCadServer, workspacePath: string, fluidcadVersion: string, getLastCameraState: () => CameraStateMessage | null): Router;
@@ -0,0 +1,47 @@
1
+ import { Router } from 'express';
2
+ import { packModel } from "../model-package/pack.js";
3
+ /**
4
+ * `POST /api/pack` — produce a `.fluidpkg` (zip) archive of the currently
5
+ * rendered file. Pulls live param overrides and the last-known camera state
6
+ * from the running server so the archive matches what the user is seeing.
7
+ * Returns the binary archive directly (application/zip).
8
+ */
9
+ export function createPackRouter(fluidCadServer, workspacePath, fluidcadVersion, getLastCameraState) {
10
+ const router = Router();
11
+ router.post('/pack', async (req, res) => {
12
+ const currentFile = fluidCadServer.getCurrentFileName();
13
+ if (!currentFile) {
14
+ res.status(404).json({ error: 'No active scene to pack' });
15
+ return;
16
+ }
17
+ const { name, description } = (req.body ?? {});
18
+ const cameraMsg = getLastCameraState();
19
+ const camera = cameraMsg
20
+ ? {
21
+ position: cameraMsg.position,
22
+ target: cameraMsg.target,
23
+ up: cameraMsg.up,
24
+ projection: cameraMsg.projection,
25
+ }
26
+ : undefined;
27
+ try {
28
+ const result = await packModel({
29
+ entryPath: currentFile,
30
+ workspacePath,
31
+ fluidcadVersion,
32
+ name,
33
+ description,
34
+ paramOverrides: fluidCadServer.getParamOverrides(currentFile),
35
+ camera,
36
+ });
37
+ res.setHeader('Content-Type', 'application/zip');
38
+ res.setHeader('X-FluidCAD-Package-Name', result.manifest.name);
39
+ res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(result.manifest.name)}.fluidpkg"`);
40
+ res.send(result.zip);
41
+ }
42
+ catch (err) {
43
+ res.status(500).json({ error: err?.message ?? String(err) });
44
+ }
45
+ });
46
+ return router;
47
+ }
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ import type { FluidCadServer } from '../fluidcad-server.ts';
3
+ export declare function createParamsRouter(fluidCadServer: FluidCadServer, sendToExtension: (msg: any) => void, broadcastToUI: (msg: any) => void): Router;
@@ -0,0 +1,75 @@
1
+ import { Router } from 'express';
2
+ export function createParamsRouter(fluidCadServer, sendToExtension, broadcastToUI) {
3
+ const router = Router();
4
+ router.post('/recompute', async (_req, res) => {
5
+ const data = await fluidCadServer.recomputeCurrentFile();
6
+ if (!data) {
7
+ res.status(404).json({ error: 'No active scene' });
8
+ return;
9
+ }
10
+ sendToExtension({
11
+ type: 'scene-rendered',
12
+ absPath: data.absPath,
13
+ result: data.result,
14
+ rollbackStop: data.rollbackStop,
15
+ });
16
+ broadcastToUI({
17
+ type: 'scene-rendered',
18
+ result: data.result,
19
+ absPath: data.absPath,
20
+ breakpointHit: data.breakpointHit,
21
+ params: data.params,
22
+ });
23
+ res.json({ success: true });
24
+ });
25
+ router.post('/set-param', async (req, res) => {
26
+ const { label, value } = req.body;
27
+ if (typeof label !== 'string') {
28
+ res.status(400).json({ error: 'Invalid label' });
29
+ return;
30
+ }
31
+ fluidCadServer.setParam(fluidCadServer.getCurrentFileName(), label, value);
32
+ const data = await fluidCadServer.recomputeCurrentFile();
33
+ if (!data) {
34
+ res.status(404).json({ error: 'No active scene' });
35
+ return;
36
+ }
37
+ sendToExtension({
38
+ type: 'scene-rendered',
39
+ absPath: data.absPath,
40
+ result: data.result,
41
+ rollbackStop: data.rollbackStop,
42
+ });
43
+ broadcastToUI({
44
+ type: 'scene-rendered',
45
+ result: data.result,
46
+ absPath: data.absPath,
47
+ rollbackStop: data.rollbackStop,
48
+ params: data.params,
49
+ });
50
+ res.json({ success: true });
51
+ });
52
+ router.post('/reset-params', async (_req, res) => {
53
+ fluidCadServer.resetParams(fluidCadServer.getCurrentFileName());
54
+ const data = await fluidCadServer.recomputeCurrentFile();
55
+ if (!data) {
56
+ res.status(404).json({ error: 'No active scene' });
57
+ return;
58
+ }
59
+ sendToExtension({
60
+ type: 'scene-rendered',
61
+ absPath: data.absPath,
62
+ result: data.result,
63
+ rollbackStop: data.rollbackStop,
64
+ });
65
+ broadcastToUI({
66
+ type: 'scene-rendered',
67
+ result: data.result,
68
+ absPath: data.absPath,
69
+ rollbackStop: data.rollbackStop,
70
+ params: data.params,
71
+ });
72
+ res.json({ success: true });
73
+ });
74
+ return router;
75
+ }
@@ -0,0 +1,33 @@
1
+ import { Router } from 'express';
2
+ import type { CompileError } from '../ws-protocol.ts';
3
+ export type RenderOutcome = {
4
+ state: 'rendered';
5
+ version: number;
6
+ absPath: string;
7
+ durationMs: number;
8
+ } | {
9
+ state: 'compile-error';
10
+ version: number;
11
+ durationMs: number;
12
+ compileError: CompileError;
13
+ } | {
14
+ state: 'superseded';
15
+ version: number;
16
+ durationMs: number;
17
+ } | {
18
+ state: 'no-scene-manager';
19
+ version: number;
20
+ durationMs: number;
21
+ };
22
+ /**
23
+ * `POST /api/render` — synchronous render trigger used by the MCP server
24
+ * after it writes a `.fluid.js` file. The body carries the post-write
25
+ * contents so the server doesn't need to re-read disk and so dedup against
26
+ * `lastRendered` is exact. Returns the render outcome (rendered / compile-
27
+ * error / superseded / no-scene-manager) once the OCC pass settles.
28
+ *
29
+ * Whoever invokes this is responsible for the on-disk write — we only run
30
+ * the render. Pairing both in one HTTP round-trip is what lets MCP
31
+ * `write_file` return a synchronous { written, render } to the agent.
32
+ */
33
+ export declare function createRenderRouter(runLiveRender: (fileName: string, code: string) => Promise<RenderOutcome>): Router;
@@ -0,0 +1,34 @@
1
+ import { Router } from 'express';
2
+ /**
3
+ * `POST /api/render` — synchronous render trigger used by the MCP server
4
+ * after it writes a `.fluid.js` file. The body carries the post-write
5
+ * contents so the server doesn't need to re-read disk and so dedup against
6
+ * `lastRendered` is exact. Returns the render outcome (rendered / compile-
7
+ * error / superseded / no-scene-manager) once the OCC pass settles.
8
+ *
9
+ * Whoever invokes this is responsible for the on-disk write — we only run
10
+ * the render. Pairing both in one HTTP round-trip is what lets MCP
11
+ * `write_file` return a synchronous { written, render } to the agent.
12
+ */
13
+ export function createRenderRouter(runLiveRender) {
14
+ const router = Router();
15
+ router.post('/render', async (req, res) => {
16
+ const { filePath, code } = req.body ?? {};
17
+ if (typeof filePath !== 'string' || filePath.length === 0) {
18
+ res.status(400).json({ error: '`filePath` must be a non-empty string.' });
19
+ return;
20
+ }
21
+ if (typeof code !== 'string') {
22
+ res.status(400).json({ error: '`code` must be a string.' });
23
+ return;
24
+ }
25
+ try {
26
+ const outcome = await runLiveRender(filePath, code);
27
+ res.json(outcome);
28
+ }
29
+ catch (err) {
30
+ res.status(500).json({ error: err?.message ?? String(err) });
31
+ }
32
+ });
33
+ return router;
34
+ }
@@ -0,0 +1,5 @@
1
+ import { Router } from 'express';
2
+ import type { FluidCadServer } from '../fluidcad-server.ts';
3
+ import type { CameraStateMessage } from '../ws-protocol.ts';
4
+ export type CameraStateGetter = () => CameraStateMessage | null;
5
+ export declare function createSceneRouter(fluidCadServer: FluidCadServer, getCameraState?: CameraStateGetter): Router;
@@ -0,0 +1,48 @@
1
+ import { Router } from 'express';
2
+ export function createSceneRouter(fluidCadServer, getCameraState = () => null) {
3
+ const router = Router();
4
+ router.get('/scene/summary', (_req, res) => {
5
+ try {
6
+ const summary = fluidCadServer.getSceneSummary();
7
+ if (!summary) {
8
+ res.status(404).json({ error: 'No scene available — the workspace has not rendered a file yet.' });
9
+ return;
10
+ }
11
+ res.json(summary);
12
+ }
13
+ catch (err) {
14
+ res.status(500).json({ error: err?.message ?? String(err) });
15
+ }
16
+ });
17
+ router.get('/scene/shapes', (_req, res) => {
18
+ try {
19
+ const shapes = fluidCadServer.getShapesList();
20
+ if (!shapes) {
21
+ res.status(404).json({ error: 'No scene available — the workspace has not rendered a file yet.' });
22
+ return;
23
+ }
24
+ res.json(shapes);
25
+ }
26
+ catch (err) {
27
+ res.status(500).json({ error: err?.message ?? String(err) });
28
+ }
29
+ });
30
+ router.get('/scene/compile-error', (_req, res) => {
31
+ try {
32
+ const compileError = fluidCadServer.getCompileError();
33
+ res.json({ compileError });
34
+ }
35
+ catch (err) {
36
+ res.status(500).json({ error: err?.message ?? String(err) });
37
+ }
38
+ });
39
+ router.get('/camera/state', (_req, res) => {
40
+ const state = getCameraState();
41
+ if (!state) {
42
+ res.status(404).json({ error: 'No camera state available yet — the UI has not connected, or no view change has been observed.' });
43
+ return;
44
+ }
45
+ res.json(state);
46
+ });
47
+ return router;
48
+ }
@@ -1,8 +1,13 @@
1
1
  import { Router } from 'express';
2
+ const NAMED_VIEWS = new Set([
3
+ 'front', 'back', 'left', 'right', 'top', 'bottom',
4
+ 'iso-ftr', 'iso-fbr', 'iso-ftl', 'iso-fbl',
5
+ 'iso-btr', 'iso-bbr', 'iso-btl', 'iso-bbl',
6
+ ]);
2
7
  export function createScreenshotRouter(requestScreenshot) {
3
8
  const router = Router();
4
9
  router.post('/screenshot', async (req, res) => {
5
- const { width, height, showGrid, showAxes, transparent, autoCrop, fitToModel, margin } = req.body;
10
+ const { width, height, showGrid, showAxes, transparent, autoCrop, fitToModel, margin, view, multi, } = req.body;
6
11
  const options = {};
7
12
  if (width !== undefined) {
8
13
  if (typeof width !== 'number' || width < 1 || width > 8192) {
@@ -60,6 +65,21 @@ export function createScreenshotRouter(requestScreenshot) {
60
65
  }
61
66
  options.margin = margin;
62
67
  }
68
+ if (multi !== undefined) {
69
+ if (typeof multi !== 'boolean') {
70
+ res.status(400).json({ error: 'multi must be a boolean.' });
71
+ return;
72
+ }
73
+ options.multi = multi;
74
+ }
75
+ if (view !== undefined) {
76
+ const validated = validateView(view);
77
+ if (typeof validated === 'string') {
78
+ res.status(400).json({ error: validated });
79
+ return;
80
+ }
81
+ options.view = validated;
82
+ }
63
83
  try {
64
84
  const png = await requestScreenshot(options);
65
85
  res.setHeader('Content-Type', 'image/png');
@@ -74,3 +94,50 @@ export function createScreenshotRouter(requestScreenshot) {
74
94
  });
75
95
  return router;
76
96
  }
97
+ /** Validate the `view` payload. Returns the parsed view on success or an
98
+ * error-message string on failure. */
99
+ function validateView(raw) {
100
+ if (raw === null || typeof raw !== 'object') {
101
+ return 'view must be an object.';
102
+ }
103
+ const v = raw;
104
+ switch (v.kind) {
105
+ case 'current':
106
+ return { kind: 'current' };
107
+ case 'named': {
108
+ if (typeof v.name !== 'string' || !NAMED_VIEWS.has(v.name)) {
109
+ return `view.name must be one of: ${Array.from(NAMED_VIEWS).join(', ')}.`;
110
+ }
111
+ return { kind: 'named', name: v.name };
112
+ }
113
+ case 'orbit-from-current': {
114
+ if (typeof v.azimuthDeg !== 'number' || !Number.isFinite(v.azimuthDeg)) {
115
+ return 'view.azimuthDeg must be a finite number.';
116
+ }
117
+ if (typeof v.elevationDeg !== 'number' || !Number.isFinite(v.elevationDeg)) {
118
+ return 'view.elevationDeg must be a finite number.';
119
+ }
120
+ return { kind: 'orbit-from-current', azimuthDeg: v.azimuthDeg, elevationDeg: v.elevationDeg };
121
+ }
122
+ case 'look-from': {
123
+ if (!isVec3(v.eye)) {
124
+ return 'view.eye must be a 3-element array of finite numbers.';
125
+ }
126
+ if (v.target !== undefined && !isVec3(v.target)) {
127
+ return 'view.target must be a 3-element array of finite numbers when provided.';
128
+ }
129
+ return {
130
+ kind: 'look-from',
131
+ eye: v.eye,
132
+ target: v.target,
133
+ };
134
+ }
135
+ default:
136
+ return `view.kind must be one of: current, named, orbit-from-current, look-from.`;
137
+ }
138
+ }
139
+ function isVec3(value) {
140
+ return (Array.isArray(value) &&
141
+ value.length === 3 &&
142
+ value.every((n) => typeof n === 'number' && Number.isFinite(n)));
143
+ }
@@ -0,0 +1,3 @@
1
+ import { Router } from 'express';
2
+ import type { FluidCadServer } from '../fluidcad-server.ts';
3
+ export declare function createSketchEditsRouter(fluidCadServer: FluidCadServer, sendToExtension: (msg: any) => void, workspacePath: string): Router;