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
@@ -1,30 +1,7 @@
1
1
  import { createServer } from 'vite';
2
2
  import { dirname, resolve, isAbsolute } from 'path';
3
- import { normalizePath } from "./normalize-path.js";
4
- const BLOCKED_NODE_MODULES = new Set([
5
- 'fs',
6
- 'child_process',
7
- 'net',
8
- 'dgram',
9
- 'tls',
10
- 'http',
11
- 'https',
12
- 'http2',
13
- 'os',
14
- 'worker_threads',
15
- 'vm',
16
- 'cluster',
17
- 'dns',
18
- 'module',
19
- ]);
20
- function getBlockedNodeModule(id) {
21
- let name = id;
22
- if (name.startsWith('node:')) {
23
- name = name.slice(5);
24
- }
25
- const baseName = name.split('/')[0];
26
- return BLOCKED_NODE_MODULES.has(baseName) ? baseName : null;
27
- }
3
+ import { normalizePath } from "../normalize-path.js";
4
+ import { getBlockedNodeModule } from "./blocked-imports.js";
28
5
  const IMPORT_PATTERN = /\b(?:import|export)\s[\s\S]*?from\s+['"]([^'"]+)['"]|\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
29
6
  function scanForBlockedImports(code) {
30
7
  let match;
@@ -38,7 +15,7 @@ function scanForBlockedImports(code) {
38
15
  }
39
16
  return null;
40
17
  }
