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
package/README.md CHANGED
@@ -115,6 +115,11 @@ Import and export STEP files with full color support. Bring in existing CAD mode
115
115
 
116
116
  FluidCAD ships official extensions for **VS Code** and **Neovim**, but works with any editor -- just point the CLI at your project.
117
117
 
118
+ ### LLM / AI Agent Integration (MCP)
119
+
120
+ FluidCAD ships an [MCP](https://modelcontextprotocol.io) server so AI agents (Claude Code, Claude Desktop, Cursor, opencode, etc.) can drive a running workspace -- take screenshots, inspect geometry, edit `.fluid.js` files, and look up the API by symbol. See [Set Up the MCP Server](#3-optional-set-up-the-mcp-server) below.
121
+
122
+
118
123
  ---
119
124
 
120
125
  ## Tutorials
@@ -216,10 +221,10 @@ See the full list of commands in the [Neovim plugin README](extension/neovim/REA
216
221
  <details>
217
222
  <summary><strong>Any Other Editor</strong></summary>
218
223
 
219
- Run the FluidCAD server directly:
224
+ From your project directory, run the FluidCAD server directly:
220
225
 
221
226
  ```bash
222
- npx fluidcad -w ./my-app
227
+ npx fluidcad serve
223
228
  ```
224
229
 
225
230
  This starts a local server and opens a 3D viewport in your browser. Edit your `.fluid.js` files in any editor -- the viewport updates on save.
@@ -230,9 +235,74 @@ This starts a local server and opens a 3D viewport in your browser. Edit your `.
230
235
  |------|-------------|---------|
231
236
  | `-w, --workspace <path>` | Path to your project | Current directory |
232
237
  | `-p, --port <port>` | Server port | `3100` |
238
+ | `--open` | Open the viewport in your default browser when ready | _off_ |
239
+
240
+ </details>
241
+
242
+ ### 3. (Optional) Set Up the MCP Server
243
+
244
+ FluidCAD bundles an [MCP](https://modelcontextprotocol.io) server so LLM agents can drive your workspace -- screenshots, geometry inspection, source edits, API lookup. It's included in the `fluidcad` package; no separate install needed.
245
+
246
+ Wire it into your MCP client:
247
+
248
+ <details>
249
+ <summary><strong>Claude Code</strong></summary>
250
+
251
+ Register at user scope so it's available in every project:
252
+
253
+ ```bash
254
+ claude mcp add --scope user FluidCAD -- npx -y fluidcad mcp
255
+ ```
256
+
257
+ </details>
258
+
259
+ <details>
260
+ <summary><strong>Claude Desktop / Cursor</strong></summary>
261
+
262
+ Add to `claude_desktop_config.json` or `~/.cursor/mcp.json`:
263
+
264
+ ```json
265
+ {
266
+ "mcpServers": {
267
+ "FluidCAD": {
268
+ "command": "npx",
269
+ "args": ["-y", "fluidcad", "mcp"]
270
+ }
271
+ }
272
+ }
273
+ ```
233
274
 
234
275
  </details>
235
276
 
277
+ <details>
278
+ <summary><strong>opencode</strong></summary>
279
+
280
+ Run `opencode mcp add` and answer the prompts, or add to `~/.config/opencode/opencode.json`:
281
+
282
+ ```json
283
+ {
284
+ "$schema": "https://opencode.ai/config.json",
285
+ "mcp": {
286
+ "FluidCAD": {
287
+ "type": "local",
288
+ "command": ["npx", "-y", "fluidcad", "mcp"],
289
+ "enabled": true
290
+ }
291
+ }
292
+ }
293
+ ```
294
+
295
+ </details>
296
+
297
+ Then install the companion skill so agents follow the FluidCAD workflow:
298
+
299
+ ```bash
300
+ npx skills add Fluid-CAD/FluidCAD
301
+ ```
302
+
303
+ See the [MCP README](mcp/README.md) for the full tool surface, transport details, and local-testing guide.
304
+
305
+
236
306
  ---
237
307
 
238
308
  ## License
@@ -0,0 +1,55 @@
1
+ import { writeFileSync, existsSync } from 'fs';
2
+ import { resolve } from 'path';
3
+
4
+ const INIT_JS = `import { init } from 'fluidcad'\n\nexport default await init()\n`;
5
+
6
+ const TEST_FLUID_JS = `import { extrude, fillet, rect, shell, sketch } from "fluidcad/core";
7
+
8
+ sketch("xy", () => {
9
+ rect(100, 50).radius(10).centered();
10
+ });
11
+
12
+ const e = extrude(30);
13
+
14
+ fillet(4, e.startEdges());
15
+
16
+ shell(-2, e.endFaces());
17
+ `;
18
+
19
+ const JSCONFIG = JSON.stringify({
20
+ compilerOptions: {
21
+ checkJs: true,
22
+ module: 'node20',
23
+ },
24
+ }, null, 2) + '\n';
25
+
26
+ function runInit() {
27
+ const cwd = process.cwd();
28
+
29
+ const initPath = resolve(cwd, 'init.js');
30
+ if (existsSync(initPath)) {
31
+ console.error('init.js already exists in this directory.');
32
+ process.exit(1);
33
+ }
34
+
35
+ writeFileSync(initPath, INIT_JS);
36
+
37
+ const testPath = resolve(cwd, 'test.fluid.js');
38
+ if (!existsSync(testPath)) {
39
+ writeFileSync(testPath, TEST_FLUID_JS);
40
+ }
41
+
42
+ const jsconfigPath = resolve(cwd, 'jsconfig.json');
43
+ if (!existsSync(jsconfigPath)) {
44
+ writeFileSync(jsconfigPath, JSCONFIG);
45
+ }
46
+
47
+ console.log('FluidCAD initialized.');
48
+ }
49
+
50
+ export function registerInitCommand(program) {
51
+ program
52
+ .command('init')
53
+ .description('Scaffold init.js, test.fluid.js, and jsconfig.json in the current directory')
54
+ .action(runInit);
55
+ }
@@ -0,0 +1,120 @@
1
+ import http from 'http';
2
+ import { randomBytes, createHash } from 'crypto';
3
+ import { getHubUrl, writeCredentials } from '../lib/config.js';
4
+ import { HubClient } from '../lib/api-client.js';
5
+ import { openBrowser } from '../lib/browser.js';
6
+
7
+ const CLIENT_ID = 'fluidcad-cli';
8
+ const LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
9
+
10
+ function base64url(buf) {
11
+ return buf.toString('base64url');
12
+ }
13
+
14
+ /**
15
+ * Loopback + PKCE login (RFC 8252): spin up a one-shot 127.0.0.1 server, send
16
+ * the browser to the hub's /cli/authorize, receive the code on /callback,
17
+ * exchange it (with the PKCE verifier) for a token, and save it.
18
+ */
19
+ function runLogin(opts) {
20
+ return new Promise((resolve, reject) => {
21
+ const hubUrl = getHubUrl(opts.hub);
22
+ const verifier = base64url(randomBytes(32));
23
+ const challenge = base64url(createHash('sha256').update(verifier).digest());
24
+ const state = base64url(randomBytes(16));
25
+ let redirectUri;
26
+ let settled = false;
27
+
28
+ const finish = (fn, arg) => {
29
+ if (settled) {
30
+ return;
31
+ }
32
+ settled = true;
33
+ clearTimeout(timer);
34
+ server.close();
35
+ fn(arg);
36
+ };
37
+
38
+ const server = http.createServer((req, res) => {
39
+ const url = new URL(req.url, 'http://127.0.0.1');
40
+ if (url.pathname !== '/callback') {
41
+ res.writeHead(404);
42
+ res.end();
43
+ return;
44
+ }
45
+ res.writeHead(200, { 'content-type': 'text/html' });
46
+ res.end(
47
+ '<!doctype html><meta charset="utf-8"><body style="font-family:system-ui;text-align:center;padding-top:3rem">' +
48
+ '<h2>FluidCAD CLI</h2><p>You can close this tab and return to your terminal.</p>',
49
+ );
50
+ const error = url.searchParams.get('error');
51
+ const code = url.searchParams.get('code');
52
+ const returnedState = url.searchParams.get('state');
53
+
54
+ (async () => {
55
+ if (error) {
56
+ throw new Error(`authorization ${error}`);
57
+ }
58
+ if (returnedState !== state) {
59
+ throw new Error('state mismatch — aborting (possible CSRF)');
60
+ }
61
+ if (!code) {
62
+ throw new Error('no authorization code in callback');
63
+ }
64
+ const { status, body } = await new HubClient(hubUrl).postJson('/api/cli/token', {
65
+ client_id: CLIENT_ID,
66
+ code,
67
+ code_verifier: verifier,
68
+ redirect_uri: redirectUri,
69
+ });
70
+ if (status !== 200 || !body.access_token) {
71
+ throw new Error(`token exchange failed: ${body.error_description || body.error || `HTTP ${status}`}`);
72
+ }
73
+ writeCredentials({ token: body.access_token, email: body.user?.email ?? null, hubUrl });
74
+ return body.user?.email ?? null;
75
+ })().then(
76
+ (email) => finish(resolve, email),
77
+ (err) => finish(reject, err),
78
+ );
79
+ });
80
+
81
+ const timer = setTimeout(
82
+ () => finish(reject, new Error('login timed out — no response within 5 minutes')),
83
+ LOGIN_TIMEOUT_MS,
84
+ );
85
+
86
+ server.on('error', (err) => finish(reject, err));
87
+ server.listen(0, '127.0.0.1', () => {
88
+ const { port } = server.address();
89
+ redirectUri = `http://127.0.0.1:${port}/callback`;
90
+ const authorizeUrl =
91
+ `${hubUrl}/cli/authorize?` +
92
+ new URLSearchParams({
93
+ response_type: 'code',
94
+ client_id: CLIENT_ID,
95
+ redirect_uri: redirectUri,
96
+ code_challenge: challenge,
97
+ code_challenge_method: 'S256',
98
+ state,
99
+ });
100
+ console.log('Opening your browser to authorize the FluidCAD CLI…');
101
+ console.log(`\n ${authorizeUrl}\n`);
102
+ openBrowser(authorizeUrl);
103
+ });
104
+ });
105
+ }
106
+
107
+ export function registerLoginCommand(program) {
108
+ program
109
+ .command('login')
110
+ .description('Authenticate this machine with the FluidCAD hub')
111
+ .option('--hub <url>', 'Hub base URL (default: $FLUIDCAD_HUB_URL or https://hub.fluidcad.io)')
112
+ .action((opts) => {
113
+ runLogin(opts)
114
+ .then((email) => console.log(`Logged in as ${email ?? 'your account'}`))
115
+ .catch((err) => {
116
+ console.error(err?.message ?? err);
117
+ process.exit(1);
118
+ });
119
+ });
120
+ }
@@ -0,0 +1,33 @@
1
+ import { resolve, dirname } from 'path';
2
+ import { fileURLToPath } from 'url';
3
+
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ const mcpEntry = resolve(__dirname, '..', '..', 'mcp', 'dist', 'server.js');
6
+
7
+ async function runMcp() {
8
+ // stdout is reserved for the MCP protocol. Any incidental console.log from
9
+ // FluidCAD internals would corrupt the stream — route everything to stderr.
10
+ console.log = (...args) => console.error(...args);
11
+ console.info = (...args) => console.error(...args);
12
+
13
+ const mod = await import(mcpEntry);
14
+ if (typeof mod.runStdio !== 'function') {
15
+ console.error('mcp/dist/server.js does not export runStdio.');
16
+ process.exit(1);
17
+ }
18
+ await mod.runStdio();
19
+ }
20
+
21
+ export function registerMcpCommand(program) {
22
+ program
23
+ .command('mcp')
24
+ .description('Run the FluidCAD MCP server over stdio (for Claude Desktop, Claude Code, Cursor, ...)')
25
+ .action(async () => {
26
+ try {
27
+ await runMcp();
28
+ } catch (err) {
29
+ console.error(err?.stack || err?.message || String(err));
30
+ process.exit(1);
31
+ }
32
+ });
33
+ }
@@ -0,0 +1,49 @@
1
+ import { writeFileSync } from 'fs';
2
+ import { resolve, basename } from 'path';
3
+ import { findEntry, readPackageVersion } from '../lib/workspace.js';
4
+
5
+ async function runPack(opts) {
6
+ // The packer lives in server/ because esbuild + jszip belong at that layer;
7
+ // import it lazily so the rest of the CLI doesn't pay the cost.
8
+ const { packModel } = await import('../../server/dist/model-package/pack.js');
9
+
10
+ const workspace = resolve(opts.workspace ?? process.cwd());
11
+ const entry = findEntry(workspace, opts.entry);
12
+ const fluidcadVersion = readPackageVersion();
13
+
14
+ const { manifest, zip } = await packModel({
15
+ entryPath: entry,
16
+ workspacePath: workspace,
17
+ fluidcadVersion,
18
+ name: opts.name,
19
+ description: opts.description,
20
+ });
21
+
22
+ const outPath = opts.out
23
+ ? resolve(opts.out)
24
+ : resolve(workspace, basename(entry).replace(/\.fluid\.js$/i, '') + '.fluidpkg');
25
+ writeFileSync(outPath, zip);
26
+ const fileCount = manifest.files?.length ?? 0;
27
+ const assetCount = manifest.assets.length;
28
+ console.log(
29
+ `Wrote ${outPath} (${zip.length} bytes, ${fileCount} file${fileCount === 1 ? '' : 's'}, ` +
30
+ `${assetCount} asset${assetCount === 1 ? '' : 's'})`,
31
+ );
32
+ }
33
+
34
+ export function registerPackCommand(program) {
35
+ program
36
+ .command('pack')
37
+ .description('Package a .fluid.js model into a shareable .fluidpkg archive')
38
+ .option('-w, --workspace <path>', 'Workspace directory (defaults to cwd)')
39
+ .option('-e, --entry <file>', 'Entry .fluid.js file (auto-detected if only one exists)')
40
+ .option('-o, --out <path>', 'Output path (defaults to <entry-basename>.fluidpkg in the workspace)')
41
+ .option('-n, --name <name>', 'Package name (defaults to the entry file basename)')
42
+ .option('-d, --description <text>', 'Optional human description')
43
+ .action((opts) => {
44
+ runPack(opts).catch((err) => {
45
+ console.error(err?.message ?? err);
46
+ process.exit(1);
47
+ });
48
+ });
49
+ }
@@ -0,0 +1,136 @@
1
+ import { resolve } from 'path';
2
+ import { getHubUrl, readCredentials } from '../lib/config.js';
3
+ import { HubClient } from '../lib/api-client.js';
4
+ import { findEntry, readPackageVersion, readWorkspacePackage } from '../lib/workspace.js';
5
+ import { readModelId, writeModelId } from '../lib/model-config.js';
6
+ import { openBrowser } from '../lib/browser.js';
7
+
8
+ async function runPublish(opts) {
9
+ const creds = readCredentials();
10
+ if (!creds) {
11
+ throw new Error('Not logged in. Run `fluidcad login` first.');
12
+ }
13
+ const hubUrl = getHubUrl(opts.hub || creds.hubUrl);
14
+
15
+ const workspace = resolve(opts.workspace ?? process.cwd());
16
+ const entry = findEntry(workspace, opts.entry);
17
+
18
+ // Prefills from the model's own package.json + the stable identity from
19
+ // fluidcad.json (null on the first publish → the hub mints one).
20
+ const pkg = readWorkspacePackage(workspace);
21
+ const name = opts.name ?? pkg.name;
22
+ const description = opts.description ?? pkg.description;
23
+ const modelId = readModelId(workspace);
24
+
25
+ // Render once to capture the full param schema for the manifest. This also
26
+ // acts as a build gate — a compile/runtime error fails the publish here,
27
+ // before anything is uploaded.
28
+ //
29
+ // Dynamic import is deliberate (and necessary): this pulls in the whole
30
+ // engine — Vite + OC wasm, ~40MB / ~110ms. bin/fluidcad.js eagerly loads
31
+ // every command module at startup, so a top-level import here would make
32
+ // `init`, `serve`, `login`, `--help` etc. pay that cost too. Loading it only
33
+ // when `publish` actually runs is the justified exception to "no inline
34
+ // imports".
35
+ console.log('Building model…');
36
+ const { captureParamDefinitions } = await import(
37
+ '../../server/dist/model-package/capture-params.js'
38
+ );
39
+ let paramDefinitions;
40
+ try {
41
+ paramDefinitions = await captureParamDefinitions(entry, workspace);
42
+ } catch (err) {
43
+ throw new Error(`Model failed to build — fix the error and retry:\n${err?.message ?? err}`);
44
+ }
45
+
46
+ // Pack the whole workspace (Pack v2), embedding the captured params. Lazy
47
+ // import for the same reason (keeps esbuild/jszip off the startup path of
48
+ // non-publish commands), consistent with how `pack` loads it.
49
+ const { packModel } = await import('../../server/dist/model-package/pack.js');
50
+ const { manifest, zip } = await packModel({
51
+ entryPath: entry,
52
+ workspacePath: workspace,
53
+ fluidcadVersion: readPackageVersion(),
54
+ name,
55
+ description,
56
+ paramDefinitions,
57
+ });
58
+
59
+ // Show exactly what's going up so stray files/secrets get caught before they
60
+ // leave the machine (the .gitignore guard plus an always-exclude list).
61
+ const files = manifest.files ?? [];
62
+ console.log(`\nUploading ${files.length} file${files.length === 1 ? '' : 's'} (${(zip.length / 1024).toFixed(1)} KB):`);
63
+ for (const f of files) {
64
+ console.log(` ${f}`);
65
+ }
66
+
67
+ const form = new FormData();
68
+ form.append('fluidpkg', new Blob([zip], { type: 'application/zip' }), 'model.fluidpkg');
69
+ if (modelId) {
70
+ form.append('modelId', modelId);
71
+ }
72
+ if (name) {
73
+ form.append('name', name);
74
+ }
75
+ if (opts.visibility) {
76
+ form.append('visibility', opts.visibility);
77
+ }
78
+
79
+ const { status, body } = await new HubClient(hubUrl, creds.token).postForm('/api/publish', form);
80
+ if (status === 401) {
81
+ throw new Error('Your session has expired. Run `fluidcad login` again.');
82
+ }
83
+ if (status === 403) {
84
+ throw new Error(body.error || 'That model belongs to another account.');
85
+ }
86
+ if (status === 422) {
87
+ throw new Error(body.error || 'That FluidCAD version is not hosted yet.');
88
+ }
89
+ if (status !== 200 && status !== 201) {
90
+ throw new Error(body.error || `Publish failed (HTTP ${status})`);
91
+ }
92
+
93
+ // First publish for this workspace → persist the hub-minted id so the next
94
+ // publish lands as a new version of the same model.
95
+ if (!modelId && body.modelId) {
96
+ writeModelId(workspace, body.modelId);
97
+ }
98
+
99
+ console.log('');
100
+ if (body.isNewVersion) {
101
+ console.log(`A new version (v${body.version}) will be published — add details in your browser.`);
102
+ } else {
103
+ console.log('Created model — finish setup in your browser.');
104
+ }
105
+
106
+ if (body.formUrl) {
107
+ const formUrl = body.formUrl.startsWith('http') ? body.formUrl : hubUrl + body.formUrl;
108
+ console.log(`\n ${formUrl}\n`);
109
+ await openBrowser(formUrl);
110
+ } else if (body.shareUrl) {
111
+ // Fallback for a pre-v2 hub that deployed synchronously and has no form.
112
+ console.log(`\n ${body.shareUrl}\n`);
113
+ }
114
+ }
115
+
116
+ export function registerPublishCommand(program) {
117
+ program
118
+ .command('publish')
119
+ .description('Pack the current model and publish it to the FluidCAD hub')
120
+ .option('-w, --workspace <path>', 'Workspace directory (defaults to cwd)')
121
+ .option('-e, --entry <file>', 'Entry .fluid.js file (auto-detected if only one exists)')
122
+ .option('-n, --name <name>', 'Model name (defaults to the package name)')
123
+ .option('-d, --description <text>', 'Optional human description')
124
+ .option('--visibility <visibility>', 'public | unlisted | private (default: unlisted)')
125
+ .option('--hub <url>', 'Hub base URL (default: the hub you logged into)')
126
+ .action((opts) => {
127
+ runPublish(opts)
128
+ // The render spins up engine + Vite handles; exit explicitly so a
129
+ // successful publish doesn't hang waiting for the loop to drain.
130
+ .then(() => process.exit(0))
131
+ .catch((err) => {
132
+ console.error(err?.message ?? err);
133
+ process.exit(1);
134
+ });
135
+ });
136
+ }
@@ -0,0 +1,77 @@
1
+ import { fork } from 'child_process';
2
+ import { resolve, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import open from 'open';
5
+ import { createFileWatcher, findFluidFiles } from '../watcher.js';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const serverEntry = resolve(__dirname, '..', '..', 'server', 'dist', 'index.js');
9
+
10
+ function runServe(opts) {
11
+ const workspacePath = resolve(opts.workspace);
12
+ const port = String(opts.port);
13
+
14
+ const server = fork(serverEntry, [], {
15
+ env: {
16
+ ...process.env,
17
+ FLUIDCAD_SERVER_PORT: port,
18
+ FLUIDCAD_WORKSPACE_PATH: workspacePath,
19
+ },
20
+ stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
21
+ });
22
+
23
+ server.stdout.on('data', (data) => { process.stdout.write(data); });
24
+ server.stderr.on('data', (data) => { process.stderr.write(data); });
25
+
26
+ let watcher;
27
+
28
+ server.on('message', (msg) => {
29
+ if (msg.type === 'ready') {
30
+ console.log(`FluidCAD server ready at ${msg.url}`);
31
+ if (opts.open) {
32
+ open(msg.url).catch((err) => {
33
+ console.error(`Failed to open browser: ${err.message}`);
34
+ });
35
+ }
36
+ }
37
+ if (msg.type === 'init-complete') {
38
+ if (msg.success) {
39
+ console.log('FluidCAD initialized successfully.');
40
+ watcher = createFileWatcher(workspacePath, server);
41
+
42
+ const files = findFluidFiles(workspacePath);
43
+ if (files.length > 0) {
44
+ server.send({ type: 'process-file', filePath: files[0] });
45
+ }
46
+ } else {
47
+ console.error(`FluidCAD initialization failed: ${msg.error}`);
48
+ process.exit(1);
49
+ }
50
+ }
51
+ });
52
+
53
+ server.on('exit', (code) => {
54
+ if (watcher) { watcher.close(); }
55
+ process.exit(code || 0);
56
+ });
57
+
58
+ process.on('SIGINT', () => {
59
+ if (watcher) { watcher.close(); }
60
+ server.kill('SIGINT');
61
+ });
62
+
63
+ process.on('SIGTERM', () => {
64
+ if (watcher) { watcher.close(); }
65
+ server.kill('SIGTERM');
66
+ });
67
+ }
68
+
69
+ export function registerServeCommand(program) {
70
+ program
71
+ .command('serve')
72
+ .description('Start the FluidCAD server and watch .fluid.js files')
73
+ .option('-w, --workspace <path>', 'workspace directory', process.cwd())
74
+ .option('-p, --port <port>', 'server port', '3100')
75
+ .option('--open', 'open the UI in the default browser when ready', false)
76
+ .action(runServe);
77
+ }