@mentagen/mcp 0.5.0 → 0.6.0
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/dist/tools/batch-update.js +18 -6
- package/dist/tools/check-collision.js +30 -12
- package/dist/tools/create-graph.js +22 -16
- package/dist/tools/create.js +17 -15
- package/dist/tools/find-overlaps.js +24 -4
- package/dist/tools/find-position.js +97 -72
- package/dist/tools/grid-calc.js +44 -27
- package/dist/tools/index.js +53 -35
- package/dist/tools/list-nodes.js +5 -4
- package/dist/tools/list-positions.js +5 -4
- package/dist/tools/read.js +14 -1
- package/dist/tools/size-calc.js +10 -5
- package/dist/tools/update.js +21 -23
- package/dist/utils/units.js +19 -0
- package/package.json +1 -1
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { formatError } from '../utils/errors.js';
|
|
3
|
+
import { unitsToPixels } from '../utils/units.js';
|
|
4
|
+
import { snapToGrid } from './grid-calc.js';
|
|
3
5
|
const nodeUpdateSchema = z.object({
|
|
4
6
|
nodeId: z.string().describe('The node ID to update'),
|
|
5
7
|
name: z.string().optional().describe('New name for the node'),
|
|
6
8
|
content: z.string().optional().describe('New content for the node'),
|
|
7
9
|
color: z.string().optional().describe('Node color'),
|
|
8
|
-
x: z.number().optional().describe('X position'),
|
|
9
|
-
y: z.number().optional().describe('Y position'),
|
|
10
|
-
width: z.number().optional().describe('Node width'),
|
|
11
|
-
height: z.number().optional().describe('Node height'),
|
|
10
|
+
x: z.number().optional().describe('X position in grid units'),
|
|
11
|
+
y: z.number().optional().describe('Y position in grid units'),
|
|
12
|
+
width: z.number().optional().describe('Node width in grid units'),
|
|
13
|
+
height: z.number().optional().describe('Node height in grid units'),
|
|
12
14
|
});
|
|
13
15
|
const edgeUpdateSchema = z.object({
|
|
14
16
|
edgeId: z.string().describe('The edge ID to update'),
|
|
@@ -44,8 +46,18 @@ export async function handleBatchUpdate(client, params) {
|
|
|
44
46
|
// Process node updates in parallel
|
|
45
47
|
const nodePromises = nodeUpdates.map(async (update) => {
|
|
46
48
|
const { nodeId, ...data } = update;
|
|
47
|
-
// Filter out undefined values
|
|
48
|
-
const cleanData =
|
|
49
|
+
// Filter out undefined values and convert position fields from units to pixels
|
|
50
|
+
const cleanData = {};
|
|
51
|
+
for (const [key, value] of Object.entries(data)) {
|
|
52
|
+
if (value !== undefined) {
|
|
53
|
+
if (key === 'x' || key === 'y' || key === 'width' || key === 'height') {
|
|
54
|
+
cleanData[key] = snapToGrid(unitsToPixels(value));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
cleanData[key] = value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
49
61
|
if (Object.keys(cleanData).length === 0) {
|
|
50
62
|
return { id: nodeId, success: false, error: 'No fields to update' };
|
|
51
63
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { findCollidingNodes, snapToGrid } from '../utils/collision.js';
|
|
3
3
|
import { formatError } from '../utils/errors.js';
|
|
4
|
+
import { pixelsToUnits, unitsToPixels } from '../utils/units.js';
|
|
4
5
|
export const checkCollisionSchema = z.object({
|
|
5
6
|
boardId: z.string().describe('The board ID to check against'),
|
|
6
|
-
x: z.number().describe('Left position of the rectangle'),
|
|
7
|
-
y: z.number().describe('Top position of the rectangle'),
|
|
8
|
-
width: z.number().describe('Width of the rectangle'),
|
|
9
|
-
height: z.number().describe('Height of the rectangle'),
|
|
7
|
+
x: z.number().describe('Left position of the rectangle in grid units'),
|
|
8
|
+
y: z.number().describe('Top position of the rectangle in grid units'),
|
|
9
|
+
width: z.number().describe('Width of the rectangle in grid units'),
|
|
10
|
+
height: z.number().describe('Height of the rectangle in grid units'),
|
|
10
11
|
excludeNodeId: z
|
|
11
12
|
.string()
|
|
12
13
|
.optional()
|
|
@@ -19,15 +20,32 @@ export async function handleCheckCollision(client, params) {
|
|
|
19
20
|
boardId: params.boardId,
|
|
20
21
|
limit: 500,
|
|
21
22
|
});
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
// Convert units to pixels and snap position to grid
|
|
24
|
+
// Note: width/height don't need snapToGrid since unitsToPixels already produces grid-aligned values
|
|
25
|
+
const rectPixels = {
|
|
26
|
+
x: snapToGrid(unitsToPixels(params.x)),
|
|
27
|
+
y: snapToGrid(unitsToPixels(params.y)),
|
|
28
|
+
width: unitsToPixels(params.width),
|
|
29
|
+
height: unitsToPixels(params.height),
|
|
28
30
|
};
|
|
29
31
|
// Check for collisions
|
|
30
|
-
const result = findCollidingNodes(
|
|
32
|
+
const result = findCollidingNodes(rectPixels, nodes, params.excludeNodeId);
|
|
33
|
+
// Convert results back to units
|
|
34
|
+
const rect = {
|
|
35
|
+
x: pixelsToUnits(rectPixels.x),
|
|
36
|
+
y: pixelsToUnits(rectPixels.y),
|
|
37
|
+
width: pixelsToUnits(rectPixels.width),
|
|
38
|
+
height: pixelsToUnits(rectPixels.height),
|
|
39
|
+
};
|
|
40
|
+
const collidingNodes = result.collidingNodes.map(node => ({
|
|
41
|
+
...node,
|
|
42
|
+
rect: {
|
|
43
|
+
x: pixelsToUnits(node.rect.x),
|
|
44
|
+
y: pixelsToUnits(node.rect.y),
|
|
45
|
+
width: pixelsToUnits(node.rect.width),
|
|
46
|
+
height: pixelsToUnits(node.rect.height),
|
|
47
|
+
},
|
|
48
|
+
}));
|
|
31
49
|
return {
|
|
32
50
|
content: [
|
|
33
51
|
{
|
|
@@ -35,7 +53,7 @@ export async function handleCheckCollision(client, params) {
|
|
|
35
53
|
text: JSON.stringify({
|
|
36
54
|
collides: result.collides,
|
|
37
55
|
rect,
|
|
38
|
-
collidingNodes
|
|
56
|
+
collidingNodes,
|
|
39
57
|
}, null, 2),
|
|
40
58
|
},
|
|
41
59
|
],
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { formatError } from '../utils/errors.js';
|
|
3
|
+
import { pixelsToUnits, unitsToPixels } from '../utils/units.js';
|
|
3
4
|
import { applyExtension, generateId } from './create.js';
|
|
5
|
+
import { snapToGrid } from './grid-calc.js';
|
|
4
6
|
import { calculateNodeSize } from './size-calc.js';
|
|
5
7
|
const nodeInputSchema = z.object({
|
|
6
8
|
tempId: z.string().describe('Temporary ID for referencing in edges'),
|
|
@@ -16,10 +18,10 @@ const nodeInputSchema = z.object({
|
|
|
16
18
|
.default('text')
|
|
17
19
|
.describe('Node type'),
|
|
18
20
|
color: z.string().optional().describe('Node color'),
|
|
19
|
-
x: z.number().optional().describe('X position'),
|
|
20
|
-
y: z.number().optional().describe('Y position'),
|
|
21
|
-
width: z.number().optional().describe('Width'),
|
|
22
|
-
height: z.number().optional().describe('Height'),
|
|
21
|
+
x: z.number().optional().describe('X position in grid units'),
|
|
22
|
+
y: z.number().optional().describe('Y position in grid units'),
|
|
23
|
+
width: z.number().optional().describe('Width in grid units'),
|
|
24
|
+
height: z.number().optional().describe('Height in grid units'),
|
|
23
25
|
});
|
|
24
26
|
const edgeInputSchema = z.object({
|
|
25
27
|
source: z.string().describe('Source node tempId'),
|
|
@@ -50,10 +52,14 @@ async function createNode(client, boardId, nodeInput, idMap) {
|
|
|
50
52
|
const finalName = applyExtension(nodeInput.name, type, nodeInput.extension);
|
|
51
53
|
const color = nodeInput.color ?? DEFAULT_NODE_COLORS[type];
|
|
52
54
|
const autoSize = calculateNodeSize(finalName, type);
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
55
|
+
const xPixels = snapToGrid(nodeInput.x !== undefined ? unitsToPixels(nodeInput.x) : unitsToPixels(6));
|
|
56
|
+
const yPixels = snapToGrid(nodeInput.y !== undefined ? unitsToPixels(nodeInput.y) : unitsToPixels(6));
|
|
57
|
+
const widthPixels = snapToGrid(nodeInput.width !== undefined
|
|
58
|
+
? unitsToPixels(nodeInput.width)
|
|
59
|
+
: autoSize.width);
|
|
60
|
+
const heightPixels = snapToGrid(nodeInput.height !== undefined
|
|
61
|
+
? unitsToPixels(nodeInput.height)
|
|
62
|
+
: autoSize.height);
|
|
57
63
|
try {
|
|
58
64
|
// For code nodes, store content in the `code` field
|
|
59
65
|
const isCodeNode = type === 'code';
|
|
@@ -66,10 +72,10 @@ async function createNode(client, boardId, nodeInput, idMap) {
|
|
|
66
72
|
...(isCodeNode ? { code: nodeContent } : { content: nodeContent }),
|
|
67
73
|
type,
|
|
68
74
|
color,
|
|
69
|
-
x,
|
|
70
|
-
y,
|
|
71
|
-
width,
|
|
72
|
-
height,
|
|
75
|
+
x: xPixels,
|
|
76
|
+
y: yPixels,
|
|
77
|
+
width: widthPixels,
|
|
78
|
+
height: heightPixels,
|
|
73
79
|
},
|
|
74
80
|
});
|
|
75
81
|
idMap[nodeInput.tempId] = node._id;
|
|
@@ -79,10 +85,10 @@ async function createNode(client, boardId, nodeInput, idMap) {
|
|
|
79
85
|
tempId: nodeInput.tempId,
|
|
80
86
|
id: node._id,
|
|
81
87
|
name: node.name,
|
|
82
|
-
x,
|
|
83
|
-
y,
|
|
84
|
-
width,
|
|
85
|
-
height,
|
|
88
|
+
x: pixelsToUnits(xPixels),
|
|
89
|
+
y: pixelsToUnits(yPixels),
|
|
90
|
+
width: pixelsToUnits(widthPixels),
|
|
91
|
+
height: pixelsToUnits(heightPixels),
|
|
86
92
|
},
|
|
87
93
|
};
|
|
88
94
|
}
|
package/dist/tools/create.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { formatError } from '../utils/errors.js';
|
|
3
|
+
import { pixelsToUnits, unitsToPixels } from '../utils/units.js';
|
|
3
4
|
import { getNodeUrl } from '../utils/urls.js';
|
|
4
5
|
import { snapToGrid } from './grid-calc.js';
|
|
5
6
|
import { calculateNodeSize } from './size-calc.js';
|
|
@@ -66,22 +67,16 @@ export const createSchema = z.object({
|
|
|
66
67
|
.string()
|
|
67
68
|
.optional()
|
|
68
69
|
.describe('Optional color override. If not specified, uses default for type (markdown=cyan, code=indigo, url=blue)'),
|
|
69
|
-
x: z
|
|
70
|
-
|
|
71
|
-
.optional()
|
|
72
|
-
.describe('X position (must be multiple of 16, default: 96)'),
|
|
73
|
-
y: z
|
|
74
|
-
.number()
|
|
75
|
-
.optional()
|
|
76
|
-
.describe('Y position (must be multiple of 16, default: 96)'),
|
|
70
|
+
x: z.number().optional().describe('X position in grid units (default: 6)'),
|
|
71
|
+
y: z.number().optional().describe('Y position in grid units (default: 6)'),
|
|
77
72
|
width: z
|
|
78
73
|
.number()
|
|
79
74
|
.optional()
|
|
80
|
-
.describe('Width
|
|
75
|
+
.describe('Width in grid units (default: auto-calculated)'),
|
|
81
76
|
height: z
|
|
82
77
|
.number()
|
|
83
78
|
.optional()
|
|
84
|
-
.describe('Height
|
|
79
|
+
.describe('Height in grid units (default: auto-calculated)'),
|
|
85
80
|
});
|
|
86
81
|
/**
|
|
87
82
|
* Generate a simple ObjectId-like string.
|
|
@@ -129,10 +124,14 @@ export async function handleCreate(client, params, mentagenUrl) {
|
|
|
129
124
|
const color = params.color || DEFAULT_NODE_COLORS[params.type];
|
|
130
125
|
const autoSize = calculateNodeSize(finalName, params.type);
|
|
131
126
|
const position = {
|
|
132
|
-
x: snapToGrid(params.x ??
|
|
133
|
-
y: snapToGrid(params.y ??
|
|
134
|
-
width: snapToGrid(params.width
|
|
135
|
-
|
|
127
|
+
x: snapToGrid(unitsToPixels(params.x ?? 6)),
|
|
128
|
+
y: snapToGrid(unitsToPixels(params.y ?? 6)),
|
|
129
|
+
width: snapToGrid(params.width !== undefined
|
|
130
|
+
? unitsToPixels(params.width)
|
|
131
|
+
: autoSize.width),
|
|
132
|
+
height: snapToGrid(params.height !== undefined
|
|
133
|
+
? unitsToPixels(params.height)
|
|
134
|
+
: autoSize.height),
|
|
136
135
|
};
|
|
137
136
|
const nodeFields = buildNodeFields({ ...params, name: finalName }, color, position);
|
|
138
137
|
const node = await client.addNode({
|
|
@@ -146,7 +145,10 @@ export async function handleCreate(client, params, mentagenUrl) {
|
|
|
146
145
|
id: node._id,
|
|
147
146
|
name: node.name,
|
|
148
147
|
board: params.boardId,
|
|
149
|
-
|
|
148
|
+
x: pixelsToUnits(position.x),
|
|
149
|
+
y: pixelsToUnits(position.y),
|
|
150
|
+
width: pixelsToUnits(position.width),
|
|
151
|
+
height: pixelsToUnits(position.height),
|
|
150
152
|
link: getNodeUrl(mentagenUrl, params.boardId, node._id),
|
|
151
153
|
},
|
|
152
154
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { rectsIntersect } from '../utils/collision.js';
|
|
3
3
|
import { formatError } from '../utils/errors.js';
|
|
4
|
+
import { pixelsToUnits } from '../utils/units.js';
|
|
4
5
|
export const findOverlapsSchema = z.object({
|
|
5
6
|
boardId: z.string().describe('The board ID to check for overlapping nodes'),
|
|
6
7
|
});
|
|
@@ -43,10 +44,29 @@ export async function handleFindOverlaps(client, params) {
|
|
|
43
44
|
height: node2.height,
|
|
44
45
|
};
|
|
45
46
|
if (rectsIntersect(rect1, rect2)) {
|
|
47
|
+
const overlapAreaPixels = calculateOverlapArea(rect1, rect2);
|
|
46
48
|
overlaps.push({
|
|
47
|
-
node1: {
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
node1: {
|
|
50
|
+
id: node1._id,
|
|
51
|
+
name: node1.name,
|
|
52
|
+
rect: {
|
|
53
|
+
x: pixelsToUnits(rect1.x),
|
|
54
|
+
y: pixelsToUnits(rect1.y),
|
|
55
|
+
width: pixelsToUnits(rect1.width),
|
|
56
|
+
height: pixelsToUnits(rect1.height),
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
node2: {
|
|
60
|
+
id: node2._id,
|
|
61
|
+
name: node2.name,
|
|
62
|
+
rect: {
|
|
63
|
+
x: pixelsToUnits(rect2.x),
|
|
64
|
+
y: pixelsToUnits(rect2.y),
|
|
65
|
+
width: pixelsToUnits(rect2.width),
|
|
66
|
+
height: pixelsToUnits(rect2.height),
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
overlapArea: overlapAreaPixels,
|
|
50
70
|
});
|
|
51
71
|
}
|
|
52
72
|
}
|
|
@@ -68,7 +88,7 @@ export async function handleFindOverlaps(client, params) {
|
|
|
68
88
|
return (`${i + 1}. ${o.node1.name} (${o.node1.id})\n` +
|
|
69
89
|
` overlaps with\n` +
|
|
70
90
|
` ${o.node2.name} (${o.node2.id})\n` +
|
|
71
|
-
` Overlap area: ${o.overlapArea}px
|
|
91
|
+
` Overlap area: ${o.overlapArea}px² (${Math.round(o.overlapArea / 256)} units²)`);
|
|
72
92
|
})
|
|
73
93
|
.join('\n\n');
|
|
74
94
|
return {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { findFreePosition, } from '../utils/collision.js';
|
|
3
3
|
import { formatError } from '../utils/errors.js';
|
|
4
|
+
import { pixelsToUnits, unitsToPixels } from '../utils/units.js';
|
|
4
5
|
export const findPositionSchema = z.object({
|
|
5
6
|
boardId: z.string().describe('The board ID to search'),
|
|
6
|
-
width: z.number().describe('Width of the node to place'),
|
|
7
|
-
height: z.number().describe('Height of the node to place'),
|
|
7
|
+
width: z.number().describe('Width of the node to place in grid units'),
|
|
8
|
+
height: z.number().describe('Height of the node to place in grid units'),
|
|
8
9
|
anchorNodeId: z
|
|
9
10
|
.string()
|
|
10
11
|
.optional()
|
|
@@ -16,92 +17,116 @@ export const findPositionSchema = z.object({
|
|
|
16
17
|
gap: z
|
|
17
18
|
.number()
|
|
18
19
|
.optional()
|
|
19
|
-
.describe('Gap between nodes in
|
|
20
|
+
.describe('Gap between nodes in grid units. Use 6+ for connected nodes, 1 for unrelated (default: 1)'),
|
|
20
21
|
startX: z
|
|
21
22
|
.number()
|
|
22
23
|
.optional()
|
|
23
|
-
.describe('Explicit start X (ignored if anchorNodeId provided)'),
|
|
24
|
+
.describe('Explicit start X in grid units (ignored if anchorNodeId provided)'),
|
|
24
25
|
startY: z
|
|
25
26
|
.number()
|
|
26
27
|
.optional()
|
|
27
|
-
.describe('Explicit start Y (ignored if anchorNodeId provided)'),
|
|
28
|
+
.describe('Explicit start Y in grid units (ignored if anchorNodeId provided)'),
|
|
28
29
|
});
|
|
30
|
+
function findAnchorNode(nodes, anchorNodeId) {
|
|
31
|
+
const anchorNode = nodes.find(n => n._id === anchorNodeId);
|
|
32
|
+
if (!anchorNode)
|
|
33
|
+
return null;
|
|
34
|
+
return {
|
|
35
|
+
anchor: {
|
|
36
|
+
x: anchorNode.x,
|
|
37
|
+
y: anchorNode.y,
|
|
38
|
+
width: anchorNode.width,
|
|
39
|
+
height: anchorNode.height,
|
|
40
|
+
},
|
|
41
|
+
anchorInfo: {
|
|
42
|
+
id: anchorNode._id,
|
|
43
|
+
name: anchorNode.name,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function formatSuccessResponse(position, anchorInfo, direction, searchSteps) {
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: 'text',
|
|
52
|
+
text: JSON.stringify({
|
|
53
|
+
found: true,
|
|
54
|
+
position: {
|
|
55
|
+
x: pixelsToUnits(position.x),
|
|
56
|
+
y: pixelsToUnits(position.y),
|
|
57
|
+
},
|
|
58
|
+
anchor: anchorInfo || null,
|
|
59
|
+
direction: direction || 'right',
|
|
60
|
+
searchSteps,
|
|
61
|
+
}, null, 2),
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function formatErrorResponse(error, searchSteps) {
|
|
67
|
+
return {
|
|
68
|
+
content: [
|
|
69
|
+
{
|
|
70
|
+
type: 'text',
|
|
71
|
+
text: JSON.stringify({
|
|
72
|
+
found: false,
|
|
73
|
+
error: error || 'No valid position found',
|
|
74
|
+
searchSteps,
|
|
75
|
+
}, null, 2),
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
isError: true,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async function prepareSearchParams(client, params) {
|
|
82
|
+
const nodes = await client.listNodes({
|
|
83
|
+
boardId: params.boardId,
|
|
84
|
+
limit: 500,
|
|
85
|
+
});
|
|
86
|
+
if (!params.anchorNodeId) {
|
|
87
|
+
return { nodes };
|
|
88
|
+
}
|
|
89
|
+
const anchorData = findAnchorNode(nodes, params.anchorNodeId);
|
|
90
|
+
if (!anchorData) {
|
|
91
|
+
return {
|
|
92
|
+
error: {
|
|
93
|
+
content: [
|
|
94
|
+
{
|
|
95
|
+
type: 'text',
|
|
96
|
+
text: `Anchor node not found: ${params.anchorNodeId}`,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
isError: true,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
nodes,
|
|
105
|
+
anchor: anchorData.anchor,
|
|
106
|
+
anchorInfo: anchorData.anchorInfo,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
29
109
|
export async function handleFindPosition(client, params) {
|
|
30
110
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
limit: 500,
|
|
35
|
-
});
|
|
36
|
-
// Find anchor node if specified
|
|
37
|
-
let anchor;
|
|
38
|
-
let anchorInfo;
|
|
39
|
-
if (params.anchorNodeId) {
|
|
40
|
-
const anchorNode = nodes.find(n => n._id === params.anchorNodeId);
|
|
41
|
-
if (!anchorNode) {
|
|
42
|
-
return {
|
|
43
|
-
content: [
|
|
44
|
-
{
|
|
45
|
-
type: 'text',
|
|
46
|
-
text: `Anchor node not found: ${params.anchorNodeId}`,
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
isError: true,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
anchor = {
|
|
53
|
-
x: anchorNode.x,
|
|
54
|
-
y: anchorNode.y,
|
|
55
|
-
width: anchorNode.width,
|
|
56
|
-
height: anchorNode.height,
|
|
57
|
-
};
|
|
58
|
-
anchorInfo = {
|
|
59
|
-
id: anchorNode._id,
|
|
60
|
-
name: anchorNode.name,
|
|
61
|
-
};
|
|
111
|
+
const prepared = await prepareSearchParams(client, params);
|
|
112
|
+
if ('error' in prepared) {
|
|
113
|
+
return prepared.error;
|
|
62
114
|
}
|
|
63
|
-
|
|
115
|
+
const { nodes, anchor, anchorInfo } = prepared;
|
|
64
116
|
const result = findFreePosition({
|
|
65
|
-
width: params.width,
|
|
66
|
-
height: params.height,
|
|
67
|
-
nodes,
|
|
117
|
+
width: unitsToPixels(params.width),
|
|
118
|
+
height: unitsToPixels(params.height),
|
|
119
|
+
nodes: nodes,
|
|
68
120
|
anchor,
|
|
69
121
|
direction: params.direction,
|
|
70
|
-
gap: params.gap,
|
|
71
|
-
startX: params.startX,
|
|
72
|
-
startY: params.startY,
|
|
122
|
+
gap: params.gap !== undefined ? unitsToPixels(params.gap) : undefined,
|
|
123
|
+
startX: params.startX !== undefined ? unitsToPixels(params.startX) : undefined,
|
|
124
|
+
startY: params.startY !== undefined ? unitsToPixels(params.startY) : undefined,
|
|
73
125
|
});
|
|
74
126
|
if (result.found && result.position) {
|
|
75
|
-
return
|
|
76
|
-
content: [
|
|
77
|
-
{
|
|
78
|
-
type: 'text',
|
|
79
|
-
text: JSON.stringify({
|
|
80
|
-
found: true,
|
|
81
|
-
position: result.position,
|
|
82
|
-
anchor: anchorInfo || null,
|
|
83
|
-
direction: params.direction || 'right',
|
|
84
|
-
searchSteps: result.searchSteps,
|
|
85
|
-
}, null, 2),
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
return {
|
|
92
|
-
content: [
|
|
93
|
-
{
|
|
94
|
-
type: 'text',
|
|
95
|
-
text: JSON.stringify({
|
|
96
|
-
found: false,
|
|
97
|
-
error: result.error || 'No valid position found',
|
|
98
|
-
searchSteps: result.searchSteps,
|
|
99
|
-
}, null, 2),
|
|
100
|
-
},
|
|
101
|
-
],
|
|
102
|
-
isError: true,
|
|
103
|
-
};
|
|
127
|
+
return formatSuccessResponse(result.position, anchorInfo, params.direction, result.searchSteps);
|
|
104
128
|
}
|
|
129
|
+
return formatErrorResponse(result.error, result.searchSteps);
|
|
105
130
|
}
|
|
106
131
|
catch (error) {
|
|
107
132
|
return {
|
package/dist/tools/grid-calc.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { findCollidingNodes, findSuggestedPosition, } from '../utils/collision.js';
|
|
3
|
+
import { pixelsToUnits, unitsToPixels } from '../utils/units.js';
|
|
3
4
|
const GRID_SIZE = 16;
|
|
4
5
|
export const gridCalcSchema = z
|
|
5
6
|
.object({
|
|
@@ -9,23 +10,23 @@ export const gridCalcSchema = z
|
|
|
9
10
|
value: z
|
|
10
11
|
.number()
|
|
11
12
|
.optional()
|
|
12
|
-
.describe('Value to snap to the grid (for snap operation)'),
|
|
13
|
+
.describe('Value to snap to the grid in units (for snap operation)'),
|
|
13
14
|
position: z
|
|
14
15
|
.number()
|
|
15
16
|
.optional()
|
|
16
|
-
.describe('Current x or y position (for next_x/next_y operations)'),
|
|
17
|
+
.describe('Current x or y position in units (for next_x/next_y operations)'),
|
|
17
18
|
size: z
|
|
18
19
|
.number()
|
|
19
20
|
.optional()
|
|
20
|
-
.describe('Width or height of the current node (for next_x/next_y)'),
|
|
21
|
+
.describe('Width or height of the current node in units (for next_x/next_y)'),
|
|
21
22
|
gap: z
|
|
22
23
|
.number()
|
|
23
24
|
.optional()
|
|
24
|
-
.describe('Gap between nodes in
|
|
25
|
+
.describe('Gap between nodes in grid units. Use 6+ for connected nodes, 1 for unrelated (default: 1)'),
|
|
25
26
|
units: z
|
|
26
27
|
.number()
|
|
27
28
|
.optional()
|
|
28
|
-
.describe('Number of grid units
|
|
29
|
+
.describe('Number of grid units (for grid_units operation - deprecated, returns same value)'),
|
|
29
30
|
// Collision-aware params (optional)
|
|
30
31
|
boardId: z
|
|
31
32
|
.string()
|
|
@@ -34,19 +35,19 @@ export const gridCalcSchema = z
|
|
|
34
35
|
nodeWidth: z
|
|
35
36
|
.number()
|
|
36
37
|
.optional()
|
|
37
|
-
.describe('Width of node being placed (required with boardId)'),
|
|
38
|
+
.describe('Width of node being placed in grid units (required with boardId)'),
|
|
38
39
|
nodeHeight: z
|
|
39
40
|
.number()
|
|
40
41
|
.optional()
|
|
41
|
-
.describe('Height of node being placed (required with boardId)'),
|
|
42
|
+
.describe('Height of node being placed in grid units (required with boardId)'),
|
|
42
43
|
nodeY: z
|
|
43
44
|
.number()
|
|
44
45
|
.optional()
|
|
45
|
-
.describe('Y position of node (required for next_x collision check)'),
|
|
46
|
+
.describe('Y position of node in grid units (required for next_x collision check)'),
|
|
46
47
|
nodeX: z
|
|
47
48
|
.number()
|
|
48
49
|
.optional()
|
|
49
|
-
.describe('X position of node (required for next_y collision check)'),
|
|
50
|
+
.describe('X position of node in grid units (required for next_y collision check)'),
|
|
50
51
|
excludeNodeId: z
|
|
51
52
|
.string()
|
|
52
53
|
.optional()
|
|
@@ -85,30 +86,26 @@ export function snapToGrid(value) {
|
|
|
85
86
|
}
|
|
86
87
|
/**
|
|
87
88
|
* Calculate the next position after a node, with a gap.
|
|
88
|
-
*
|
|
89
|
+
* All inputs and output are in grid units.
|
|
89
90
|
*/
|
|
90
91
|
export function calculateNextPosition(position, size, gap) {
|
|
91
|
-
return
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Convert grid units to pixels.
|
|
95
|
-
* 1 grid unit = 16 pixels.
|
|
96
|
-
*/
|
|
97
|
-
export function gridUnitsToPixels(units) {
|
|
98
|
-
return units * GRID_SIZE;
|
|
92
|
+
return position + size + gap;
|
|
99
93
|
}
|
|
100
94
|
/**
|
|
101
95
|
* Calculate result based on operation type.
|
|
96
|
+
* All operations now work with grid units.
|
|
102
97
|
*/
|
|
103
98
|
function calculateResult(params) {
|
|
104
99
|
if (params.operation === 'snap') {
|
|
105
|
-
|
|
100
|
+
// Snap to nearest integer unit
|
|
101
|
+
return Math.round(params.value);
|
|
106
102
|
}
|
|
107
103
|
if (params.operation === 'grid_units') {
|
|
108
|
-
return
|
|
104
|
+
// Deprecated: just return the units value (identity operation)
|
|
105
|
+
return params.units;
|
|
109
106
|
}
|
|
110
107
|
// next_x or next_y
|
|
111
|
-
return calculateNextPosition(params.position, params.size, params.gap ??
|
|
108
|
+
return calculateNextPosition(params.position, params.size, params.gap ?? 1);
|
|
112
109
|
}
|
|
113
110
|
/**
|
|
114
111
|
* Build input object for result output.
|
|
@@ -156,18 +153,38 @@ function supportsCollisionCheck(operation) {
|
|
|
156
153
|
*/
|
|
157
154
|
async function checkPositionCollision(params, baseResult, client) {
|
|
158
155
|
const nodes = await client.listNodes({ boardId: params.boardId, limit: 500 });
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
156
|
+
// Convert units to pixels for collision checking
|
|
157
|
+
const xPixels = params.operation === 'next_x'
|
|
158
|
+
? unitsToPixels(baseResult.result)
|
|
159
|
+
: unitsToPixels(params.nodeX ?? 0);
|
|
160
|
+
const yPixels = params.operation === 'next_y'
|
|
161
|
+
? unitsToPixels(baseResult.result)
|
|
162
|
+
: unitsToPixels(params.nodeY ?? 0);
|
|
163
|
+
const rectPixels = {
|
|
164
|
+
x: xPixels,
|
|
165
|
+
y: yPixels,
|
|
166
|
+
width: unitsToPixels(params.nodeWidth),
|
|
167
|
+
height: unitsToPixels(params.nodeHeight),
|
|
168
|
+
};
|
|
169
|
+
const collisionResult = findCollidingNodes(rectPixels, nodes, params.excludeNodeId);
|
|
163
170
|
const collision = {
|
|
164
171
|
checked: true,
|
|
165
172
|
collides: collisionResult.collides,
|
|
166
|
-
collidingNodes: collisionResult.collidingNodes
|
|
173
|
+
collidingNodes: collisionResult.collidingNodes.map(node => ({
|
|
174
|
+
...node,
|
|
175
|
+
rect: {
|
|
176
|
+
x: pixelsToUnits(node.rect.x),
|
|
177
|
+
y: pixelsToUnits(node.rect.y),
|
|
178
|
+
width: pixelsToUnits(node.rect.width),
|
|
179
|
+
height: pixelsToUnits(node.rect.height),
|
|
180
|
+
},
|
|
181
|
+
})),
|
|
167
182
|
};
|
|
168
183
|
if (collisionResult.collides) {
|
|
169
184
|
const direction = params.operation === 'next_x' ? 'x' : 'y';
|
|
170
|
-
|
|
185
|
+
const suggestedPixels = findSuggestedPosition(xPixels, yPixels, rectPixels.width, rectPixels.height, nodes, direction, params.excludeNodeId);
|
|
186
|
+
collision.suggestedPosition =
|
|
187
|
+
suggestedPixels !== null ? pixelsToUnits(suggestedPixels) : null;
|
|
171
188
|
}
|
|
172
189
|
return collision;
|
|
173
190
|
}
|
package/dist/tools/index.js
CHANGED
|
@@ -313,11 +313,11 @@ export function registerTools(server, client, config) {
|
|
|
313
313
|
},
|
|
314
314
|
x: {
|
|
315
315
|
type: 'number',
|
|
316
|
-
description: 'X position (
|
|
316
|
+
description: 'X position in grid units (1 unit = 16px, default: 6)',
|
|
317
317
|
},
|
|
318
318
|
y: {
|
|
319
319
|
type: 'number',
|
|
320
|
-
description: 'Y position (
|
|
320
|
+
description: 'Y position in grid units (1 unit = 16px, default: 6)',
|
|
321
321
|
},
|
|
322
322
|
width: {
|
|
323
323
|
type: 'number',
|
|
@@ -363,19 +363,19 @@ export function registerTools(server, client, config) {
|
|
|
363
363
|
},
|
|
364
364
|
x: {
|
|
365
365
|
type: 'number',
|
|
366
|
-
description: 'X position
|
|
366
|
+
description: 'X position in grid units (1 unit = 16px)',
|
|
367
367
|
},
|
|
368
368
|
y: {
|
|
369
369
|
type: 'number',
|
|
370
|
-
description: 'Y position
|
|
370
|
+
description: 'Y position in grid units (1 unit = 16px)',
|
|
371
371
|
},
|
|
372
372
|
width: {
|
|
373
373
|
type: 'number',
|
|
374
|
-
description: 'Node width in
|
|
374
|
+
description: 'Node width in grid units (1 unit = 16px)',
|
|
375
375
|
},
|
|
376
376
|
height: {
|
|
377
377
|
type: 'number',
|
|
378
|
-
description: 'Node height in
|
|
378
|
+
description: 'Node height in grid units (1 unit = 16px)',
|
|
379
379
|
},
|
|
380
380
|
autoSize: {
|
|
381
381
|
type: 'boolean',
|
|
@@ -423,10 +423,22 @@ export function registerTools(server, client, config) {
|
|
|
423
423
|
name: { type: 'string', description: 'New name' },
|
|
424
424
|
content: { type: 'string', description: 'New content' },
|
|
425
425
|
color: { type: 'string', description: 'Node color' },
|
|
426
|
-
x: {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
426
|
+
x: {
|
|
427
|
+
type: 'number',
|
|
428
|
+
description: 'X position in grid units (1 unit = 16px)',
|
|
429
|
+
},
|
|
430
|
+
y: {
|
|
431
|
+
type: 'number',
|
|
432
|
+
description: 'Y position in grid units (1 unit = 16px)',
|
|
433
|
+
},
|
|
434
|
+
width: {
|
|
435
|
+
type: 'number',
|
|
436
|
+
description: 'Width in grid units (1 unit = 16px)',
|
|
437
|
+
},
|
|
438
|
+
height: {
|
|
439
|
+
type: 'number',
|
|
440
|
+
description: 'Height in grid units (1 unit = 16px)',
|
|
441
|
+
},
|
|
430
442
|
},
|
|
431
443
|
required: ['nodeId'],
|
|
432
444
|
},
|
|
@@ -563,15 +575,21 @@ export function registerTools(server, client, config) {
|
|
|
563
575
|
description: 'Node type (default: text).',
|
|
564
576
|
},
|
|
565
577
|
color: { type: 'string', description: 'Node color' },
|
|
566
|
-
x: {
|
|
567
|
-
|
|
578
|
+
x: {
|
|
579
|
+
type: 'number',
|
|
580
|
+
description: 'X position in grid units (1 unit = 16px)',
|
|
581
|
+
},
|
|
582
|
+
y: {
|
|
583
|
+
type: 'number',
|
|
584
|
+
description: 'Y position in grid units (1 unit = 16px)',
|
|
585
|
+
},
|
|
568
586
|
width: {
|
|
569
587
|
type: 'number',
|
|
570
|
-
description: 'DO NOT USE - auto-calculated from name',
|
|
588
|
+
description: 'Width in grid units (1 unit = 16px, DO NOT USE - auto-calculated from name)',
|
|
571
589
|
},
|
|
572
590
|
height: {
|
|
573
591
|
type: 'number',
|
|
574
|
-
description: 'DO NOT USE - auto-calculated from name',
|
|
592
|
+
description: 'Height in grid units (1 unit = 16px, DO NOT USE - auto-calculated from name)',
|
|
575
593
|
},
|
|
576
594
|
},
|
|
577
595
|
required: ['tempId', 'name'],
|
|
@@ -733,7 +751,7 @@ export function registerTools(server, client, config) {
|
|
|
733
751
|
},
|
|
734
752
|
{
|
|
735
753
|
name: 'mentagen_grid_calc',
|
|
736
|
-
description: 'Math helper for calculating node positions
|
|
754
|
+
description: 'Math helper for calculating node positions in grid units (1 unit = 16px). Use gap=6+ for connected nodes, gap=1 for unrelated nodes. Operations: "snap" rounds to nearest integer unit, "next_x"/"next_y" calculate position after a node, "grid_units" is deprecated (returns same value).',
|
|
737
755
|
inputSchema: {
|
|
738
756
|
type: 'object',
|
|
739
757
|
properties: {
|
|
@@ -744,23 +762,23 @@ export function registerTools(server, client, config) {
|
|
|
744
762
|
},
|
|
745
763
|
value: {
|
|
746
764
|
type: 'number',
|
|
747
|
-
description: 'Value to snap to the grid (for snap operation)',
|
|
765
|
+
description: 'Value to snap to the grid in units (for snap operation)',
|
|
748
766
|
},
|
|
749
767
|
position: {
|
|
750
768
|
type: 'number',
|
|
751
|
-
description: 'Current x or y position (for next_x/next_y operations)',
|
|
769
|
+
description: 'Current x or y position in units (for next_x/next_y operations)',
|
|
752
770
|
},
|
|
753
771
|
size: {
|
|
754
772
|
type: 'number',
|
|
755
|
-
description: 'Width or height of the current node (for next_x/next_y)',
|
|
773
|
+
description: 'Width or height of the current node in units (for next_x/next_y)',
|
|
756
774
|
},
|
|
757
775
|
gap: {
|
|
758
776
|
type: 'number',
|
|
759
|
-
description: 'Gap in
|
|
777
|
+
description: 'Gap in grid units (1 unit = 16px). Use 6+ for connected nodes, 1 for unrelated (default: 1)',
|
|
760
778
|
},
|
|
761
779
|
units: {
|
|
762
780
|
type: 'number',
|
|
763
|
-
description: 'Number of grid units
|
|
781
|
+
description: 'Number of grid units (1 unit = 16px, for grid_units operation - deprecated, returns same value)',
|
|
764
782
|
},
|
|
765
783
|
boardId: {
|
|
766
784
|
type: 'string',
|
|
@@ -768,19 +786,19 @@ export function registerTools(server, client, config) {
|
|
|
768
786
|
},
|
|
769
787
|
nodeWidth: {
|
|
770
788
|
type: 'number',
|
|
771
|
-
description: 'Width of node being placed (required with boardId)',
|
|
789
|
+
description: 'Width of node being placed in grid units (1 unit = 16px, required with boardId)',
|
|
772
790
|
},
|
|
773
791
|
nodeHeight: {
|
|
774
792
|
type: 'number',
|
|
775
|
-
description: 'Height of node being placed (required with boardId)',
|
|
793
|
+
description: 'Height of node being placed in grid units (1 unit = 16px, required with boardId)',
|
|
776
794
|
},
|
|
777
795
|
nodeY: {
|
|
778
796
|
type: 'number',
|
|
779
|
-
description: 'Y position of node (for next_x collision check)',
|
|
797
|
+
description: 'Y position of node in grid units (1 unit = 16px, for next_x collision check)',
|
|
780
798
|
},
|
|
781
799
|
nodeX: {
|
|
782
800
|
type: 'number',
|
|
783
|
-
description: 'X position of node (for next_y collision check)',
|
|
801
|
+
description: 'X position of node in grid units (1 unit = 16px, for next_y collision check)',
|
|
784
802
|
},
|
|
785
803
|
excludeNodeId: {
|
|
786
804
|
type: 'string',
|
|
@@ -802,19 +820,19 @@ export function registerTools(server, client, config) {
|
|
|
802
820
|
},
|
|
803
821
|
x: {
|
|
804
822
|
type: 'number',
|
|
805
|
-
description: 'Left position of the rectangle',
|
|
823
|
+
description: 'Left position of the rectangle in grid units (1 unit = 16px)',
|
|
806
824
|
},
|
|
807
825
|
y: {
|
|
808
826
|
type: 'number',
|
|
809
|
-
description: 'Top position of the rectangle',
|
|
827
|
+
description: 'Top position of the rectangle in grid units (1 unit = 16px)',
|
|
810
828
|
},
|
|
811
829
|
width: {
|
|
812
830
|
type: 'number',
|
|
813
|
-
description: 'Width of the rectangle',
|
|
831
|
+
description: 'Width of the rectangle in grid units (1 unit = 16px)',
|
|
814
832
|
},
|
|
815
833
|
height: {
|
|
816
834
|
type: 'number',
|
|
817
|
-
description: 'Height of the rectangle',
|
|
835
|
+
description: 'Height of the rectangle in grid units (1 unit = 16px)',
|
|
818
836
|
},
|
|
819
837
|
excludeNodeId: {
|
|
820
838
|
type: 'string',
|
|
@@ -840,7 +858,7 @@ export function registerTools(server, client, config) {
|
|
|
840
858
|
},
|
|
841
859
|
{
|
|
842
860
|
name: 'mentagen_find_position',
|
|
843
|
-
description: 'Find a valid non-colliding position for a new node. IMPORTANT: Use gap=
|
|
861
|
+
description: 'Find a valid non-colliding position for a new node. IMPORTANT: Use gap=6+ (96px+) when nodes will be connected by edges, otherwise gap=1 (16px) is fine for unrelated nodes. All positions are in grid units (1 unit = 16px).',
|
|
844
862
|
inputSchema: {
|
|
845
863
|
type: 'object',
|
|
846
864
|
properties: {
|
|
@@ -850,11 +868,11 @@ export function registerTools(server, client, config) {
|
|
|
850
868
|
},
|
|
851
869
|
width: {
|
|
852
870
|
type: 'number',
|
|
853
|
-
description: 'Width of the node to place',
|
|
871
|
+
description: 'Width of the node to place in grid units (1 unit = 16px)',
|
|
854
872
|
},
|
|
855
873
|
height: {
|
|
856
874
|
type: 'number',
|
|
857
|
-
description: 'Height of the node to place',
|
|
875
|
+
description: 'Height of the node to place in grid units (1 unit = 16px)',
|
|
858
876
|
},
|
|
859
877
|
anchorNodeId: {
|
|
860
878
|
type: 'string',
|
|
@@ -867,15 +885,15 @@ export function registerTools(server, client, config) {
|
|
|
867
885
|
},
|
|
868
886
|
gap: {
|
|
869
887
|
type: 'number',
|
|
870
|
-
description: 'Gap between nodes in
|
|
888
|
+
description: 'Gap between nodes in grid units (1 unit = 16px). Use 6+ for connected nodes (edges), 1 for unrelated nodes',
|
|
871
889
|
},
|
|
872
890
|
startX: {
|
|
873
891
|
type: 'number',
|
|
874
|
-
description: 'Explicit start X (ignored if anchorNodeId provided)',
|
|
892
|
+
description: 'Explicit start X in grid units (1 unit = 16px, ignored if anchorNodeId provided)',
|
|
875
893
|
},
|
|
876
894
|
startY: {
|
|
877
895
|
type: 'number',
|
|
878
|
-
description: 'Explicit start Y (ignored if anchorNodeId provided)',
|
|
896
|
+
description: 'Explicit start Y in grid units (1 unit = 16px, ignored if anchorNodeId provided)',
|
|
879
897
|
},
|
|
880
898
|
},
|
|
881
899
|
required: ['boardId', 'width', 'height'],
|
|
@@ -883,7 +901,7 @@ export function registerTools(server, client, config) {
|
|
|
883
901
|
},
|
|
884
902
|
{
|
|
885
903
|
name: 'mentagen_size_calc',
|
|
886
|
-
description: 'Calculate recommended node dimensions based on name and type. Returns width and height values (
|
|
904
|
+
description: 'Calculate recommended node dimensions based on name and type. Returns width and height values in grid units (1 unit = 16px) sized to fit the title. Content is intentionally ignored — nodes are sized for their title with content scrollable inside.',
|
|
887
905
|
inputSchema: {
|
|
888
906
|
type: 'object',
|
|
889
907
|
properties: {
|
package/dist/tools/list-nodes.js
CHANGED
|
@@ -2,6 +2,7 @@ import Papa from 'papaparse';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { formatDate } from '../utils/ejson.js';
|
|
4
4
|
import { formatError } from '../utils/errors.js';
|
|
5
|
+
import { pixelsToUnits } from '../utils/units.js';
|
|
5
6
|
import { getNodeUrl } from '../utils/urls.js';
|
|
6
7
|
export const listNodesSchema = z.object({
|
|
7
8
|
boardId: z.string().describe('The board ID to list nodes from'),
|
|
@@ -27,10 +28,10 @@ function formatNodesTsv(nodes) {
|
|
|
27
28
|
id: n._id,
|
|
28
29
|
name: n.name,
|
|
29
30
|
type: n.type,
|
|
30
|
-
x: n.x,
|
|
31
|
-
y: n.y,
|
|
32
|
-
width: n.width,
|
|
33
|
-
height: n.height,
|
|
31
|
+
x: pixelsToUnits(n.x),
|
|
32
|
+
y: pixelsToUnits(n.y),
|
|
33
|
+
width: pixelsToUnits(n.width),
|
|
34
|
+
height: pixelsToUnits(n.height),
|
|
34
35
|
content: truncateContent(n.content ?? null, MAX_CONTENT_LENGTH),
|
|
35
36
|
updatedAt: formatDate(n.updatedAt),
|
|
36
37
|
link: n.link,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Papa from 'papaparse';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { formatError } from '../utils/errors.js';
|
|
4
|
+
import { pixelsToUnits } from '../utils/units.js';
|
|
4
5
|
export const listPositionsSchema = z.object({
|
|
5
6
|
boardId: z.string().describe('The board ID to list node positions from'),
|
|
6
7
|
limit: z
|
|
@@ -13,10 +14,10 @@ function formatPositionsTsv(nodes) {
|
|
|
13
14
|
const rows = nodes.map(n => ({
|
|
14
15
|
id: n._id,
|
|
15
16
|
name: n.name,
|
|
16
|
-
x: n.x,
|
|
17
|
-
y: n.y,
|
|
18
|
-
w: n.width,
|
|
19
|
-
h: n.height,
|
|
17
|
+
x: pixelsToUnits(n.x),
|
|
18
|
+
y: pixelsToUnits(n.y),
|
|
19
|
+
w: pixelsToUnits(n.width),
|
|
20
|
+
h: pixelsToUnits(n.height),
|
|
20
21
|
}));
|
|
21
22
|
return Papa.unparse(rows, {
|
|
22
23
|
delimiter: '\t',
|
package/dist/tools/read.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { formatError } from '../utils/errors.js';
|
|
3
|
+
import { pixelsToUnits } from '../utils/units.js';
|
|
3
4
|
import { getNodeUrl } from '../utils/urls.js';
|
|
4
5
|
export const readSchema = z.object({
|
|
5
6
|
boardId: z.string().describe('The board ID containing the node'),
|
|
@@ -13,9 +14,21 @@ export async function handleRead(client, params, baseUrl) {
|
|
|
13
14
|
});
|
|
14
15
|
// Exclude large fields that aren't useful for AI consumption
|
|
15
16
|
const { _vectors, __v, textDetections, ...nodeData } = node;
|
|
16
|
-
//
|
|
17
|
+
// Convert position fields from pixels to units
|
|
17
18
|
const response = {
|
|
18
19
|
...nodeData,
|
|
20
|
+
x: 'x' in nodeData && typeof nodeData.x === 'number'
|
|
21
|
+
? pixelsToUnits(nodeData.x)
|
|
22
|
+
: nodeData.x,
|
|
23
|
+
y: 'y' in nodeData && typeof nodeData.y === 'number'
|
|
24
|
+
? pixelsToUnits(nodeData.y)
|
|
25
|
+
: nodeData.y,
|
|
26
|
+
width: 'width' in nodeData && typeof nodeData.width === 'number'
|
|
27
|
+
? pixelsToUnits(nodeData.width)
|
|
28
|
+
: nodeData.width,
|
|
29
|
+
height: 'height' in nodeData && typeof nodeData.height === 'number'
|
|
30
|
+
? pixelsToUnits(nodeData.height)
|
|
31
|
+
: nodeData.height,
|
|
19
32
|
link: getNodeUrl(baseUrl, params.boardId, params.nodeId),
|
|
20
33
|
};
|
|
21
34
|
return {
|
package/dist/tools/size-calc.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { pixelsToUnits } from '../utils/units.js';
|
|
2
3
|
// Grid and constraint constants (from src/common/boards/constraints.ts)
|
|
4
|
+
// Values in pixels (internal calculations)
|
|
3
5
|
const GRID = 16;
|
|
4
|
-
const MIN_WIDTH = 128;
|
|
5
|
-
const MIN_HEIGHT = 64;
|
|
6
|
-
const MAX_WIDTH = 512; //
|
|
7
|
-
const MAX_HEIGHT = 512;
|
|
6
|
+
const MIN_WIDTH = 128; // 8 units
|
|
7
|
+
const MIN_HEIGHT = 64; // 4 units
|
|
8
|
+
const MAX_WIDTH = 512; // 32 units - reasonable max for auto-sizing (not the 4096 absolute max)
|
|
9
|
+
const MAX_HEIGHT = 512; // 32 units
|
|
8
10
|
// Text measurement constants (from measure-node-size.ts)
|
|
9
11
|
const MAX_AUTO_WIDTH = 400;
|
|
10
12
|
const PADDING_X = 12;
|
|
@@ -96,7 +98,10 @@ export function calculateNodeSize(name, type) {
|
|
|
96
98
|
return { width, height };
|
|
97
99
|
}
|
|
98
100
|
export function handleSizeCalc(params) {
|
|
99
|
-
const { width, height } = calculateNodeSize(params.name, params.type);
|
|
101
|
+
const { width: widthPixels, height: heightPixels } = calculateNodeSize(params.name, params.type);
|
|
102
|
+
// Convert output to units
|
|
103
|
+
const width = pixelsToUnits(widthPixels);
|
|
104
|
+
const height = pixelsToUnits(heightPixels);
|
|
100
105
|
return {
|
|
101
106
|
content: [
|
|
102
107
|
{
|
package/dist/tools/update.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { NodeType } from '../client/types.js';
|
|
3
3
|
import { formatError } from '../utils/errors.js';
|
|
4
|
+
import { pixelsToUnits, unitsToPixels } from '../utils/units.js';
|
|
4
5
|
import { snapToGrid } from './grid-calc.js';
|
|
5
6
|
import { calculateNodeSize } from './size-calc.js';
|
|
6
7
|
export const updateSchema = z.object({
|
|
@@ -24,22 +25,10 @@ export const updateSchema = z.object({
|
|
|
24
25
|
.string()
|
|
25
26
|
.optional()
|
|
26
27
|
.describe('Node color name (use mentagen_colors to see available colors)'),
|
|
27
|
-
x: z
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
y: z
|
|
32
|
-
.number()
|
|
33
|
-
.optional()
|
|
34
|
-
.describe('Y position on the board canvas (must be multiple of 16)'),
|
|
35
|
-
width: z
|
|
36
|
-
.number()
|
|
37
|
-
.optional()
|
|
38
|
-
.describe('Node width in pixels (must be multiple of 16)'),
|
|
39
|
-
height: z
|
|
40
|
-
.number()
|
|
41
|
-
.optional()
|
|
42
|
-
.describe('Node height in pixels (must be multiple of 16)'),
|
|
28
|
+
x: z.number().optional().describe('X position in grid units'),
|
|
29
|
+
y: z.number().optional().describe('Y position in grid units'),
|
|
30
|
+
width: z.number().optional().describe('Node width in grid units'),
|
|
31
|
+
height: z.number().optional().describe('Node height in grid units'),
|
|
43
32
|
autoSize: z
|
|
44
33
|
.boolean()
|
|
45
34
|
.optional()
|
|
@@ -85,7 +74,7 @@ function buildUpdateData(params, nodeType) {
|
|
|
85
74
|
for (const field of POSITION_FIELDS) {
|
|
86
75
|
const value = params[field];
|
|
87
76
|
if (value !== undefined) {
|
|
88
|
-
data[field] = snapToGrid(value);
|
|
77
|
+
data[field] = snapToGrid(unitsToPixels(value));
|
|
89
78
|
}
|
|
90
79
|
}
|
|
91
80
|
return Object.keys(data).length > 0 ? data : null;
|
|
@@ -116,10 +105,10 @@ function formatSuccessResponse(node) {
|
|
|
116
105
|
node: {
|
|
117
106
|
id: node._id,
|
|
118
107
|
name: node.name,
|
|
119
|
-
x: node.x,
|
|
120
|
-
y: node.y,
|
|
121
|
-
width: node.width,
|
|
122
|
-
height: node.height,
|
|
108
|
+
x: pixelsToUnits(node.x),
|
|
109
|
+
y: pixelsToUnits(node.y),
|
|
110
|
+
width: pixelsToUnits(node.width),
|
|
111
|
+
height: pixelsToUnits(node.height),
|
|
123
112
|
updatedAt: node.updatedAt || new Date().toISOString(),
|
|
124
113
|
},
|
|
125
114
|
};
|
|
@@ -132,8 +121,17 @@ export async function handleUpdate(client, params) {
|
|
|
132
121
|
const { node: currentNode, autoSize } = await getNodeContext(client, params);
|
|
133
122
|
const data = buildUpdateData({
|
|
134
123
|
...params,
|
|
135
|
-
|
|
136
|
-
|
|
124
|
+
// Convert autoSize from pixels to units since buildUpdateData expects units
|
|
125
|
+
width: params.width !== undefined
|
|
126
|
+
? params.width
|
|
127
|
+
: autoSize?.width !== undefined
|
|
128
|
+
? pixelsToUnits(autoSize.width)
|
|
129
|
+
: undefined,
|
|
130
|
+
height: params.height !== undefined
|
|
131
|
+
? params.height
|
|
132
|
+
: autoSize?.height !== undefined
|
|
133
|
+
? pixelsToUnits(autoSize.height)
|
|
134
|
+
: undefined,
|
|
137
135
|
}, currentNode.type);
|
|
138
136
|
if (!data) {
|
|
139
137
|
return {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit conversion utilities for grid-based positioning.
|
|
3
|
+
* 1 grid unit = 16 pixels
|
|
4
|
+
*/
|
|
5
|
+
const GRID_SIZE = 16;
|
|
6
|
+
/**
|
|
7
|
+
* Convert grid units to pixels.
|
|
8
|
+
* Rounds the input to the nearest integer before conversion.
|
|
9
|
+
*/
|
|
10
|
+
export function unitsToPixels(units) {
|
|
11
|
+
return Math.round(units) * GRID_SIZE;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Convert pixels to grid units.
|
|
15
|
+
* Rounds the result to the nearest integer unit.
|
|
16
|
+
*/
|
|
17
|
+
export function pixelsToUnits(pixels) {
|
|
18
|
+
return Math.round(pixels / GRID_SIZE);
|
|
19
|
+
}
|