41
- export class ViteManager {
18
+ export class LocalSceneHost {
42
19
  server;
43
20
  rootPath = '';
44
21
  buffers = new Map();
@@ -102,6 +79,9 @@ export class ViteManager {
102
79
  setBuffer(id, code) {
103
80
  this.buffers.set(id, code);
104
81
  }
82
+ getBuffer(fileName) {
83
+ return this.buffers.get(`virtual:live-render:${fileName}`) ?? null;
84
+ }
105
85
  async loadModule(filePath) {
106
86
  const mod = await this.server.ssrLoadModule(filePath);
107
87
  for (const value of Object.values(mod)) {
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Source-of-truth + execution seam for a workspace's scene code.
3
+ *
4
+ * `FluidCadServer` owns the engine pipeline (param registry, scene cache,
5
+ * render orchestration) and does not care where `.fluid.js` source comes
6
+ * from or how it gets turned into a runnable module. That responsibility
7
+ * lives behind this interface.
8
+ *
9
+ * Implementations:
10
+ * - LocalSceneHost: Vite SSR over the workspace directory (desktop).
11
+ * - HubSceneHost (Phase 2): in-memory consumer of a packed model bundle.
12
+ */
13
+ export interface SceneHost {
14
+ init(workspacePath: string): Promise<void>;
15
+ loadModule(filePath: string): Promise<Record<string, any>>;
16
+ setBuffer(id: string, code: string): void;
17
+ getBuffer(fileName: string): string | null;
18
+ invalidateModule(): void;
19
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,19 +1,46 @@
1
- import crypto from 'crypto';
1
+ import fs from 'fs';
2
2
  import http from 'http';
3
3
  import path from 'path';
4
4
  import express from 'express';
5
- import { WebSocketServer, WebSocket } from 'ws';
6
5
  import { FluidCadServer } from "./fluidcad-server.js";
6
+ import { createServerCore } from "./server-core.js";
7
7
  import { createPropertiesRouter } from "./routes/properties.js";
8
- import { createActionsRouter } from "./routes/actions.js";
8
+ import { createParamsRouter } from "./routes/params.js";
9
+ import { createHitTestRouter } from "./routes/hit-test.js";
10
+ import { createTimelineRouter } from "./routes/timeline.js";
11
+ import { createSketchEditsRouter } from "./routes/sketch-edits.js";
9
12
  import { createExportRouter } from "./routes/export.js";
10
13
  import { createScreenshotRouter } from "./routes/screenshot.js";
11
14
  import { createPreferencesRouter } from "./routes/preferences.js";
15
+ import { createHealthRouter } from "./routes/health.js";
16
+ import { createSceneRouter } from "./routes/scene.js";
17
+ import { createEditorRouter, DirtyBufferState } from "./routes/editor.js";
18
+ import { createRenderRouter } from "./routes/render.js";
19
+ import { createLintRouter } from "./routes/lint.js";
20
+ import { createPackRouter } from "./routes/pack.js";
12
21
  import { normalizePath } from "./normalize-path.js";
22
+ import { writeInstanceFile, deleteInstanceFile } from "./instance-file.js";
23
+ import { addInstance, removeInstance } from "./global-registry.js";
13
24
  import { extractSourceLocation } from '../../lib/dist/index.js';
14
25
  const PORT = parseInt(process.env.FLUIDCAD_SERVER_PORT || '3100', 10);
15
26
  const WORKSPACE_PATH = normalizePath(process.env.FLUIDCAD_WORKSPACE_PATH || '');
16
27
  const UI_DIST = path.resolve(import.meta.dirname, '../../ui/dist');
28
+ function readPackageVersion() {
29
+ try {
30
+ const pkgPath = path.resolve(import.meta.dirname, '../../package.json');
31
+ const raw = fs.readFileSync(pkgPath, 'utf8');
32
+ const parsed = JSON.parse(raw);
33
+ if (typeof parsed.version === 'string') {
34
+ return parsed.version;
35
+ }
36
+ }
37
+ catch {
38
+ // Fall through to unknown.
39
+ }
40
+ return '0.0.0';
41
+ }
42
+ const PACKAGE_VERSION = readPackageVersion();
43
+ const STARTED_AT = new Date().toISOString();
17
44
  // ---------------------------------------------------------------------------
18
45
  // IPC helpers — communication with extension host process
19
46
  // ---------------------------------------------------------------------------
@@ -26,13 +53,35 @@ function sendToExtension(msg) {
26
53
  // Express app
27
54
  // ---------------------------------------------------------------------------
28
55
  const fluidCadServer = new FluidCadServer();
56
+ const dirtyBufferState = new DirtyBufferState();
29
57
  const app = express();
30
58
  app.use(express.json({ limit: '50mb' }));
59
+ // ---------------------------------------------------------------------------
60
+ // HTTP + WebSocket server (set up early so routes can reference its helpers)
61
+ // ---------------------------------------------------------------------------
62
+ const httpServer = http.createServer(app);
63
+ const core = createServerCore(httpServer);
64
+ const broadcastToUI = core.broadcastToUI;
65
+ const requestScreenshot = core.requestScreenshot;
66
+ const getLastCameraState = core.getLastCameraState;
67
+ app.use('/api', createHealthRouter({
68
+ version: PACKAGE_VERSION,
69
+ workspacePath: WORKSPACE_PATH,
70
+ startedAt: STARTED_AT,
71
+ }));
31
72
  app.use('/api', createPropertiesRouter(fluidCadServer));
32
- app.use('/api', createActionsRouter(fluidCadServer, sendToExtension, broadcastToUI, WORKSPACE_PATH));
33
- app.use('/api', createExportRouter(fluidCadServer));
73
+ app.use('/api', createParamsRouter(fluidCadServer, sendToExtension, broadcastToUI));
74
+ app.use('/api', createHitTestRouter(fluidCadServer));
75
+ app.use('/api', createTimelineRouter(fluidCadServer, sendToExtension, broadcastToUI));
76
+ app.use('/api', createSketchEditsRouter(fluidCadServer, sendToExtension, WORKSPACE_PATH));
77
+ app.use('/api', createExportRouter(fluidCadServer, WORKSPACE_PATH));
34
78
  app.use('/api', createScreenshotRouter(requestScreenshot));
35
79
  app.use('/api', createPreferencesRouter());
80
+ app.use('/api', createSceneRouter(fluidCadServer, getLastCameraState));
81
+ app.use('/api', createEditorRouter(dirtyBufferState));
82
+ app.use('/api', createRenderRouter((fileName, code) => runLiveRender(fileName, code)));
83
+ app.use('/api', createLintRouter());
84
+ app.use('/api', createPackRouter(fluidCadServer, WORKSPACE_PATH, PACKAGE_VERSION, getLastCameraState));
36
85
  // Static files — serve UI build, with SPA fallback
37
86
  app.use(express.static(UI_DIST, {
38
87
  setHeaders(res, filePath) {
@@ -46,106 +95,14 @@ app.get('*splat', (_req, res) => {
46
95
  res.sendFile(path.join(UI_DIST, 'index.html'));
47
96
  });
48
97
  // ---------------------------------------------------------------------------
49
- // HTTP + WebSocket server
50
- // ---------------------------------------------------------------------------
51
- const httpServer = http.createServer(app);
52
- const wss = new WebSocketServer({ server: httpServer });
53
- const uiClients = new Set();
54
- let lastSceneMessage = null;
55
- let initCompleteMessage = null;
56
- function broadcastToUI(msg) {
57
- const data = JSON.stringify(msg);
58
- if (msg.type === 'scene-rendered') {
59
- lastSceneMessage = data;
60
- }
61
- if (msg.type === 'init-complete') {
62
- initCompleteMessage = data;
63
- }
64
- for (const client of uiClients) {
65
- if (client.readyState === WebSocket.OPEN) {
66
- client.send(data);
67
- }
68
- }
69
- }
70
- // ---------------------------------------------------------------------------
71
- // Screenshot request/response coordination
72
- // ---------------------------------------------------------------------------
73
- const SCREENSHOT_TIMEOUT_MS = 10_000;
74
- const pendingScreenshots = new Map();
75
- function requestScreenshot(options) {
76
- return new Promise((resolve, reject) => {
77
- if (uiClients.size === 0) {
78
- reject(new Error('No UI client connected.'));
79
- return;
80
- }
81
- const requestId = crypto.randomUUID();
82
- const timeout = setTimeout(() => {
83
- pendingScreenshots.delete(requestId);
84
- reject(new Error('Screenshot request timed out.'));
85
- }, SCREENSHOT_TIMEOUT_MS);
86
- pendingScreenshots.set(requestId, {
87
- resolve(data) {
88
- clearTimeout(timeout);
89
- pendingScreenshots.delete(requestId);
90
- resolve(data);
91
- },
92
- reject(err) {
93
- clearTimeout(timeout);
94
- pendingScreenshots.delete(requestId);
95
- reject(err);
96
- },
97
- });
98
- broadcastToUI({ type: 'take-screenshot', requestId, options });
99
- });
100
- }
101
- function handleUIMessage(raw) {
102
- let msg;
103
- try {
104
- msg = JSON.parse(raw);
105
- }
106
- catch {
107
- return;
108
- }
109
- if (msg.type === 'screenshot-result' && msg.requestId) {
110
- const pending = pendingScreenshots.get(msg.requestId);
111
- if (!pending) {
112
- return;
113
- }
114
- if (msg.success && msg.data) {
115
- pending.resolve(Buffer.from(msg.data, 'base64'));
116
- }
117
- else {
118
- pending.reject(new Error(msg.error || 'Screenshot failed.'));
119
- }
120
- }
121
- }
122
- // ---------------------------------------------------------------------------
123
- // WebSocket connections
124
- // ---------------------------------------------------------------------------
125
- wss.on('connection', (ws) => {
126
- uiClients.add(ws);
127
- // Replay init-complete and last scene to newly connected UI client
128
- if (initCompleteMessage) {
129
- ws.send(initCompleteMessage);
130
- }
131
- if (lastSceneMessage) {
132
- ws.send(lastSceneMessage);
133
- }
134
- ws.on('message', (data) => {
135
- handleUIMessage(String(data));
136
- });
137
- ws.on('close', () => {
138
- uiClients.delete(ws);
139
- });
140
- });
141
- // ---------------------------------------------------------------------------
142
98
  // IPC message handling — extension host → server
143
99
  // ---------------------------------------------------------------------------
144
100
  let currentFile = null;
145
101
  let renderVersion = 0;
146
102
  const lastSceneByFile = new Map();
147
- function emitSuccess(absPath, result, rollbackStop, breakpointHit) {
103
+ function emitSuccess(version, absPath, result, rollbackStop, breakpointHit, params) {
148
104
  lastSceneByFile.set(absPath, { result, rollbackStop });
105
+ fluidCadServer.setCompileError(null);
149
106
  sendToExtension({
150
107
  type: 'scene-rendered',
151
108
  absPath,
@@ -158,7 +115,9 @@ function emitSuccess(absPath, result, rollbackStop, breakpointHit) {
158
115
  absPath,
159
116
  rollbackStop,
160
117
  breakpointHit,
118
+ params,
161
119
  });
120
+ broadcastToUI({ type: 'render-version', version, state: 'end', absPath });
162
121
  }
163
122
  function buildCompileError(filePath, err) {
164
123
  const message = err?.message || String(err);
@@ -178,12 +137,13 @@ function buildCompileError(filePath, err) {
178
137
  sourceLocation: sourceLocation ?? undefined,
179
138
  };
180
139
  }
181
- function emitCompileError(filePath, err) {
140
+ function emitCompileError(version, filePath, err) {
182
141
  const compileError = buildCompileError(filePath, err);
183
142
  const key = compileError.filePath ?? normalizePath(filePath).replace('virtual:live-render:', '');
184
143
  const prev = lastSceneByFile.get(key);
185
144
  const result = prev?.result ?? [];
186
145
  const rollbackStop = prev?.rollbackStop ?? -1;
146
+ fluidCadServer.setCompileError(compileError);
187
147
  sendToExtension({
188
148
  type: 'scene-rendered',
189
149
  absPath: key,
@@ -198,12 +158,59 @@ function emitCompileError(filePath, err) {
198
158
  rollbackStop,
199
159
  compileError,
200
160
  });
161
+ broadcastToUI({ type: 'render-version', version, state: 'error', absPath: key });
162
+ return compileError;
163
+ }
164
+ /**
165
+ * Render-orchestration chokepoint shared by the IPC `live-update` handler and
166
+ * the HTTP `/api/render` route. Bumps `renderVersion`, broadcasts the
167
+ * lifecycle pings, runs the dedupable `updateLiveCode`, and emits success /
168
+ * compile-error to the UI + extension. Returns a structured outcome so the
169
+ * HTTP caller (MCP) can hand it straight to the agent.
170
+ */
171
+ async function runLiveRender(fileName, code) {
172
+ const startedAt = Date.now();
173
+ const myVersion = ++renderVersion;
174
+ broadcastToUI({ type: 'render-version', version: myVersion, state: 'start' });
175
+ if (fileName !== currentFile) {
176
+ broadcastToUI({ type: 'processing-file' });
177
+ currentFile = fileName;
178
+ }
179
+ try {
180
+ const data = await fluidCadServer.updateLiveCode(fileName, code);
181
+ if (myVersion !== renderVersion) {
182
+ return { state: 'superseded', version: myVersion, durationMs: Date.now() - startedAt };
183
+ }
184
+ if (!data) {
185
+ return { state: 'no-scene-manager', version: myVersion, durationMs: Date.now() - startedAt };
186
+ }
187
+ emitSuccess(myVersion, data.absPath, data.result, data.rollbackStop, data.breakpointHit, data.params);
188
+ return {
189
+ state: 'rendered',
190
+ version: myVersion,
191
+ absPath: data.absPath,
192
+ durationMs: Date.now() - startedAt,
193
+ };
194
+ }
195
+ catch (err) {
196
+ if (myVersion !== renderVersion) {
197
+ return { state: 'superseded', version: myVersion, durationMs: Date.now() - startedAt };
198
+ }
199
+ const compileError = emitCompileError(myVersion, fileName, err);
200
+ return {
201
+ state: 'compile-error',
202
+ version: myVersion,
203
+ durationMs: Date.now() - startedAt,
204
+ compileError,
205
+ };
206
+ }
201
207
  }
202
208
  async function handleExtensionMessage(msg) {
203
209
  try {
204
210
  switch (msg.type) {
205
211
  case 'process-file': {
206
212
  const myVersion = ++renderVersion;
213
+ broadcastToUI({ type: 'render-version', version: myVersion, state: 'start' });
207
214
  broadcastToUI({ type: 'processing-file' });
208
215
  currentFile = msg.filePath;
209
216
  try {
@@ -212,48 +219,30 @@ async function handleExtensionMessage(msg) {
212
219
  return;
213
220
  }
214
221
  if (data) {
215
- emitSuccess(data.absPath, data.result, data.rollbackStop, data.breakpointHit);
222
+ emitSuccess(myVersion, data.absPath, data.result, data.rollbackStop, data.breakpointHit, data.params);
216
223
  }
217
224
  }
218
225
  catch (err) {
219
226
  if (myVersion !== renderVersion) {
220
227
  return;
221
228
  }
222
- emitCompileError(msg.filePath, err);
229
+ emitCompileError(myVersion, msg.filePath, err);
223
230
  }
224
231
  break;
225
232
  }
226
233
  case 'live-update': {
227
- const myVersion = ++renderVersion;
228
- if (msg.fileName !== currentFile) {
229
- broadcastToUI({ type: 'processing-file' });
230
- currentFile = msg.fileName;
231
- }
232
- try {
233
- const data = await fluidCadServer.updateLiveCode(msg.fileName, msg.code);
234
- if (myVersion !== renderVersion) {
235
- return;
236
- }
237
- if (data) {
238
- emitSuccess(data.absPath, data.result, data.rollbackStop, data.breakpointHit);
239
- }
240
- }
241
- catch (err) {
242
- if (myVersion !== renderVersion) {
243
- return;
244
- }
245
- emitCompileError(msg.fileName, err);
246
- }
234
+ await runLiveRender(msg.fileName, msg.code);
247
235
  break;
248
236
  }
249
237
  case 'rollback': {
250
238
  const myVersion = ++renderVersion;
239
+ broadcastToUI({ type: 'render-version', version: myVersion, state: 'start' });
251
240
  const data = await fluidCadServer.rollback(msg.fileName, msg.index);
252
241
  if (myVersion !== renderVersion) {
253
242
  return;
254
243
  }
255
244
  if (data) {
256
- emitSuccess(data.absPath, data.result, data.rollbackStop);
245
+ emitSuccess(myVersion, data.absPath, data.result, data.rollbackStop);
257
246
  }
258
247
  break;
259
248
  }
@@ -279,6 +268,13 @@ async function handleExtensionMessage(msg) {
279
268
  broadcastToUI({ type: 'show-shape-properties', shapeId: msg.shapeId });
280
269
  break;
281
270
  }
271
+ case 'editor-dirty-state': {
272
+ if (Array.isArray(msg.dirtyFiles)) {
273
+ const paths = msg.dirtyFiles.filter((p) => typeof p === 'string');
274
+ dirtyBufferState.setDirtyFiles(paths);
275
+ }
276
+ break;
277
+ }
282
278
  case 'export-scene': {
283
279
  try {
284
280
  const result = fluidCadServer.exportShapes(msg.shapeIds, msg.options);
@@ -321,6 +317,36 @@ process.on('message', (msg) => {
321
317
  httpServer.listen(PORT, () => {
322
318
  const url = `http://localhost:${PORT}`;
323
319
  console.log(`FluidCAD server listening on ${url}`);
320
+ // Publish this instance so a standalone MCP process can discover us.
321
+ // Discovery is best-effort: an MCP-less workflow must keep working even if
322
+ // we can't write the file (read-only FS, permissions, …).
323
+ if (WORKSPACE_PATH) {
324
+ try {
325
+ writeInstanceFile({
326
+ schemaVersion: 1,
327
+ port: PORT,
328
+ pid: process.pid,
329
+ workspacePath: WORKSPACE_PATH,
330
+ version: PACKAGE_VERSION,
331
+ startedAt: STARTED_AT,
332
+ });
333
+ }
334
+ catch (err) {
335
+ console.warn(`Failed to write instance file: ${err?.message ?? err}`);
336
+ }
337
+ try {
338
+ addInstance({
339
+ workspacePath: WORKSPACE_PATH,
340
+ port: PORT,
341
+ pid: process.pid,
342
+ version: PACKAGE_VERSION,
343
+ startedAt: STARTED_AT,
344
+ });
345
+ }
346
+ catch (err) {
347
+ console.warn(`Failed to update global registry: ${err?.message ?? err}`);
348
+ }
349
+ }
324
350
  // Signal ready immediately so extension can show the webview
325
351
  sendToExtension({ type: 'ready', port: PORT, url });
326
352
  // Initialize FluidCAD server in the background
@@ -333,3 +359,29 @@ httpServer.listen(PORT, () => {
333
359
  broadcastToUI({ type: 'init-complete', success: false, error });
334
360
  });
335
361
  });
362
+ // ---------------------------------------------------------------------------
363
+ // Shutdown — clean up the instance file and registry entry
364
+ // ---------------------------------------------------------------------------
365
+ let cleanedUp = false;
366
+ function cleanupDiscovery() {
367
+ if (cleanedUp || !WORKSPACE_PATH) {
368
+ return;
369
+ }
370
+ cleanedUp = true;
371
+ deleteInstanceFile(WORKSPACE_PATH, process.pid);
372
+ try {
373
+ removeInstance(WORKSPACE_PATH, process.pid);
374
+ }
375
+ catch {
376
+ // Registry cleanup is best-effort; stale entries are pruned by readers.
377
+ }
378
+ }
379
+ process.on('exit', cleanupDiscovery);
380
+ process.on('SIGINT', () => {
381
+ cleanupDiscovery();
382
+ process.exit(0);
383
+ });
384
+ process.on('SIGTERM', () => {
385
+ cleanupDiscovery();
386
+ process.exit(0);
387
+ });
@@ -0,0 +1,31 @@
1
+ export declare const INSTANCE_DIR_NAME = ".fluidcad";
2
+ export declare const INSTANCE_FILE_NAME = "instance.json";
3
+ export type InstanceFile = {
4
+ /** Schema version; bump when changing the shape. */
5
+ schemaVersion: 1;
6
+ /** Port the HTTP+WS server is listening on. */
7
+ port: number;
8
+ /** PID of the server process — used by MCP for liveness probes. */
9
+ pid: number;
10
+ /** Absolute path to the workspace this instance serves. */
11
+ workspacePath: string;
12
+ /** FluidCAD package version. */
13
+ version: string;
14
+ /** ISO 8601 timestamp captured when the server began listening. */
15
+ startedAt: string;
16
+ };
17
+ export declare function instanceFilePath(workspacePath: string): string;
18
+ /**
19
+ * Atomically write the instance file. Uses a PID-suffixed tmp file plus
20
+ * `renameSync` so a concurrent reader never observes a half-written file.
21
+ */
22
+ export declare function writeInstanceFile(entry: InstanceFile): void;
23
+ /**
24
+ * Best-effort delete; never throws. Called from SIGINT/SIGTERM and `exit`
25
+ * handlers, where we cannot afford to interrupt shutdown over an I/O error.
26
+ *
27
+ * `expectedPid` guards against deleting another process's file on race —
28
+ * e.g., a second window started for the same workspace after a crash.
29
+ */
30
+ export declare function deleteInstanceFile(workspacePath: string, expectedPid: number): void;
31
+ export declare function readInstanceFile(workspacePath: string): InstanceFile | null;
@@ -0,0 +1,73 @@
1
+ // Per-workspace instance discovery file at <workspace>/.fluidcad/instance.json.
2
+ //
3
+ // Written when the HTTP server is listening, deleted on graceful shutdown. A
4
+ // standalone MCP process reads this file to learn the workspace's random port
5
+ // without scanning. The on-disk format is intentionally small and stable.
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ export const INSTANCE_DIR_NAME = '.fluidcad';
9
+ export const INSTANCE_FILE_NAME = 'instance.json';
10
+ function instanceDir(workspacePath) {
11
+ return path.join(workspacePath, INSTANCE_DIR_NAME);
12
+ }
13
+ export function instanceFilePath(workspacePath) {
14
+ return path.join(instanceDir(workspacePath), INSTANCE_FILE_NAME);
15
+ }
16
+ /**
17
+ * Atomically write the instance file. Uses a PID-suffixed tmp file plus
18
+ * `renameSync` so a concurrent reader never observes a half-written file.
19
+ */
20
+ export function writeInstanceFile(entry) {
21
+ const dir = instanceDir(entry.workspacePath);
22
+ fs.mkdirSync(dir, { recursive: true });
23
+ const destination = path.join(dir, INSTANCE_FILE_NAME);
24
+ const tmp = path.join(dir, `${INSTANCE_FILE_NAME}.${entry.pid}.tmp`);
25
+ const payload = JSON.stringify(entry, null, 2) + '\n';
26
+ fs.writeFileSync(tmp, payload, { encoding: 'utf8', mode: 0o644 });
27
+ fs.renameSync(tmp, destination);
28
+ }
29
+ /**
30
+ * Best-effort delete; never throws. Called from SIGINT/SIGTERM and `exit`
31
+ * handlers, where we cannot afford to interrupt shutdown over an I/O error.
32
+ *
33
+ * `expectedPid` guards against deleting another process's file on race —
34
+ * e.g., a second window started for the same workspace after a crash.
35
+ */
36
+ export function deleteInstanceFile(workspacePath, expectedPid) {
37
+ const file = instanceFilePath(workspacePath);
38
+ try {
39
+ const existing = readInstanceFile(workspacePath);
40
+ if (existing && existing.pid !== expectedPid) {
41
+ return;
42
+ }
43
+ fs.unlinkSync(file);
44
+ }
45
+ catch {
46
+ // File missing, permissions, partial write — nothing useful to do during shutdown.
47
+ }
48
+ }
49
+ export function readInstanceFile(workspacePath) {
50
+ try {
51
+ const raw = fs.readFileSync(instanceFilePath(workspacePath), 'utf8');
52
+ const parsed = JSON.parse(raw);
53
+ if (!isInstanceFile(parsed)) {
54
+ return null;
55
+ }
56
+ return parsed;
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
62
+ function isInstanceFile(value) {
63
+ if (typeof value !== 'object' || value === null) {
64
+ return false;
65
+ }
66
+ const v = value;
67
+ return (v.schemaVersion === 1 &&
68
+ typeof v.port === 'number' &&
69
+ typeof v.pid === 'number' &&
70
+ typeof v.workspacePath === 'string' &&
71
+ typeof v.version === 'string' &&
72
+ typeof v.startedAt === 'string');
73
+ }
@@ -0,0 +1,15 @@
1
+ export type MissingImport = {
2
+ symbol: string;
3
+ module: string;
4
+ /** Zero-based line where the unbound use first appears. */
5
+ line: number;
6
+ /** Zero-based UTF-16 column where the unbound use first appears. */
7
+ column: number;
8
+ };
9
+ export type LintFluidJsResult = {
10
+ /** Free-variable usages of known symbols that lack an import. */
11
+ missing: MissingImport[];
12
+ /** A copy-pasteable block of suggested import statements, grouped by module. */
13
+ suggestion: string;
14
+ };
15
+ export declare function lintFluidJs(code: string): Promise<LintFluidJsResult>;