@pascal-app/mcp 0.3.1 → 1.0.0-beta.1
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 +2 -1
- package/dist/bridge/scene-bridge.d.ts.map +1 -1
- package/dist/bridge/scene-bridge.js +6 -0
- package/dist/operations/scene-operations.js +7 -6
- package/dist/resources/constraints.js +1 -1
- package/dist/templates/empty-studio.d.ts.map +1 -1
- package/dist/templates/empty-studio.js +2 -4
- package/dist/templates/garden-house.js +1 -1
- package/dist/templates/two-bedroom.js +1 -1
- package/dist/tools/construction-tools.d.ts +21 -21
- package/dist/tools/construction-tools.d.ts.map +1 -1
- package/dist/tools/construction-tools.js +81 -33
- package/dist/tools/create-level.d.ts +1 -1
- package/dist/tools/create-level.d.ts.map +1 -1
- package/dist/tools/create-level.js +19 -7
- package/dist/tools/create-wall.d.ts +2 -2
- package/dist/tools/create-wall.d.ts.map +1 -1
- package/dist/tools/create-wall.js +6 -2
- package/dist/tools/cut-opening.d.ts +3 -3
- package/dist/tools/cut-opening.d.ts.map +1 -1
- package/dist/tools/cut-opening.js +3 -2
- package/dist/tools/describe-node.d.ts.map +1 -1
- package/dist/tools/describe-node.js +14 -5
- package/dist/tools/find-nodes.d.ts +2 -2
- package/dist/tools/find-nodes.js +1 -1
- package/dist/tools/measurement.d.ts +37 -0
- package/dist/tools/measurement.d.ts.map +1 -0
- package/dist/tools/measurement.js +94 -0
- package/dist/tools/photo-to-scene/photo-to-scene.d.ts +2 -2
- package/dist/tools/photo-to-scene/photo-to-scene.d.ts.map +1 -1
- package/dist/tools/photo-to-scene/photo-to-scene.js +14 -5
- package/dist/tools/place-item.d.ts +1 -1
- package/dist/tools/place-item.d.ts.map +1 -1
- package/dist/tools/place-item.js +2 -1
- package/dist/tools/room-tools.d.ts +8 -8
- package/dist/tools/room-tools.d.ts.map +1 -1
- package/dist/tools/room-tools.js +17 -7
- package/dist/tools/scene-query.d.ts +4 -0
- package/dist/tools/scene-query.d.ts.map +1 -1
- package/dist/tools/scene-query.js +24 -11
- package/package.json +5 -4
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { resolveCeilingHeight } from '@pascal-app/core';
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import { ErrorCode, throwMcpError } from './errors';
|
|
4
|
+
import { resolveReportedWallHeight } from './scene-query';
|
|
3
5
|
import { NodeIdSchema } from './schemas';
|
|
4
6
|
export const describeNodeInput = {
|
|
5
7
|
id: NodeIdSchema,
|
|
@@ -17,13 +19,13 @@ export const describeNodeOutput = {
|
|
|
17
19
|
* Build a short, human-readable one-liner describing the node.
|
|
18
20
|
* Covers the common shapes; falls back to a generic sentence otherwise.
|
|
19
21
|
*/
|
|
20
|
-
function describe(node) {
|
|
22
|
+
function describe(node, bridge) {
|
|
21
23
|
switch (node.type) {
|
|
22
24
|
case 'wall': {
|
|
23
25
|
const [x1, z1] = node.start;
|
|
24
26
|
const [x2, z2] = node.end;
|
|
25
27
|
const t = node.thickness ?? 0.1;
|
|
26
|
-
const h = node
|
|
28
|
+
const h = resolveReportedWallHeight(bridge, node);
|
|
27
29
|
return `Wall from (${x1},${z1}) to (${x2},${z2}), thickness ${t.toFixed(2)}m, height ${h.toFixed(2)}m`;
|
|
28
30
|
}
|
|
29
31
|
case 'level':
|
|
@@ -39,7 +41,7 @@ function describe(node) {
|
|
|
39
41
|
case 'slab':
|
|
40
42
|
return `Slab with ${node.polygon.length} vertices`;
|
|
41
43
|
case 'ceiling':
|
|
42
|
-
return `Ceiling with ${node.polygon.length} vertices, height ${node.
|
|
44
|
+
return `Ceiling with ${node.polygon.length} vertices, height ${resolveCeilingHeight(node, bridge.getNodes()).toFixed(2)}m`;
|
|
43
45
|
case 'door':
|
|
44
46
|
return `Door (${node.width.toFixed(2)}m x ${node.height.toFixed(2)}m)`;
|
|
45
47
|
case 'window':
|
|
@@ -69,14 +71,21 @@ export function registerDescribeNode(server, bridge) {
|
|
|
69
71
|
const children = bridge.getChildren(id);
|
|
70
72
|
const childrenIds = children.map((n) => n.id);
|
|
71
73
|
const n = node;
|
|
74
|
+
const properties = n.type === 'wall'
|
|
75
|
+
? {
|
|
76
|
+
...n,
|
|
77
|
+
resolvedHeight: resolveReportedWallHeight(bridge, n),
|
|
78
|
+
heightIsExplicit: n.height !== undefined,
|
|
79
|
+
}
|
|
80
|
+
: n;
|
|
72
81
|
const payload = {
|
|
73
82
|
id: n.id,
|
|
74
83
|
type: n.type,
|
|
75
84
|
parentId: (n.parentId ?? null),
|
|
76
85
|
ancestryIds,
|
|
77
86
|
childrenIds,
|
|
78
|
-
properties:
|
|
79
|
-
description: describe(n),
|
|
87
|
+
properties: properties,
|
|
88
|
+
description: describe(n, bridge),
|
|
80
89
|
};
|
|
81
90
|
return {
|
|
82
91
|
content: [{ type: 'text', text: JSON.stringify(payload) }],
|
|
@@ -8,9 +8,10 @@ export declare const findNodesInput: {
|
|
|
8
8
|
level: "level";
|
|
9
9
|
wall: "wall";
|
|
10
10
|
fence: "fence";
|
|
11
|
+
slab: "slab";
|
|
12
|
+
door: "door";
|
|
11
13
|
item: "item";
|
|
12
14
|
zone: "zone";
|
|
13
|
-
slab: "slab";
|
|
14
15
|
stair: "stair";
|
|
15
16
|
ceiling: "ceiling";
|
|
16
17
|
roof: "roof";
|
|
@@ -19,7 +20,6 @@ export declare const findNodesInput: {
|
|
|
19
20
|
scan: "scan";
|
|
20
21
|
guide: "guide";
|
|
21
22
|
window: "window";
|
|
22
|
-
door: "door";
|
|
23
23
|
}>>;
|
|
24
24
|
parentId: z.ZodOptional<z.ZodString>;
|
|
25
25
|
levelId: z.ZodOptional<z.ZodString>;
|
package/dist/tools/find-nodes.js
CHANGED
|
@@ -79,7 +79,7 @@ export function registerFindNodes(server, bridge) {
|
|
|
79
79
|
// Zone-polygon filter: point-in-polygon on a representative 2D point.
|
|
80
80
|
if (zoneId) {
|
|
81
81
|
const zone = bridge.getNode(zoneId);
|
|
82
|
-
if (
|
|
82
|
+
if (zone?.type !== 'zone') {
|
|
83
83
|
// Unknown zoneId → return empty list rather than throw; matches
|
|
84
84
|
// typical "filter" semantics.
|
|
85
85
|
results = [];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Kind } from '@pascal-app/lingo';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* A zod field for a measurement tool argument that a model may emit as a bare
|
|
5
|
+
* number OR as natural language. `measurement('length', 'm')` accepts `0.15`,
|
|
6
|
+
* `"6 in"`, `"180cm"`, `"2 ft 3 in"`; `measurement('angle', 'deg')` accepts
|
|
7
|
+
* `45`, `"45°"`, `"1.57rad"`, `"0.25 turn"`. The value is canonicalized (via
|
|
8
|
+
* `@pascal-app/lingo`) to a number in `unit` — the exact unit the tool handler
|
|
9
|
+
* already expects — so no handler change is needed. `min`/`max` (in `unit`)
|
|
10
|
+
* reject out-of-range values with a model-readable message.
|
|
11
|
+
*
|
|
12
|
+
* The emitted JSON Schema is `number | string`, so the model is free to answer
|
|
13
|
+
* in whatever unit it is thinking in; AI SDK v6 applies the transform and
|
|
14
|
+
* forwards the canonical number to the tool executor.
|
|
15
|
+
*
|
|
16
|
+
* Tool-boundary safety: genuinely ambiguous separators (`"1,234"`, which could
|
|
17
|
+
* mean 1234 or 1.234) are rejected rather than silently absorbed, so a European
|
|
18
|
+
* decimal never becomes a 1000× value.
|
|
19
|
+
*
|
|
20
|
+
* NOTE: intentionally duplicated as `editor/packages/mcp/src/tools/measurement.ts`
|
|
21
|
+
* (MCP tools) and `packages/ai/src/tools/scene/measurement.ts` (AI-chat tools).
|
|
22
|
+
* The two tool stacks live in different packages — one inside the `editor`
|
|
23
|
+
* submodule — so they can't share a module. Keep the two copies identical; the
|
|
24
|
+
* clean long-term dedup is a zod-field adapter exported from `@pascal-app/lingo`.
|
|
25
|
+
*/
|
|
26
|
+
export interface MeasurementOptions {
|
|
27
|
+
/** Semantic description (e.g. "Wall thickness"). The natural-language note is appended. */
|
|
28
|
+
description?: string;
|
|
29
|
+
/** Upper bound, in `unit`. */
|
|
30
|
+
max?: number;
|
|
31
|
+
/** Inclusive lower bound, in `unit`. */
|
|
32
|
+
min?: number;
|
|
33
|
+
/** Require the value to be strictly greater than 0 (for non-zero dimensions). */
|
|
34
|
+
positive?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare function measurement(kind: Kind, unit: string, opts?: MeasurementOptions): z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>;
|
|
37
|
+
//# sourceMappingURL=measurement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measurement.d.ts","sourceRoot":"","sources":["../../src/tools/measurement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,EAAiB,MAAM,mBAAmB,CAAA;AAC5D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,kBAAkB;IACjC,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,wCAAwC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AA6CD,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,kBAAuB,uGAoDlF"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { parseQuantity } from '@pascal-app/lingo';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
function unitNoun(unit) {
|
|
4
|
+
switch (unit) {
|
|
5
|
+
case 'm':
|
|
6
|
+
return 'meters';
|
|
7
|
+
case 'deg':
|
|
8
|
+
return 'degrees';
|
|
9
|
+
case 'rad':
|
|
10
|
+
return 'radians';
|
|
11
|
+
default:
|
|
12
|
+
return unit;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function naturalLanguageNote(kind, unit) {
|
|
16
|
+
let examples;
|
|
17
|
+
if (kind === 'angle') {
|
|
18
|
+
// Lead with an example in the field's own unit so a bare number is read
|
|
19
|
+
// the way the model intends (a bare "45" in a radians field is 45 rad).
|
|
20
|
+
examples =
|
|
21
|
+
unit === 'rad' ? '1.5708, "90°", "1.57rad", "0.25 turn"' : '45, "45°", "1.57rad", "0.25 turn"';
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
examples = '0.9, "6 ft", "180cm", "2 ft 3 in"';
|
|
25
|
+
}
|
|
26
|
+
return `Accepts a number (${unitNoun(unit)}) or a natural-language string; other units are converted. e.g. ${examples}.`;
|
|
27
|
+
}
|
|
28
|
+
function boundsNote(unit, min, max, positive) {
|
|
29
|
+
if (positive) {
|
|
30
|
+
return max === undefined
|
|
31
|
+
? ` Must be greater than 0 ${unit}.`
|
|
32
|
+
: ` Greater than 0, up to ${max} ${unit}.`;
|
|
33
|
+
}
|
|
34
|
+
if (min !== undefined && max !== undefined)
|
|
35
|
+
return ` Range ${min}–${max} ${unit}.`;
|
|
36
|
+
if (min !== undefined)
|
|
37
|
+
return ` Minimum ${min} ${unit}.`;
|
|
38
|
+
if (max !== undefined)
|
|
39
|
+
return ` Maximum ${max} ${unit}.`;
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
export function measurement(kind, unit, opts = {}) {
|
|
43
|
+
const { min, max, positive = false } = opts;
|
|
44
|
+
const description = [
|
|
45
|
+
opts.description,
|
|
46
|
+
naturalLanguageNote(kind, unit) + boundsNote(unit, min, max, positive),
|
|
47
|
+
]
|
|
48
|
+
.filter(Boolean)
|
|
49
|
+
.join(' ');
|
|
50
|
+
return z
|
|
51
|
+
.union([z.number(), z.string()])
|
|
52
|
+
.transform((val, ctx) => {
|
|
53
|
+
let value;
|
|
54
|
+
if (typeof val === 'number') {
|
|
55
|
+
value = val;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const result = parseQuantity(val, {
|
|
59
|
+
kind,
|
|
60
|
+
unit,
|
|
61
|
+
strictness: 'forgiving',
|
|
62
|
+
// A genuinely ambiguous separator ("1,234") must not silently become
|
|
63
|
+
// a 1000× value at a tool boundary — fail so the model self-corrects.
|
|
64
|
+
escalate: { AMBIGUOUS_NUMBER: 'error' },
|
|
65
|
+
});
|
|
66
|
+
if (!result.ok) {
|
|
67
|
+
ctx.addIssue({
|
|
68
|
+
code: 'custom',
|
|
69
|
+
message: result.issues[0]?.message ?? `Could not read "${val}" as a ${kind}.`,
|
|
70
|
+
});
|
|
71
|
+
return z.NEVER;
|
|
72
|
+
}
|
|
73
|
+
value = result.quantity.to(unit).value;
|
|
74
|
+
}
|
|
75
|
+
if (!Number.isFinite(value)) {
|
|
76
|
+
ctx.addIssue({ code: 'custom', message: `Value must be a finite ${kind} in ${unit}.` });
|
|
77
|
+
return z.NEVER;
|
|
78
|
+
}
|
|
79
|
+
if (positive && value <= 0) {
|
|
80
|
+
ctx.addIssue({ code: 'custom', message: `Must be greater than 0 ${unit} (got ${value}).` });
|
|
81
|
+
return z.NEVER;
|
|
82
|
+
}
|
|
83
|
+
if (min !== undefined && value < min) {
|
|
84
|
+
ctx.addIssue({ code: 'custom', message: `Must be at least ${min} ${unit} (got ${value}).` });
|
|
85
|
+
return z.NEVER;
|
|
86
|
+
}
|
|
87
|
+
if (max !== undefined && value > max) {
|
|
88
|
+
ctx.addIssue({ code: 'custom', message: `Must be at most ${max} ${unit} (got ${value}).` });
|
|
89
|
+
return z.NEVER;
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
})
|
|
93
|
+
.describe(description);
|
|
94
|
+
}
|
|
@@ -10,8 +10,8 @@ export declare const photoToSceneInput: {
|
|
|
10
10
|
scaleHint: z.ZodOptional<z.ZodString>;
|
|
11
11
|
name: z.ZodDefault<z.ZodString>;
|
|
12
12
|
save: z.ZodDefault<z.ZodBoolean>;
|
|
13
|
-
defaultWallThickness: z.ZodDefault<z.ZodNumber
|
|
14
|
-
defaultWallHeight: z.ZodDefault<z.ZodNumber
|
|
13
|
+
defaultWallThickness: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
14
|
+
defaultWallHeight: z.ZodDefault<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
15
15
|
};
|
|
16
16
|
export declare const photoToSceneOutput: {
|
|
17
17
|
sceneId: z.ZodOptional<z.ZodString>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"photo-to-scene.d.ts","sourceRoot":"","sources":["../../../src/tools/photo-to-scene/photo-to-scene.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAYxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"photo-to-scene.d.ts","sourceRoot":"","sources":["../../../src/tools/photo-to-scene/photo-to-scene.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAYxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAIvD;;;GAGG;AACH,eAAO,MAAM,iBAAiB;;;;;;;CAa7B,CAAA;AAED,eAAO,MAAM,kBAAkB;;;;;;;;CAQ9B,CAAA;AAmTD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CA6ErF"}
|
|
@@ -2,6 +2,7 @@ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
|
2
2
|
import { AnyNode, BuildingNode, LevelNode, SiteNode, WallNode, ZoneNode, } from '@pascal-app/core/schema';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { appendLiveSceneEvent } from '../live-sync';
|
|
5
|
+
import { measurement } from '../measurement';
|
|
5
6
|
/**
|
|
6
7
|
* Input shape for the `photo_to_scene` orchestrator. `image` matches the
|
|
7
8
|
* contract documented on `analyze_floorplan_image` — base64 or http(s) URL.
|
|
@@ -11,8 +12,14 @@ export const photoToSceneInput = {
|
|
|
11
12
|
scaleHint: z.string().optional().describe('e.g. "1 cm = 1 m" or "approx 80 m²"'),
|
|
12
13
|
name: z.string().default('Scene from photo'),
|
|
13
14
|
save: z.boolean().default(true),
|
|
14
|
-
defaultWallThickness:
|
|
15
|
-
|
|
15
|
+
defaultWallThickness: measurement('length', 'm', {
|
|
16
|
+
positive: true,
|
|
17
|
+
description: 'Default wall thickness.',
|
|
18
|
+
}).default(0.2),
|
|
19
|
+
defaultWallHeight: measurement('length', 'm', {
|
|
20
|
+
positive: true,
|
|
21
|
+
description: 'Default wall height.',
|
|
22
|
+
}).default(2.6),
|
|
16
23
|
};
|
|
17
24
|
export const photoToSceneOutput = {
|
|
18
25
|
sceneId: z.string().optional(),
|
|
@@ -33,6 +40,7 @@ const VisionResponseSchema = z.object({
|
|
|
33
40
|
start: z.tuple([z.number(), z.number()]),
|
|
34
41
|
end: z.tuple([z.number(), z.number()]),
|
|
35
42
|
thickness: z.number().optional(),
|
|
43
|
+
height: z.number().positive().optional(),
|
|
36
44
|
})),
|
|
37
45
|
rooms: z.array(z.object({
|
|
38
46
|
name: z.string(),
|
|
@@ -54,13 +62,14 @@ const SYSTEM_PROMPT = `You are a vision assistant that extracts structured floor
|
|
|
54
62
|
Your ONLY job: return a JSON object that exactly matches this schema — no prose, no markdown fences.
|
|
55
63
|
|
|
56
64
|
{
|
|
57
|
-
"walls": [{ "start": [x, z], "end": [x, z], "thickness": number? }, ...],
|
|
65
|
+
"walls": [{ "start": [x, z], "end": [x, z], "thickness": number?, "height": number? }, ...],
|
|
58
66
|
"rooms": [{ "name": string, "polygon": [[x,z], ...], "approximateAreaSqM": number? }, ...],
|
|
59
67
|
"approximateDimensions": { "widthM": number, "depthM": number },
|
|
60
68
|
"confidence": number 0..1
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
Coordinates are in metres. Origin can be the floor plan's centre or bottom-left — be consistent.
|
|
72
|
+
Only include a wall height when it is visibly measured or annotated in the image.
|
|
64
73
|
If the image is unclear, lower the confidence score but still produce your best attempt.
|
|
65
74
|
DO NOT wrap the JSON in markdown. DO NOT explain. Just output the raw JSON.`;
|
|
66
75
|
const DATA_URI_RE = /^data:(image\/[a-z0-9.+-]+);base64,(.+)$/i;
|
|
@@ -163,7 +172,7 @@ function buildSceneGraphFromVision(vision, defaultWallThickness, defaultWallHeig
|
|
|
163
172
|
const warnings = [];
|
|
164
173
|
// Build the skeleton: site → building → level.
|
|
165
174
|
const building = BuildingNode.parse({});
|
|
166
|
-
const level = LevelNode.parse({ level: 0 });
|
|
175
|
+
const level = LevelNode.parse({ level: 0, height: defaultWallHeight });
|
|
167
176
|
const site = SiteNode.parse({ children: [building.id] });
|
|
168
177
|
// Link parent ids so downstream traversal works.
|
|
169
178
|
const siteId = site.id;
|
|
@@ -201,7 +210,7 @@ function buildSceneGraphFromVision(vision, defaultWallThickness, defaultWallHeig
|
|
|
201
210
|
start: w.start,
|
|
202
211
|
end: w.end,
|
|
203
212
|
thickness: w.thickness ?? defaultWallThickness,
|
|
204
|
-
height:
|
|
213
|
+
...(w.height !== undefined ? { height: w.height } : {}),
|
|
205
214
|
});
|
|
206
215
|
const linkedWall = {
|
|
207
216
|
...wall,
|
|
@@ -5,7 +5,7 @@ export declare const placeItemInput: {
|
|
|
5
5
|
catalogItemId: z.ZodString;
|
|
6
6
|
targetNodeId: z.ZodString;
|
|
7
7
|
position: z.ZodArray<z.ZodNumber>;
|
|
8
|
-
rotation: z.ZodOptional<z.ZodNumber
|
|
8
|
+
rotation: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
9
9
|
};
|
|
10
10
|
export declare const placeItemOutput: {
|
|
11
11
|
itemId: z.ZodString;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"place-item.d.ts","sourceRoot":"","sources":["../../src/tools/place-item.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"place-item.d.ts","sourceRoot":"","sources":["../../src/tools/place-item.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAQpD,eAAO,MAAM,cAAc;;;;;CAK1B,CAAA;AAED,eAAO,MAAM,eAAe;;;CAG3B,CAAA;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAsFlF"}
|
package/dist/tools/place-item.js
CHANGED
|
@@ -4,12 +4,13 @@ import { findCatalogItem } from './asset-catalog';
|
|
|
4
4
|
import { ErrorCode, throwMcpError } from './errors';
|
|
5
5
|
import { projectWorldPointToWallLocalX, wallLength } from './geometry';
|
|
6
6
|
import { publishLiveSceneSnapshot } from './live-sync';
|
|
7
|
+
import { measurement } from './measurement';
|
|
7
8
|
import { NodeIdSchema, Vec3Schema } from './schemas';
|
|
8
9
|
export const placeItemInput = {
|
|
9
10
|
catalogItemId: z.string().min(1),
|
|
10
11
|
targetNodeId: NodeIdSchema,
|
|
11
12
|
position: Vec3Schema,
|
|
12
|
-
rotation:
|
|
13
|
+
rotation: measurement('angle', 'rad', { description: 'Y-axis rotation.' }).optional(),
|
|
13
14
|
};
|
|
14
15
|
export const placeItemOutput = {
|
|
15
16
|
itemId: z.string(),
|
|
@@ -14,8 +14,8 @@ export declare const createRoomInput: {
|
|
|
14
14
|
name: z.ZodString;
|
|
15
15
|
polygon: z.ZodArray<z.ZodArray<z.ZodNumber>>;
|
|
16
16
|
color: z.ZodOptional<z.ZodString>;
|
|
17
|
-
wallHeight: z.ZodOptional<z.ZodNumber
|
|
18
|
-
wallThickness: z.ZodOptional<z.ZodNumber
|
|
17
|
+
wallHeight: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
18
|
+
wallThickness: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
19
19
|
};
|
|
20
20
|
export declare const createRoomOutput: {
|
|
21
21
|
zoneId: z.ZodString;
|
|
@@ -28,11 +28,11 @@ export declare const addDoorInput: {
|
|
|
28
28
|
wallId: z.ZodString;
|
|
29
29
|
t: z.ZodOptional<z.ZodNumber>;
|
|
30
30
|
position: z.ZodOptional<z.ZodNumber>;
|
|
31
|
-
width: z.ZodOptional<z.ZodNumber
|
|
32
|
-
height: z.ZodOptional<z.ZodNumber
|
|
31
|
+
width: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
32
|
+
height: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
33
33
|
hingesSide: z.ZodOptional<z.ZodEnum<{
|
|
34
|
-
left: "left";
|
|
35
34
|
right: "right";
|
|
35
|
+
left: "left";
|
|
36
36
|
}>>;
|
|
37
37
|
swingDirection: z.ZodOptional<z.ZodEnum<{
|
|
38
38
|
inward: "inward";
|
|
@@ -52,9 +52,9 @@ export declare const addWindowInput: {
|
|
|
52
52
|
wallId: z.ZodString;
|
|
53
53
|
t: z.ZodOptional<z.ZodNumber>;
|
|
54
54
|
position: z.ZodOptional<z.ZodNumber>;
|
|
55
|
-
width: z.ZodOptional<z.ZodNumber
|
|
56
|
-
height: z.ZodOptional<z.ZodNumber
|
|
57
|
-
sillHeight: z.ZodOptional<z.ZodNumber
|
|
55
|
+
width: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
56
|
+
height: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
57
|
+
sillHeight: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>, z.ZodTransform<number, string | number>>>;
|
|
58
58
|
};
|
|
59
59
|
export declare const addWindowOutput: {
|
|
60
60
|
windowId: z.ZodString;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"room-tools.d.ts","sourceRoot":"","sources":["../../src/tools/room-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAWxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"room-tools.d.ts","sourceRoot":"","sources":["../../src/tools/room-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAWxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AA2BpD,eAAO,MAAM,iBAAiB;;;CAG7B,CAAA;AAED,eAAO,MAAM,kBAAkB;;;CAG9B,CAAA;AAED,eAAO,MAAM,eAAe;;;;;;;CAa3B,CAAA;AAED,eAAO,MAAM,gBAAgB;;;;;;CAM5B,CAAA;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;CAQxB,CAAA;AAED,eAAO,MAAM,aAAa;;;;;;;;CAQzB,CAAA;AAED,eAAO,MAAM,cAAc;;;;;;;CAU1B,CAAA;AAED,eAAO,MAAM,eAAe;;;;;;;;;CAS3B,CAAA;AAED,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;CAM5B,CAAA;AAED,eAAO,MAAM,iBAAiB;;;;CAI7B,CAAA;AA+PD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAsB5D;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAqDnF;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CA2ChF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CA0ClF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAsEpF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAMlF"}
|
package/dist/tools/room-tools.js
CHANGED
|
@@ -4,6 +4,7 @@ import { findCatalogItem, searchCatalogItems } from './asset-catalog';
|
|
|
4
4
|
import { ErrorCode, throwMcpError } from './errors';
|
|
5
5
|
import { pointInBoundsWithPadding, polygonArea, polygonBounds, wallLength, wallLocalXFromT, } from './geometry';
|
|
6
6
|
import { publishLiveSceneSnapshot } from './live-sync';
|
|
7
|
+
import { measurement } from './measurement';
|
|
7
8
|
import { NodeIdSchema, Vec2Schema } from './schemas';
|
|
8
9
|
const ROOM_TYPES = [
|
|
9
10
|
'bedroom',
|
|
@@ -29,8 +30,14 @@ export const createRoomInput = {
|
|
|
29
30
|
name: z.string().min(1),
|
|
30
31
|
polygon: z.array(Vec2Schema).min(3),
|
|
31
32
|
color: z.string().optional(),
|
|
32
|
-
wallHeight:
|
|
33
|
-
|
|
33
|
+
wallHeight: measurement('length', 'm', {
|
|
34
|
+
positive: true,
|
|
35
|
+
description: 'Wall height.',
|
|
36
|
+
}).optional(),
|
|
37
|
+
wallThickness: measurement('length', 'm', {
|
|
38
|
+
positive: true,
|
|
39
|
+
description: 'Wall thickness.',
|
|
40
|
+
}).optional(),
|
|
34
41
|
};
|
|
35
42
|
export const createRoomOutput = {
|
|
36
43
|
zoneId: z.string(),
|
|
@@ -43,8 +50,8 @@ export const addDoorInput = {
|
|
|
43
50
|
wallId: NodeIdSchema,
|
|
44
51
|
t: z.number().min(0).max(1).optional(),
|
|
45
52
|
position: z.number().min(0).max(1).optional(),
|
|
46
|
-
width:
|
|
47
|
-
height:
|
|
53
|
+
width: measurement('length', 'm', { positive: true, description: 'Door width.' }).optional(),
|
|
54
|
+
height: measurement('length', 'm', { positive: true, description: 'Door height.' }).optional(),
|
|
48
55
|
hingesSide: z.enum(['left', 'right']).optional(),
|
|
49
56
|
swingDirection: z.enum(['inward', 'outward']).optional(),
|
|
50
57
|
};
|
|
@@ -61,9 +68,12 @@ export const addWindowInput = {
|
|
|
61
68
|
wallId: NodeIdSchema,
|
|
62
69
|
t: z.number().min(0).max(1).optional(),
|
|
63
70
|
position: z.number().min(0).max(1).optional(),
|
|
64
|
-
width:
|
|
65
|
-
height:
|
|
66
|
-
sillHeight:
|
|
71
|
+
width: measurement('length', 'm', { positive: true, description: 'Window width.' }).optional(),
|
|
72
|
+
height: measurement('length', 'm', { positive: true, description: 'Window height.' }).optional(),
|
|
73
|
+
sillHeight: measurement('length', 'm', {
|
|
74
|
+
min: 0,
|
|
75
|
+
description: 'Sill height above floor.',
|
|
76
|
+
}).optional(),
|
|
67
77
|
};
|
|
68
78
|
export const addWindowOutput = {
|
|
69
79
|
windowId: z.string(),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { AnyNode } from '@pascal-app/core/schema';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import type { SceneOperations } from '../operations';
|
|
4
5
|
export declare const levelScopedInput: {
|
|
@@ -48,6 +49,9 @@ export declare const verifySceneOutput: {
|
|
|
48
49
|
issues: z.ZodArray<z.ZodString>;
|
|
49
50
|
hasIssues: z.ZodBoolean;
|
|
50
51
|
};
|
|
52
|
+
export declare function resolveReportedWallHeight(bridge: SceneOperations, wall: Extract<AnyNode, {
|
|
53
|
+
type: 'wall';
|
|
54
|
+
}>): number;
|
|
51
55
|
export declare function registerListLevels(server: McpServer, bridge: SceneOperations): void;
|
|
52
56
|
export declare function registerGetLevelSummary(server: McpServer, bridge: SceneOperations): void;
|
|
53
57
|
export declare function registerGetWalls(server: McpServer, bridge: SceneOperations): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-query.d.ts","sourceRoot":"","sources":["../../src/tools/scene-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;
|
|
1
|
+
{"version":3,"file":"scene-query.d.ts","sourceRoot":"","sources":["../../src/tools/scene-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAQxE,OAAO,KAAK,EAAE,OAAO,EAAa,MAAM,yBAAyB,CAAA;AAEjE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAWpD,eAAO,MAAM,gBAAgB;;CAE5B,CAAA;AAID,eAAO,MAAM,gBAAgB;;;;;;;CAO5B,CAAA;AAED,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;CAcjC,CAAA;AAED,eAAO,MAAM,cAAc;;;CAG1B,CAAA;AAED,eAAO,MAAM,cAAc;;;CAG1B,CAAA;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;CAY7B,CAAA;AA8CD,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GACvC,MAAM,CAgBR;AAkWD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAyCnF;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAgBxF;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAmBjF;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAmBjF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CA4PpF;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAMxF"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DEFAULT_LEVEL_HEIGHT, getStoredLevelHeight, getWallPlaneTop, resolveStairTotalRise, resolveWallEffectiveHeight, } from '@pascal-app/core';
|
|
2
|
+
import { computeWallSlabSupport } from '@pascal-app/core/spatial-grid';
|
|
1
3
|
import { z } from 'zod';
|
|
2
4
|
import { distance2D, pointInPolygon, polygonArea, polygonContainsPolygon, wallLength, } from './geometry';
|
|
3
5
|
import { NodeIdSchema } from './schemas';
|
|
@@ -71,6 +73,19 @@ function getDefaultLevelId(bridge, requested) {
|
|
|
71
73
|
function nodesOnLevel(bridge, levelId) {
|
|
72
74
|
return Object.values(bridge.getNodes()).filter((node) => node.id !== levelId && bridge.resolveLevelId(node.id) === levelId);
|
|
73
75
|
}
|
|
76
|
+
export function resolveReportedWallHeight(bridge, wall) {
|
|
77
|
+
const levelId = bridge.resolveLevelId(wall.id);
|
|
78
|
+
// Covering-clamped plane for plane-bound walls; explicit heights pass
|
|
79
|
+
// through resolveWallEffectiveHeight untouched.
|
|
80
|
+
const planeTop = levelId
|
|
81
|
+
? getWallPlaneTop(wall, levelId, bridge.getNodes())
|
|
82
|
+
: DEFAULT_LEVEL_HEIGHT;
|
|
83
|
+
const levelNodes = levelId ? nodesOnLevel(bridge, levelId) : [];
|
|
84
|
+
const slabs = levelNodes.filter((node) => node.type === 'slab');
|
|
85
|
+
const walls = levelNodes.filter((node) => node.type === 'wall');
|
|
86
|
+
const support = computeWallSlabSupport(wall, slabs, walls, wall.supportSlabId);
|
|
87
|
+
return resolveWallEffectiveHeight(wall, planeTop, support.elevation);
|
|
88
|
+
}
|
|
74
89
|
function metadataRecord(node) {
|
|
75
90
|
return typeof node.metadata === 'object' && node.metadata !== null
|
|
76
91
|
? node.metadata
|
|
@@ -116,6 +131,7 @@ function wallSummary(bridge, wall) {
|
|
|
116
131
|
if (wall.type !== 'wall')
|
|
117
132
|
return null;
|
|
118
133
|
const length = distance2D(wall.start, wall.end);
|
|
134
|
+
const resolvedHeight = resolveReportedWallHeight(bridge, wall);
|
|
119
135
|
return {
|
|
120
136
|
id: wall.id,
|
|
121
137
|
name: wall.name,
|
|
@@ -123,6 +139,8 @@ function wallSummary(bridge, wall) {
|
|
|
123
139
|
end: wall.end,
|
|
124
140
|
length: Math.round(length * 100) / 100,
|
|
125
141
|
height: wall.height,
|
|
142
|
+
resolvedHeight,
|
|
143
|
+
heightIsExplicit: wall.height !== undefined,
|
|
126
144
|
thickness: wall.thickness,
|
|
127
145
|
openings: openingSummaries(bridge, wall.id),
|
|
128
146
|
};
|
|
@@ -235,7 +253,7 @@ function stairFootprintPolygons(bridge, stair) {
|
|
|
235
253
|
{
|
|
236
254
|
width: stair.width ?? 1,
|
|
237
255
|
length: 3,
|
|
238
|
-
height: stair
|
|
256
|
+
height: resolveStairTotalRise(stair, nodes),
|
|
239
257
|
stepCount: stair.stepCount ?? 10,
|
|
240
258
|
attachmentSide: 'front',
|
|
241
259
|
},
|
|
@@ -305,7 +323,7 @@ function parentListsChild(parent, childId) {
|
|
|
305
323
|
}
|
|
306
324
|
function levelSummary(bridge, levelId) {
|
|
307
325
|
const level = bridge.getNode(levelId);
|
|
308
|
-
if (
|
|
326
|
+
if (level?.type !== 'level') {
|
|
309
327
|
throw new Error(`Level not found: ${levelId}`);
|
|
310
328
|
}
|
|
311
329
|
const nodes = nodesOnLevel(bridge, levelId);
|
|
@@ -524,14 +542,9 @@ export function registerVerifyScene(server, bridge) {
|
|
|
524
542
|
const summary = levels.find((entry) => entry.levelId === level.id);
|
|
525
543
|
if (!summary?.isOccupiedStory)
|
|
526
544
|
continue;
|
|
527
|
-
const expectedHeight =
|
|
528
|
-
level.metadata !== null &&
|
|
529
|
-
'height' in level.metadata &&
|
|
530
|
-
typeof level.metadata.height === 'number'
|
|
531
|
-
? level.metadata.height
|
|
532
|
-
: 3.2;
|
|
545
|
+
const expectedHeight = getStoredLevelHeight(level);
|
|
533
546
|
for (const wall of nodesOnLevel(bridge, level.id).filter((node) => node.type === 'wall')) {
|
|
534
|
-
const wallHeight = wall
|
|
547
|
+
const wallHeight = resolveReportedWallHeight(bridge, wall);
|
|
535
548
|
if (wallHeight > expectedHeight + 0.25) {
|
|
536
549
|
issues.push(`Wall ${wall.name ?? wall.id} on ${level.name ?? level.id} is ${wallHeight}m high; multi-story exterior walls should be split into level-owned story walls`);
|
|
537
550
|
}
|
|
@@ -541,7 +554,7 @@ export function registerVerifyScene(server, bridge) {
|
|
|
541
554
|
for (const node of Object.values(bridge.getNodes())) {
|
|
542
555
|
if (node.type === 'door' || node.type === 'window') {
|
|
543
556
|
const parent = node.parentId ? bridge.getNode(node.parentId) : null;
|
|
544
|
-
if (
|
|
557
|
+
if (parent?.type !== 'wall') {
|
|
545
558
|
issues.push(`${node.type} ${node.id} is not parented to a wall`);
|
|
546
559
|
continue;
|
|
547
560
|
}
|
|
@@ -558,7 +571,7 @@ export function registerVerifyScene(server, bridge) {
|
|
|
558
571
|
if (localX - width / 2 < -0.01 || localX + width / 2 > length + 0.01) {
|
|
559
572
|
issues.push(`${node.type} ${node.id} extends outside wall ${parent.id}`);
|
|
560
573
|
}
|
|
561
|
-
const wallHeight = parent
|
|
574
|
+
const wallHeight = resolveReportedWallHeight(bridge, parent);
|
|
562
575
|
const bottom = node.position[1] - height / 2;
|
|
563
576
|
const top = node.position[1] + height / 2;
|
|
564
577
|
if (bottom < -0.01 || top > wallHeight + 0.01) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
4
|
"description": "Model Context Protocol server for Pascal 3D editor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -48,21 +48,22 @@
|
|
|
48
48
|
],
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build": "tsc --build",
|
|
51
|
-
"dev": "
|
|
51
|
+
"dev": "tsgo --build --watch",
|
|
52
52
|
"start": "bun dist/bin/pascal-mcp.js",
|
|
53
53
|
"test": "bun test",
|
|
54
54
|
"smoke": "bun run scripts/smoke.ts",
|
|
55
55
|
"prepublishOnly": "bun run build && bun test"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@pascal-app/core": "^0.
|
|
58
|
+
"@pascal-app/core": "^1.0.0-beta.1"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
62
|
+
"@pascal-app/lingo": "^0.2.0",
|
|
62
63
|
"zod": "^4.3.5"
|
|
63
64
|
},
|
|
64
65
|
"devDependencies": {
|
|
65
|
-
"@pascal-app/core": "^0.
|
|
66
|
+
"@pascal-app/core": "^1.0.0-beta.1",
|
|
66
67
|
"@pascal/typescript-config": "*",
|
|
67
68
|
"@types/node": "^22.19.20",
|
|
68
69
|
"typescript": "6.0.3"
|