fluidcad 0.0.32 → 0.0.34
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.
- package/README.md +3 -2
- package/bin/commands/init.js +55 -0
- package/bin/commands/mcp.js +33 -0
- package/bin/commands/serve.js +77 -0
- package/bin/fluidcad.js +15 -107
- package/lib/dist/common/scene-object.d.ts +4 -1
- package/lib/dist/common/scene-object.js +9 -2
- package/lib/dist/common/solid.d.ts +4 -1
- package/lib/dist/common/solid.js +13 -0
- package/lib/dist/core/2d/tarc.d.ts +20 -2
- package/lib/dist/core/2d/tarc.js +24 -0
- package/lib/dist/core/index.d.ts +2 -1
- package/lib/dist/core/index.js +1 -0
- package/lib/dist/core/interfaces.d.ts +107 -2
- package/lib/dist/core/load.d.ts +2 -2
- package/lib/dist/core/repeat.js +62 -46
- package/lib/dist/core/rib.d.ts +18 -0
- package/lib/dist/core/rib.js +37 -0
- package/lib/dist/features/2d/arc.d.ts +8 -2
- package/lib/dist/features/2d/arc.js +94 -17
- package/lib/dist/features/2d/back.js +3 -2
- package/lib/dist/features/2d/sketch.d.ts +4 -0
- package/lib/dist/features/2d/sketch.js +21 -0
- package/lib/dist/features/2d/tarc-constrained.d.ts +2 -0
- package/lib/dist/features/2d/tarc-constrained.js +8 -0
- package/lib/dist/features/2d/tarc-radius-to-object.d.ts +16 -0
- package/lib/dist/features/2d/tarc-radius-to-object.js +58 -0
- package/lib/dist/features/2d/tarc-to-object.d.ts +18 -0
- package/lib/dist/features/2d/tarc-to-object.js +66 -0
- package/lib/dist/features/2d/tarc-to-point-tangent.d.ts +2 -0
- package/lib/dist/features/2d/tarc-to-point-tangent.js +3 -0
- package/lib/dist/features/2d/tarc-to-point.d.ts +2 -0
- package/lib/dist/features/2d/tarc-to-point.js +3 -0
- package/lib/dist/features/2d/tarc-with-tangent.d.ts +2 -0
- package/lib/dist/features/2d/tarc-with-tangent.js +3 -0
- package/lib/dist/features/2d/tarc.d.ts +2 -0
- package/lib/dist/features/2d/tarc.js +3 -0
- package/lib/dist/features/extrude-base.d.ts +9 -0
- package/lib/dist/features/extrude-base.js +22 -0
- package/lib/dist/features/extrude-to-face.js +1 -5
- package/lib/dist/features/extrude-two-distances.js +1 -2
- package/lib/dist/features/extrude.js +1 -2
- package/lib/dist/features/load.d.ts +6 -0
- package/lib/dist/features/load.js +53 -1
- package/lib/dist/features/mirror-feature.d.ts +3 -2
- package/lib/dist/features/mirror-feature.js +1 -1
- package/lib/dist/features/repeat-circular.d.ts +3 -3
- package/lib/dist/features/repeat-circular.js +8 -1
- package/lib/dist/features/repeat-linear.d.ts +4 -2
- package/lib/dist/features/repeat-linear.js +10 -1
- package/lib/dist/features/repeat-matrix.d.ts +3 -1
- package/lib/dist/features/repeat-matrix.js +7 -2
- package/lib/dist/features/rib.d.ts +31 -0
- package/lib/dist/features/rib.js +321 -0
- package/lib/dist/features/select.d.ts +1 -0
- package/lib/dist/features/select.js +81 -10
- package/lib/dist/features/shell.d.ts +4 -1
- package/lib/dist/features/shell.js +14 -3
- package/lib/dist/filters/edge/belongs-to-face.d.ts +12 -9
- package/lib/dist/filters/edge/belongs-to-face.js +64 -15
- package/lib/dist/filters/filter-builder-base.d.ts +25 -0
- package/lib/dist/filters/filter-builder-base.js +47 -0
- package/lib/dist/filters/filter.js +39 -14
- package/lib/dist/filters/from-object.d.ts +4 -0
- package/lib/dist/filters/from-object.js +10 -0
- package/lib/dist/helpers/clone-transform.d.ts +2 -1
- package/lib/dist/helpers/scene-helpers.d.ts +1 -1
- package/lib/dist/helpers/scene-helpers.js +146 -12
- package/lib/dist/index.d.ts +7 -1
- package/lib/dist/index.js +3 -3
- package/lib/dist/io/file-import.d.ts +5 -1
- package/lib/dist/io/file-import.js +29 -18
- package/lib/dist/math/lazy-matrix.d.ts +31 -0
- package/lib/dist/math/lazy-matrix.js +66 -0
- package/lib/dist/oc/color-transfer.d.ts +19 -8
- package/lib/dist/oc/color-transfer.js +70 -12
- package/lib/dist/oc/constraints/constraint-solver-adaptor.d.ts +5 -0
- package/lib/dist/oc/constraints/constraint-solver-adaptor.js +16 -0
- package/lib/dist/oc/constraints/constraint-solver.d.ts +4 -0
- package/lib/dist/oc/constraints/curve/curve-constraint-solver.d.ts +4 -0
- package/lib/dist/oc/constraints/curve/curve-constraint-solver.js +3 -0
- package/lib/dist/oc/constraints/geometric/geometric-constraint-solver.d.ts +6 -1
- package/lib/dist/oc/constraints/geometric/geometric-constraint-solver.js +4 -0
- package/lib/dist/oc/constraints/geometric/tangent-arc-from-point-tangent.d.ts +8 -0
- package/lib/dist/oc/constraints/geometric/tangent-arc-from-point-tangent.js +111 -0
- package/lib/dist/oc/constraints/geometric/tangent-arc-radius-to-object.d.ts +8 -0
- package/lib/dist/oc/constraints/geometric/tangent-arc-radius-to-object.js +161 -0
- package/lib/dist/oc/extrude-ops.d.ts +2 -1
- package/lib/dist/oc/extrude-ops.js +51 -2
- package/lib/dist/oc/mesh.d.ts +9 -4
- package/lib/dist/oc/mesh.js +14 -13
- package/lib/dist/oc/rib-ops.d.ts +35 -0
- package/lib/dist/oc/rib-ops.js +619 -0
- package/lib/dist/oc/shell-ops.d.ts +2 -1
- package/lib/dist/oc/shell-ops.js +5 -2
- package/lib/dist/oc/topology-index.d.ts +6 -0
- package/lib/dist/oc/topology-index.js +36 -0
- package/lib/dist/rendering/mesh-builder.d.ts +3 -0
- package/lib/dist/rendering/mesh-builder.js +8 -4
- package/lib/dist/rendering/render-edge.d.ts +2 -1
- package/lib/dist/rendering/render-edge.js +2 -2
- package/lib/dist/rendering/render-face.d.ts +2 -1
- package/lib/dist/rendering/render-face.js +2 -2
- package/lib/dist/rendering/render-solid.d.ts +2 -1
- package/lib/dist/rendering/render-solid.js +3 -5
- package/lib/dist/rendering/render-wire.d.ts +2 -1
- package/lib/dist/rendering/render-wire.js +2 -2
- package/lib/dist/rendering/render.d.ts +4 -0
- package/lib/dist/rendering/render.js +50 -2
- package/lib/dist/rendering/scene-compare.js +3 -0
- package/lib/dist/rendering/scene.d.ts +1 -0
- package/lib/dist/rendering/scene.js +4 -0
- package/lib/dist/scene-manager.d.ts +4 -2
- package/lib/dist/scene-manager.js +12 -4
- package/lib/dist/tests/features/2d/arc.test.js +64 -0
- package/lib/dist/tests/features/2d/back.test.js +17 -1
- package/lib/dist/tests/features/2d/tarc.test.js +157 -0
- package/lib/dist/tests/features/color-lineage.test.js +18 -0
- package/lib/dist/tests/features/filter-positional.test.d.ts +1 -0
- package/lib/dist/tests/features/filter-positional.test.js +129 -0
- package/lib/dist/tests/features/repeat-user-repro.test.d.ts +1 -0
- package/lib/dist/tests/features/repeat-user-repro.test.js +60 -0
- package/lib/dist/tests/features/rib.test.d.ts +1 -0
- package/lib/dist/tests/features/rib.test.js +598 -0
- package/lib/dist/tests/features/shell.test.js +36 -0
- package/lib/dist/tests/global-setup.js +2 -1
- package/lib/dist/tests/helpers/extract-blocks.d.ts +9 -0
- package/lib/dist/tests/helpers/extract-blocks.js +56 -0
- package/lib/dist/tests/llm-docs-examples.test.d.ts +1 -0
- package/lib/dist/tests/llm-docs-examples.test.js +62 -0
- package/lib/dist/tests/scene-compare.test.d.ts +1 -0
- package/lib/dist/tests/scene-compare.test.js +77 -0
- package/lib/dist/tests/setup.js +2 -1
- package/lib/dist/tsconfig.tsbuildinfo +1 -1
- package/llm-docs/.coverage-allowlist.txt +9 -0
- package/llm-docs/api/arc.md +48 -0
- package/llm-docs/api/axis.md +42 -0
- package/llm-docs/api/bezier.md +42 -0
- package/llm-docs/api/booleans.md +44 -0
- package/llm-docs/api/chamfer.md +40 -0
- package/llm-docs/api/circle.md +36 -0
- package/llm-docs/api/color.md +34 -0
- package/llm-docs/api/connect.md +41 -0
- package/llm-docs/api/constraint-qualifiers.md +48 -0
- package/llm-docs/api/copy.md +63 -0
- package/llm-docs/api/cursor-lines.md +50 -0
- package/llm-docs/api/cursor-move.md +61 -0
- package/llm-docs/api/cut.md +55 -0
- package/llm-docs/api/draft.md +36 -0
- package/llm-docs/api/edge-filter.md +57 -0
- package/llm-docs/api/ellipse.md +34 -0
- package/llm-docs/api/extrude.md +74 -0
- package/llm-docs/api/face-filter.md +61 -0
- package/llm-docs/api/fillet.md +51 -0
- package/llm-docs/api/index.json +139 -0
- package/llm-docs/api/line.md +42 -0
- package/llm-docs/api/load.md +37 -0
- package/llm-docs/api/local.md +38 -0
- package/llm-docs/api/loft.md +37 -0
- package/llm-docs/api/mirror.md +44 -0
- package/llm-docs/api/offset.md +36 -0
- package/llm-docs/api/part.md +40 -0
- package/llm-docs/api/plane.md +44 -0
- package/llm-docs/api/polygon.md +37 -0
- package/llm-docs/api/primitive-solids.md +39 -0
- package/llm-docs/api/project-intersect.md +48 -0
- package/llm-docs/api/rect.md +48 -0
- package/llm-docs/api/remove.md +32 -0
- package/llm-docs/api/repeat.md +79 -0
- package/llm-docs/api/revolve.md +38 -0
- package/llm-docs/api/rib.md +40 -0
- package/llm-docs/api/rotate.md +37 -0
- package/llm-docs/api/select.md +41 -0
- package/llm-docs/api/shell.md +41 -0
- package/llm-docs/api/sketch.md +76 -0
- package/llm-docs/api/slot.md +36 -0
- package/llm-docs/api/split-trim.md +42 -0
- package/llm-docs/api/sweep.md +43 -0
- package/llm-docs/api/tarc.md +45 -0
- package/llm-docs/api/tcircle.md +38 -0
- package/llm-docs/api/tline.md +42 -0
- package/llm-docs/api/translate.md +40 -0
- package/llm-docs/api/types/aline.md +35 -0
- package/llm-docs/api/types/arc-angles.md +29 -0
- package/llm-docs/api/types/arc-points.md +48 -0
- package/llm-docs/api/types/axis-like.md +38 -0
- package/llm-docs/api/types/axis.md +21 -0
- package/llm-docs/api/types/boolean-operation.md +50 -0
- package/llm-docs/api/types/circular-repeat-options.md +31 -0
- package/llm-docs/api/types/common.md +32 -0
- package/llm-docs/api/types/cut.md +125 -0
- package/llm-docs/api/types/draft.md +21 -0
- package/llm-docs/api/types/extrudable-geometry.md +23 -0
- package/llm-docs/api/types/extrude.md +194 -0
- package/llm-docs/api/types/geometry.md +51 -0
- package/llm-docs/api/types/hline.md +35 -0
- package/llm-docs/api/types/linear-repeat-options.md +31 -0
- package/llm-docs/api/types/loft.md +154 -0
- package/llm-docs/api/types/mirror.md +35 -0
- package/llm-docs/api/types/offset.md +31 -0
- package/llm-docs/api/types/plane-like.md +35 -0
- package/llm-docs/api/types/plane-transform-options.md +29 -0
- package/llm-docs/api/types/plane.md +21 -0
- package/llm-docs/api/types/point-like.md +22 -0
- package/llm-docs/api/types/point2dlike.md +26 -0
- package/llm-docs/api/types/polygon.md +46 -0
- package/llm-docs/api/types/rect.md +128 -0
- package/llm-docs/api/types/revolve.md +102 -0
- package/llm-docs/api/types/rib.md +133 -0
- package/llm-docs/api/types/scene-object.md +33 -0
- package/llm-docs/api/types/select.md +21 -0
- package/llm-docs/api/types/shell.md +54 -0
- package/llm-docs/api/types/slot.md +43 -0
- package/llm-docs/api/types/sweep.md +189 -0
- package/llm-docs/api/types/tangent-arc-two-objects.md +46 -0
- package/llm-docs/api/types/transformable.md +93 -0
- package/llm-docs/api/types/trim.md +27 -0
- package/llm-docs/api/types/two-objects-tangent-line.md +46 -0
- package/llm-docs/api/types/vertex.md +17 -0
- package/llm-docs/api/types/vline.md +35 -0
- package/llm-docs/concepts/coordinate-system.md +45 -0
- package/llm-docs/concepts/history-and-rollback.md +40 -0
- package/llm-docs/concepts/last-selection.md +49 -0
- package/llm-docs/concepts/scene-graph.md +37 -0
- package/llm-docs/index.json +1750 -0
- package/mcp/dist/client.d.ts +64 -0
- package/mcp/dist/client.js +248 -0
- package/mcp/dist/discovery.d.ts +11 -0
- package/mcp/dist/discovery.js +78 -0
- package/mcp/dist/docs-index.d.ts +81 -0
- package/mcp/dist/docs-index.js +261 -0
- package/mcp/dist/resources.d.ts +4 -0
- package/mcp/dist/resources.js +115 -0
- package/mcp/dist/server.d.ts +12 -0
- package/mcp/dist/server.js +489 -0
- package/mcp/dist/tools/coordination.d.ts +9 -0
- package/mcp/dist/tools/coordination.js +46 -0
- package/mcp/dist/tools/docs.d.ts +66 -0
- package/mcp/dist/tools/docs.js +122 -0
- package/mcp/dist/tools/engine.d.ts +56 -0
- package/mcp/dist/tools/engine.js +145 -0
- package/mcp/dist/tools/inspection.d.ts +75 -0
- package/mcp/dist/tools/inspection.js +121 -0
- package/mcp/dist/tools/screenshot.d.ts +63 -0
- package/mcp/dist/tools/screenshot.js +263 -0
- package/mcp/dist/tools/source.d.ts +84 -0
- package/mcp/dist/tools/source.js +434 -0
- package/mcp/dist/tools/workspaces.d.ts +13 -0
- package/mcp/dist/tools/workspaces.js +33 -0
- package/mcp/dist/types.d.ts +18 -0
- package/mcp/dist/types.js +11 -0
- package/package.json +19 -5
- package/server/dist/code-editor.d.ts +36 -0
- package/server/dist/code-editor.js +8 -0
- package/server/dist/fluidcad-server.d.ts +50 -0
- package/server/dist/fluidcad-server.js +153 -1
- package/server/dist/global-registry.d.ts +30 -0
- package/server/dist/global-registry.js +126 -0
- package/server/dist/index.js +171 -26
- package/server/dist/instance-file.d.ts +31 -0
- package/server/dist/instance-file.js +73 -0
- package/server/dist/lint-fluid-js.d.ts +15 -0
- package/server/dist/lint-fluid-js.js +271 -0
- package/server/dist/routes/editor.d.ts +24 -0
- package/server/dist/routes/editor.js +44 -0
- package/server/dist/routes/export.d.ts +1 -1
- package/server/dist/routes/export.js +45 -8
- package/server/dist/routes/health.d.ts +7 -0
- package/server/dist/routes/health.js +14 -0
- package/server/dist/routes/lint.d.ts +10 -0
- package/server/dist/routes/lint.js +28 -0
- package/server/dist/routes/render.d.ts +33 -0
- package/server/dist/routes/render.js +34 -0
- package/server/dist/routes/scene.d.ts +5 -0
- package/server/dist/routes/scene.js +48 -0
- package/server/dist/routes/screenshot.js +68 -1
- package/server/dist/ws-protocol.d.ts +56 -2
- package/ui/dist/assets/{index-DMw0OYCF.js → index-BdqrMDRu.js} +30 -30
- package/ui/dist/index.html +1 -1
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// Screenshot tools — render the running FluidCAD scene as a PNG and return
|
|
2
|
+
// MCP `image` content blocks. Views are stateless: the agent picks a vantage
|
|
3
|
+
// (named view, orbit-from-current, or look-from) and the user's interactive
|
|
4
|
+
// camera is never touched.
|
|
5
|
+
import { resolveClient } from "./inspection.js";
|
|
6
|
+
import { HttpError } from "../client.js";
|
|
7
|
+
import { err, ok } from "../types.js";
|
|
8
|
+
const NAMED_VIEWS = [
|
|
9
|
+
'front', 'back', 'left', 'right', 'top', 'bottom',
|
|
10
|
+
'iso-ftr', 'iso-fbr', 'iso-ftl', 'iso-fbl',
|
|
11
|
+
'iso-btr', 'iso-bbr', 'iso-btl', 'iso-bbl',
|
|
12
|
+
];
|
|
13
|
+
export async function screenshot(input) {
|
|
14
|
+
const validated = validateScreenshotInput(input);
|
|
15
|
+
if (validated.ok === false) {
|
|
16
|
+
return validated;
|
|
17
|
+
}
|
|
18
|
+
return runScreenshot(input, validated.data, /* multi */ false);
|
|
19
|
+
}
|
|
20
|
+
export async function screenshotMulti(input) {
|
|
21
|
+
const validated = validateScreenshotInput({ ...input, view: { kind: 'current' } });
|
|
22
|
+
if (validated.ok === false) {
|
|
23
|
+
return validated;
|
|
24
|
+
}
|
|
25
|
+
return runScreenshot(input, validated.data, /* multi */ true);
|
|
26
|
+
}
|
|
27
|
+
export async function screenshotShape(input) {
|
|
28
|
+
if (!input?.shapeId || typeof input.shapeId !== 'string') {
|
|
29
|
+
return err('invalid-input', '`shapeId` is required and must be a non-empty string.');
|
|
30
|
+
}
|
|
31
|
+
const margin = input.margin ?? 1.2;
|
|
32
|
+
if (typeof margin !== 'number' || !Number.isFinite(margin) || margin <= 0) {
|
|
33
|
+
return err('invalid-input', '`margin` must be a positive finite number when provided.');
|
|
34
|
+
}
|
|
35
|
+
const resolved = resolveClient(input);
|
|
36
|
+
if (resolved.ok === false) {
|
|
37
|
+
return resolved;
|
|
38
|
+
}
|
|
39
|
+
const { client } = resolved.data;
|
|
40
|
+
try {
|
|
41
|
+
// Fetch the shape's bounding box so we can build an iso framing.
|
|
42
|
+
const props = await client.getJson(`/api/shape-properties?shapeId=${encodeURIComponent(input.shapeId)}`);
|
|
43
|
+
const bbox = extractBoundingBox(props);
|
|
44
|
+
if (!bbox) {
|
|
45
|
+
return err('invalid-input', `Shape "${input.shapeId}" has no bounding box — cannot frame it.`, { properties: props });
|
|
46
|
+
}
|
|
47
|
+
const cx = (bbox.min[0] + bbox.max[0]) / 2;
|
|
48
|
+
const cy = (bbox.min[1] + bbox.max[1]) / 2;
|
|
49
|
+
const cz = (bbox.min[2] + bbox.max[2]) / 2;
|
|
50
|
+
const sx = bbox.max[0] - bbox.min[0];
|
|
51
|
+
const sy = bbox.max[1] - bbox.min[1];
|
|
52
|
+
const sz = bbox.max[2] - bbox.min[2];
|
|
53
|
+
const diameter = Math.sqrt(sx * sx + sy * sy + sz * sz);
|
|
54
|
+
const distance = Math.max(diameter * margin, 1);
|
|
55
|
+
// Iso-ftr direction: (1, -1, 1) / sqrt(3).
|
|
56
|
+
const k = distance / Math.sqrt(3);
|
|
57
|
+
const view = {
|
|
58
|
+
kind: 'look-from',
|
|
59
|
+
eye: [cx + k, cy - k, cz + k],
|
|
60
|
+
target: [cx, cy, cz],
|
|
61
|
+
};
|
|
62
|
+
const body = { view };
|
|
63
|
+
if (input.width !== undefined) {
|
|
64
|
+
body.width = input.width;
|
|
65
|
+
}
|
|
66
|
+
if (input.height !== undefined) {
|
|
67
|
+
body.height = input.height;
|
|
68
|
+
}
|
|
69
|
+
if (input.showGrid !== undefined) {
|
|
70
|
+
body.showGrid = input.showGrid;
|
|
71
|
+
}
|
|
72
|
+
if (input.showAxes !== undefined) {
|
|
73
|
+
body.showAxes = input.showAxes;
|
|
74
|
+
}
|
|
75
|
+
if (input.transparent !== undefined) {
|
|
76
|
+
body.transparent = input.transparent;
|
|
77
|
+
}
|
|
78
|
+
return await postScreenshot(client, body);
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
return wrapError(e);
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
await client.close().catch(() => { });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export async function getCameraState(input) {
|
|
88
|
+
const resolved = resolveClient(input);
|
|
89
|
+
if (resolved.ok === false) {
|
|
90
|
+
return resolved;
|
|
91
|
+
}
|
|
92
|
+
const { client } = resolved.data;
|
|
93
|
+
try {
|
|
94
|
+
const data = await client.getJson('/api/camera/state');
|
|
95
|
+
return ok({
|
|
96
|
+
position: data.position,
|
|
97
|
+
target: data.target,
|
|
98
|
+
up: data.up,
|
|
99
|
+
projection: data.projection,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
return wrapError(e);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
await client.close().catch(() => { });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function validateScreenshotInput(input) {
|
|
110
|
+
const opts = {};
|
|
111
|
+
if (input.width !== undefined) {
|
|
112
|
+
const w = input.width;
|
|
113
|
+
if (typeof w !== 'number' || !Number.isInteger(w) || w < 1 || w > 8192) {
|
|
114
|
+
return err('invalid-input', '`width` must be an integer between 1 and 8192.');
|
|
115
|
+
}
|
|
116
|
+
opts.width = w;
|
|
117
|
+
}
|
|
118
|
+
if (input.height !== undefined) {
|
|
119
|
+
const h = input.height;
|
|
120
|
+
if (typeof h !== 'number' || !Number.isInteger(h) || h < 1 || h > 8192) {
|
|
121
|
+
return err('invalid-input', '`height` must be an integer between 1 and 8192.');
|
|
122
|
+
}
|
|
123
|
+
opts.height = h;
|
|
124
|
+
}
|
|
125
|
+
for (const k of ['showGrid', 'showAxes', 'transparent', 'autoCrop', 'fitToModel']) {
|
|
126
|
+
const v = input[k];
|
|
127
|
+
if (v !== undefined) {
|
|
128
|
+
if (typeof v !== 'boolean') {
|
|
129
|
+
return err('invalid-input', `\`${k}\` must be a boolean when provided.`);
|
|
130
|
+
}
|
|
131
|
+
opts[k] = v;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (input.margin !== undefined) {
|
|
135
|
+
const m = input.margin;
|
|
136
|
+
if (typeof m !== 'number' || !Number.isFinite(m) || m < 0) {
|
|
137
|
+
return err('invalid-input', '`margin` must be a non-negative finite number.');
|
|
138
|
+
}
|
|
139
|
+
opts.margin = m;
|
|
140
|
+
}
|
|
141
|
+
const view = input.view;
|
|
142
|
+
if (view !== undefined) {
|
|
143
|
+
const validatedView = validateView(view);
|
|
144
|
+
if (typeof validatedView === 'string') {
|
|
145
|
+
return err('invalid-input', validatedView);
|
|
146
|
+
}
|
|
147
|
+
opts.view = validatedView;
|
|
148
|
+
}
|
|
149
|
+
return ok(opts);
|
|
150
|
+
}
|
|
151
|
+
function validateView(raw) {
|
|
152
|
+
if (raw === null || typeof raw !== 'object') {
|
|
153
|
+
return '`view` must be an object.';
|
|
154
|
+
}
|
|
155
|
+
const v = raw;
|
|
156
|
+
switch (v.kind) {
|
|
157
|
+
case 'current':
|
|
158
|
+
return { kind: 'current' };
|
|
159
|
+
case 'named': {
|
|
160
|
+
if (typeof v.name !== 'string' || !NAMED_VIEWS.includes(v.name)) {
|
|
161
|
+
return `\`view.name\` must be one of: ${NAMED_VIEWS.join(', ')}.`;
|
|
162
|
+
}
|
|
163
|
+
return { kind: 'named', name: v.name };
|
|
164
|
+
}
|
|
165
|
+
case 'orbit-from-current': {
|
|
166
|
+
if (typeof v.azimuthDeg !== 'number' || !Number.isFinite(v.azimuthDeg)) {
|
|
167
|
+
return '`view.azimuthDeg` must be a finite number.';
|
|
168
|
+
}
|
|
169
|
+
if (typeof v.elevationDeg !== 'number' || !Number.isFinite(v.elevationDeg)) {
|
|
170
|
+
return '`view.elevationDeg` must be a finite number.';
|
|
171
|
+
}
|
|
172
|
+
return { kind: 'orbit-from-current', azimuthDeg: v.azimuthDeg, elevationDeg: v.elevationDeg };
|
|
173
|
+
}
|
|
174
|
+
case 'look-from': {
|
|
175
|
+
if (!isVec3(v.eye)) {
|
|
176
|
+
return '`view.eye` must be a 3-element array of finite numbers.';
|
|
177
|
+
}
|
|
178
|
+
if (v.target !== undefined && !isVec3(v.target)) {
|
|
179
|
+
return '`view.target` must be a 3-element array of finite numbers when provided.';
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
kind: 'look-from',
|
|
183
|
+
eye: v.eye,
|
|
184
|
+
target: v.target,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
default:
|
|
188
|
+
return '`view.kind` must be one of: current, named, orbit-from-current, look-from.';
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function isVec3(value) {
|
|
192
|
+
return (Array.isArray(value) &&
|
|
193
|
+
value.length === 3 &&
|
|
194
|
+
value.every((n) => typeof n === 'number' && Number.isFinite(n)));
|
|
195
|
+
}
|
|
196
|
+
async function runScreenshot(input, opts, multi) {
|
|
197
|
+
const resolved = resolveClient(input);
|
|
198
|
+
if (resolved.ok === false) {
|
|
199
|
+
return resolved;
|
|
200
|
+
}
|
|
201
|
+
const { client } = resolved.data;
|
|
202
|
+
const body = { ...opts };
|
|
203
|
+
if (multi) {
|
|
204
|
+
body.multi = true;
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
return await postScreenshot(client, body);
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
return wrapError(e);
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
await client.close().catch(() => { });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function postScreenshot(client, body) {
|
|
217
|
+
const res = await client.postRaw('/api/screenshot', body);
|
|
218
|
+
if (res.statusCode >= 400) {
|
|
219
|
+
const text = res.data.toString('utf8');
|
|
220
|
+
return err('http-error', `HTTP ${res.statusCode}: ${text.slice(0, 200)}`, {
|
|
221
|
+
statusCode: res.statusCode,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
const mime = res.contentType.split(';')[0].trim() || 'image/png';
|
|
225
|
+
return ok({
|
|
226
|
+
image: {
|
|
227
|
+
mimeType: mime,
|
|
228
|
+
base64: res.data.toString('base64'),
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function wrapError(e) {
|
|
233
|
+
if (e instanceof HttpError) {
|
|
234
|
+
return err('http-error', `HTTP ${e.statusCode}: ${e.body.slice(0, 200)}`, {
|
|
235
|
+
statusCode: e.statusCode,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return err('internal', e?.message ?? String(e));
|
|
239
|
+
}
|
|
240
|
+
function extractBoundingBox(props) {
|
|
241
|
+
if (!props || typeof props !== 'object') {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
const bbox = props.boundingBox ?? props.bbox ?? null;
|
|
245
|
+
if (!bbox || typeof bbox !== 'object') {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
const min = bbox.min;
|
|
249
|
+
const max = bbox.max;
|
|
250
|
+
const isTriple = (v) => Array.isArray(v) && v.length === 3 && v.every((n) => typeof n === 'number' && Number.isFinite(n));
|
|
251
|
+
if (isTriple(min) && isTriple(max)) {
|
|
252
|
+
return { min: min, max: max };
|
|
253
|
+
}
|
|
254
|
+
// Some payloads use `xMin/yMin/zMin` etc.
|
|
255
|
+
const fields = ['xMin', 'yMin', 'zMin', 'xMax', 'yMax', 'zMax'];
|
|
256
|
+
if (fields.every((f) => typeof bbox[f] === 'number')) {
|
|
257
|
+
return {
|
|
258
|
+
min: [bbox.xMin, bbox.yMin, bbox.zMin],
|
|
259
|
+
max: [bbox.xMax, bbox.yMax, bbox.zMax],
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { type ToolResult } from '../types.ts';
|
|
2
|
+
export type WorkspaceArg = {
|
|
3
|
+
workspace?: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Render outcome as reported by `POST /api/render`. Mirrored from
|
|
7
|
+
* `server/src/routes/render.ts` — kept hand-typed here so the MCP package
|
|
8
|
+
* doesn't take a build-time dep on the server package.
|
|
9
|
+
*/
|
|
10
|
+
export type RenderOutcome = {
|
|
11
|
+
state: 'rendered';
|
|
12
|
+
version: number;
|
|
13
|
+
absPath: string;
|
|
14
|
+
durationMs: number;
|
|
15
|
+
} | {
|
|
16
|
+
state: 'compile-error';
|
|
17
|
+
version: number;
|
|
18
|
+
durationMs: number;
|
|
19
|
+
compileError: {
|
|
20
|
+
message: string;
|
|
21
|
+
filePath?: string;
|
|
22
|
+
sourceLocation?: {
|
|
23
|
+
filePath: string;
|
|
24
|
+
line: number;
|
|
25
|
+
column: number;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
} | {
|
|
29
|
+
state: 'superseded';
|
|
30
|
+
version: number;
|
|
31
|
+
durationMs: number;
|
|
32
|
+
} | {
|
|
33
|
+
state: 'no-scene-manager';
|
|
34
|
+
version: number;
|
|
35
|
+
durationMs: number;
|
|
36
|
+
} | {
|
|
37
|
+
state: 'render-failed';
|
|
38
|
+
error: string;
|
|
39
|
+
};
|
|
40
|
+
export type ReadFileInput = WorkspaceArg & {
|
|
41
|
+
path: string;
|
|
42
|
+
};
|
|
43
|
+
export type ReadFileOutput = {
|
|
44
|
+
path: string;
|
|
45
|
+
content: string;
|
|
46
|
+
};
|
|
47
|
+
export declare function readFile(input: ReadFileInput): Promise<ToolResult<ReadFileOutput>>;
|
|
48
|
+
export type WriteFileInput = WorkspaceArg & {
|
|
49
|
+
path: string;
|
|
50
|
+
content: string;
|
|
51
|
+
force?: boolean;
|
|
52
|
+
};
|
|
53
|
+
export type WriteFileOutput = {
|
|
54
|
+
path: string;
|
|
55
|
+
bytesWritten: number;
|
|
56
|
+
render: RenderOutcome;
|
|
57
|
+
};
|
|
58
|
+
export declare function writeFile(input: WriteFileInput): Promise<ToolResult<WriteFileOutput>>;
|
|
59
|
+
export type Position = {
|
|
60
|
+
line: number;
|
|
61
|
+
column: number;
|
|
62
|
+
};
|
|
63
|
+
export type EditRangeInput = WorkspaceArg & {
|
|
64
|
+
path: string;
|
|
65
|
+
start: Position;
|
|
66
|
+
end: Position;
|
|
67
|
+
newText: string;
|
|
68
|
+
force?: boolean;
|
|
69
|
+
};
|
|
70
|
+
export type EditRangeOutput = {
|
|
71
|
+
path: string;
|
|
72
|
+
bytesWritten: number;
|
|
73
|
+
replacedRange: {
|
|
74
|
+
start: Position;
|
|
75
|
+
end: Position;
|
|
76
|
+
};
|
|
77
|
+
render: RenderOutcome;
|
|
78
|
+
};
|
|
79
|
+
export declare function editRange(input: EditRangeInput): Promise<ToolResult<EditRangeOutput>>;
|
|
80
|
+
export type ListFluidFilesInput = WorkspaceArg;
|
|
81
|
+
export type ListFluidFilesOutput = {
|
|
82
|
+
files: string[];
|
|
83
|
+
};
|
|
84
|
+
export declare function listFluidFiles(input: ListFluidFilesInput): Promise<ToolResult<ListFluidFilesOutput>>;
|