callman-mcp 0.2.0 → 0.3.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +2 -2
  3. package/dist/client/api.d.ts +1 -0
  4. package/dist/client/api.d.ts.map +1 -1
  5. package/dist/client/api.js +2 -0
  6. package/dist/client/api.js.map +1 -1
  7. package/dist/domain/diagram/build.d.ts +112 -63
  8. package/dist/domain/diagram/build.d.ts.map +1 -1
  9. package/dist/domain/diagram/build.js +49 -165
  10. package/dist/domain/diagram/build.js.map +1 -1
  11. package/dist/domain/diagram/layout.d.ts +3 -4
  12. package/dist/domain/diagram/layout.d.ts.map +1 -1
  13. package/dist/domain/diagram/layout.js +12 -45
  14. package/dist/domain/diagram/layout.js.map +1 -1
  15. package/dist/domain/diagram/patch.d.ts +298 -80
  16. package/dist/domain/diagram/patch.d.ts.map +1 -1
  17. package/dist/domain/diagram/patch.js +60 -273
  18. package/dist/domain/diagram/patch.js.map +1 -1
  19. package/dist/domain/diagram/style.d.ts +4 -12
  20. package/dist/domain/diagram/style.d.ts.map +1 -1
  21. package/dist/domain/diagram/style.js +8 -84
  22. package/dist/domain/diagram/style.js.map +1 -1
  23. package/dist/domain/diagram/summarize.d.ts +25 -24
  24. package/dist/domain/diagram/summarize.d.ts.map +1 -1
  25. package/dist/domain/diagram/summarize.js +20 -35
  26. package/dist/domain/diagram/summarize.js.map +1 -1
  27. package/dist/domain/requestIntegrations.d.ts +20 -20
  28. package/dist/domain/requestShape.d.ts +32 -32
  29. package/dist/resources/docs/diagramSchema.d.ts +1 -1
  30. package/dist/resources/docs/diagramSchema.d.ts.map +1 -1
  31. package/dist/resources/docs/diagramSchema.js +55 -10
  32. package/dist/resources/docs/diagramSchema.js.map +1 -1
  33. package/dist/resources/generated/aiPacks.d.ts +1 -1
  34. package/dist/resources/generated/aiPacks.js +2 -2
  35. package/dist/tools/diagrams.d.ts.map +1 -1
  36. package/dist/tools/diagrams.js +244 -42
  37. package/dist/tools/diagrams.js.map +1 -1
  38. package/dist-cjs/client/api.js +2 -0
  39. package/dist-cjs/domain/diagram/build.js +53 -169
  40. package/dist-cjs/domain/diagram/layout.js +11 -44
  41. package/dist-cjs/domain/diagram/patch.js +57 -270
  42. package/dist-cjs/domain/diagram/style.js +11 -88
  43. package/dist-cjs/domain/diagram/summarize.js +22 -35
  44. package/dist-cjs/resources/docs/diagramSchema.js +55 -10
  45. package/dist-cjs/resources/generated/aiPacks.js +2 -2
  46. package/dist-cjs/tools/diagrams.js +240 -38
  47. package/package.json +2 -2
@@ -3,13 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.applyDiagramPatch = exports.diagramPatchOpSchema = void 0;
4
4
  const callman_core_1 = require("callman-core");
5
5
  const zod_1 = require("zod");
6
- const errors_js_1 = require("../../client/errors.js");
7
- const ids_js_1 = require("../../lib/ids.js");
8
6
  const strict_js_1 = require("../../lib/strict.js");
9
7
  const build_js_1 = require("./build.js");
10
8
  const style_js_1 = require("./style.js");
11
9
  // Incremental edits to a stored diagram — the model sends operations, not a
12
- // 200-node document. Removing a node drops the edges attached to it.
10
+ // 200-node document. The vocabulary is callman-core's authoring ops (the
11
+ // same list the desktop's AI edits use and callman://docs/diagram-schema
12
+ // documents); this module only adds the strict zod contract and maps the
13
+ // result. Removing a node drops the edges attached to it and lifts its
14
+ // children out; moving a node drops the saved routes of its edges.
13
15
  const nodeShape = build_js_1.diagramNodeInputSchema.shape;
14
16
  const edgeShape = build_js_1.diagramEdgeInputSchema.shape;
15
17
  const nodeChangesSchema = (0, strict_js_1.strictObject)({
@@ -22,6 +24,7 @@ const nodeChangesSchema = (0, strict_js_1.strictObject)({
22
24
  rotation: nodeShape.rotation.unwrap().nullable().optional().describe("Degrees, clockwise; null resets"),
23
25
  locked: nodeShape.locked,
24
26
  hidden: nodeShape.hidden,
27
+ collapsed: nodeShape.collapsed,
25
28
  data: zod_1.z.record(zod_1.z.unknown()).nullable().optional().describe("MERGED shallowly into the node's data; a key set to null is removed, data: null clears it"),
26
29
  }, "field", "updateNode changes");
27
30
  const edgeChangesSchema = (0, strict_js_1.strictObject)({
@@ -33,7 +36,12 @@ const edgeChangesSchema = (0, strict_js_1.strictObject)({
33
36
  routing: edgeShape.routing,
34
37
  markerEnd: edgeShape.markerEnd,
35
38
  markerStart: edgeShape.markerStart,
39
+ sourceSide: zod_1.z.enum(build_js_1.SIDES).nullable().optional().describe("Fixed side at the source; null = floating"),
40
+ targetSide: zod_1.z.enum(build_js_1.SIDES).nullable().optional().describe("Fixed side at the target; null = floating"),
41
+ data: zod_1.z.record(zod_1.z.unknown()).nullable().optional().describe("MERGED shallowly into the edge's data (BPMN: { bpmn: { conditionExpression, isDefault } }); a key set to null is removed, data: null clears it"),
36
42
  }, "field", "updateEdge changes");
43
+ const pointSchema = zod_1.z.object({ x: zod_1.z.number(), y: zod_1.z.number() }).strict();
44
+ const directionSchema = zod_1.z.enum(["DOWN", "RIGHT", "UP", "LEFT"]);
37
45
  // Every op is strict: a field at the wrong level ({ op: "updateNode", id, label })
38
46
  // is an error, not a silent no-op that still counts as "applied".
39
47
  exports.diagramPatchOpSchema = zod_1.z.discriminatedUnion("op", [
@@ -44,276 +52,55 @@ exports.diagramPatchOpSchema = zod_1.z.discriminatedUnion("op", [
44
52
  (0, strict_js_1.strictObject)({ op: zod_1.z.literal("updateEdge"), id: zod_1.z.string().min(1), changes: edgeChangesSchema }, "field", "updateEdge ops"),
45
53
  (0, strict_js_1.strictObject)({ op: zod_1.z.literal("removeEdge"), id: zod_1.z.string().min(1) }, "field", "removeEdge ops"),
46
54
  (0, strict_js_1.strictObject)({ op: zod_1.z.literal("setTitle"), title: zod_1.z.string().min(1).max(200) }, "field", "setTitle ops"),
55
+ (0, strict_js_1.strictObject)({ op: zod_1.z.literal("moveNode"), id: zod_1.z.string().min(1), position: pointSchema.describe("Parent-relative top-left") }, "field", "moveNode ops"),
56
+ (0, strict_js_1.strictObject)({ op: zod_1.z.literal("reparent"), id: zod_1.z.string().min(1), parent: zod_1.z.string().max(64).nullable().describe("Container id, or null for the top level"), position: pointSchema.optional().describe("Position in the new parent; omitted keeps the canvas spot") }, "field", "reparent ops"),
57
+ (0, strict_js_1.strictObject)({
58
+ op: zod_1.z.literal("setEdgeRoute"),
59
+ id: zod_1.z.string().min(1),
60
+ routing: zod_1.z.enum(build_js_1.ROUTINGS).nullable().optional().describe("null = the canvas default"),
61
+ sourceSide: zod_1.z.enum(build_js_1.SIDES).nullable().optional(),
62
+ targetSide: zod_1.z.enum(build_js_1.SIDES).nullable().optional(),
63
+ waypoints: zod_1.z.array(pointSchema).max(50).nullable().optional().describe("Manual bends (absolute); null clears. With nothing else set, the op resets the route"),
64
+ }, "field", "setEdgeRoute ops"),
65
+ (0, strict_js_1.strictObject)({ op: zod_1.z.literal("reorder"), id: zod_1.z.string().min(1), index: zod_1.z.union([zod_1.z.number().int().nonnegative(), zod_1.z.literal("front"), zod_1.z.literal("back")]).describe("Stacking order among siblings") }, "field", "reorder ops"),
66
+ (0, strict_js_1.strictObject)({
67
+ op: zod_1.z.literal("setCanvas"),
68
+ canvas: zod_1.z
69
+ .object({
70
+ background: zod_1.z.enum(["grid", "dots", "blank"]).nullable().optional(),
71
+ gridSize: zod_1.z.number().int().positive().nullable().optional(),
72
+ defaultRouting: zod_1.z.enum(build_js_1.ROUTINGS).nullable().optional(),
73
+ })
74
+ .strict(),
75
+ }, "field", "setCanvas ops"),
76
+ (0, strict_js_1.strictObject)({ op: zod_1.z.literal("group"), ids: zod_1.z.array(zod_1.z.string().min(1)).min(1).max(200), type: zod_1.z.string().max(80).optional().describe("frame (default), pool.horizontal or pool.vertical"), label: zod_1.z.string().max(200).optional(), id: zod_1.z.string().max(64).optional() }, "field", "group ops"),
77
+ (0, strict_js_1.strictObject)({ op: zod_1.z.literal("ungroup"), id: zod_1.z.string().min(1) }, "field", "ungroup ops"),
78
+ (0, strict_js_1.strictObject)({ op: zod_1.z.literal("relayout"), scope: zod_1.z.enum(["new", "all"]), direction: directionSchema.optional() }, "field", "relayout ops"),
47
79
  ]);
48
80
  const applyDiagramPatch = (doc, ops, options = {}) => {
49
- const strict = options.strictTypes !== false;
50
- const nodes = new Map(doc.nodes.map((node) => [node.id, { ...node }]));
51
- const edges = new Map(doc.edges.map((edge) => [edge.id, { ...edge }]));
52
- let title = doc.meta.title;
53
- const addedNodeIds = [];
54
- const removedNodeIds = [];
55
- const removedEdgeIds = [];
56
- const warnings = [];
57
- const moved = new Set();
58
- const nextNodeId = (0, ids_js_1.sequentialIds)("n", nodes.keys());
59
- const nextEdgeId = (0, ids_js_1.sequentialIds)("e", edges.keys());
60
- const resolveType = (type, id) => {
61
- if ((0, callman_core_1.getDiagramComponent)(type)) {
62
- (0, build_js_1.assertNotConnector)(type, id);
63
- return type;
64
- }
65
- const suggestions = (0, build_js_1.suggestComponentTypes)(type);
66
- if (strict) {
67
- throw new errors_js_1.ToolInputError("DIAGRAM_UNKNOWN_COMPONENT_TYPE", `Unknown component type "${type}" on ${id}.`, {
68
- unknownTypes: [{ type, suggestions }],
69
- });
70
- }
71
- warnings.push(`Unknown component type "${type}" on ${id} → rect (suggestions: ${suggestions.join(", ") || "none"})`);
72
- return "rect";
73
- };
74
- for (const op of ops) {
75
- switch (op.op) {
76
- case "setTitle":
77
- title = op.title.trim();
78
- break;
79
- case "addNode": {
80
- const id = op.node.id ?? nextNodeId();
81
- if (nodes.has(id))
82
- throw new errors_js_1.ToolInputError("DIAGRAM_DUPLICATE_NODE_ID", `A node with id "${id}" already exists.`);
83
- if (op.node.parent && !nodes.has(op.node.parent)) {
84
- throw new errors_js_1.ToolInputError("DIAGRAM_PARENT_NOT_FOUND", `Parent "${op.node.parent}" does not exist.`);
85
- }
86
- const type = resolveType(op.node.type, id);
87
- const def = (0, callman_core_1.getDiagramComponent)(type);
88
- nodes.set(id, {
89
- id,
90
- type,
91
- position: op.node.position ?? { x: 0, y: 0 },
92
- size: op.node.size ?? def?.defaultSize ?? { width: 140, height: 60 },
93
- label: op.node.label ?? (type === "rect" && op.node.type !== "rect" ? op.node.type : def?.defaultLabel),
94
- ...(op.node.parent ? { parentId: op.node.parent } : {}),
95
- ...(op.node.style ? { style: (0, style_js_1.applyStyleInput)(undefined, op.node.style) } : {}),
96
- ...(op.node.rotation ? { rotation: op.node.rotation } : {}),
97
- ...(op.node.locked ? { locked: true } : {}),
98
- ...(op.node.hidden ? { hidden: true } : {}),
99
- ...(op.node.data ? { data: op.node.data } : {}),
100
- });
101
- if (op.node.parent) {
102
- const warning = (0, build_js_1.parentWarning)(id, op.node.parent, nodes.get(op.node.parent)?.type);
103
- if (warning)
104
- warnings.push(warning);
105
- }
106
- if (!op.node.position)
107
- addedNodeIds.push(id);
108
- break;
109
- }
110
- case "updateNode": {
111
- const node = nodes.get(op.id);
112
- if (!node)
113
- throw new errors_js_1.ToolInputError("DIAGRAM_NODE_NOT_FOUND", `Node "${op.id}" does not exist.`);
114
- const changes = op.changes;
115
- if (changes.parent && !nodes.has(changes.parent)) {
116
- throw new errors_js_1.ToolInputError("DIAGRAM_PARENT_NOT_FOUND", `Parent "${changes.parent}" does not exist.`);
117
- }
118
- if (changes.parent && changes.parent === op.id)
119
- throw new errors_js_1.ToolInputError("DIAGRAM_PARENT_CYCLE", `Node "${op.id}" cannot contain itself.`);
120
- const next = { ...node };
121
- if (changes.type !== undefined)
122
- next.type = resolveType(changes.type, op.id);
123
- if (changes.label !== undefined)
124
- next.label = changes.label;
125
- if (changes.position !== undefined)
126
- next.position = changes.position;
127
- if (changes.size !== undefined)
128
- next.size = changes.size;
129
- if (changes.parent !== undefined) {
130
- if (changes.parent)
131
- next.parentId = changes.parent;
132
- else
133
- delete next.parentId;
134
- }
135
- if (changes.style !== undefined) {
136
- const style = changes.style === null ? undefined : (0, style_js_1.applyStyleInput)(node.style, changes.style);
137
- if (style)
138
- next.style = style;
139
- else
140
- delete next.style;
141
- }
142
- if (changes.rotation !== undefined) {
143
- if (changes.rotation)
144
- next.rotation = changes.rotation;
145
- else
146
- delete next.rotation;
147
- }
148
- if (changes.locked !== undefined) {
149
- if (changes.locked)
150
- next.locked = true;
151
- else
152
- delete next.locked;
153
- }
154
- if (changes.hidden !== undefined) {
155
- if (changes.hidden)
156
- next.hidden = true;
157
- else
158
- delete next.hidden;
159
- }
160
- if (changes.data !== undefined) {
161
- const data = changes.data === null ? {} : { ...(node.data ?? {}) };
162
- for (const [key, value] of Object.entries(changes.data ?? {})) {
163
- if (value === null)
164
- delete data[key];
165
- else
166
- data[key] = value;
167
- }
168
- if (Object.keys(data).length > 0)
169
- next.data = data;
170
- else
171
- delete next.data;
172
- }
173
- if (changes.position !== undefined || changes.size !== undefined || changes.parent !== undefined)
174
- moved.add(op.id);
175
- if (changes.parent) {
176
- const warning = (0, build_js_1.parentWarning)(op.id, changes.parent, nodes.get(changes.parent)?.type);
177
- if (warning)
178
- warnings.push(warning);
179
- }
180
- nodes.set(op.id, next);
181
- break;
182
- }
183
- case "removeNode": {
184
- if (!nodes.delete(op.id))
185
- throw new errors_js_1.ToolInputError("DIAGRAM_NODE_NOT_FOUND", `Node "${op.id}" does not exist.`);
186
- removedNodeIds.push(op.id);
187
- for (const [edgeId, edge] of [...edges]) {
188
- if (edge.source === op.id || edge.target === op.id) {
189
- edges.delete(edgeId);
190
- removedEdgeIds.push(edgeId);
191
- }
192
- }
193
- // Children of a removed container become top-level.
194
- for (const node of nodes.values())
195
- if (node.parentId === op.id)
196
- delete node.parentId;
197
- break;
198
- }
199
- case "addEdge": {
200
- const id = op.edge.id ?? nextEdgeId();
201
- if (edges.has(id))
202
- throw new errors_js_1.ToolInputError("DIAGRAM_DUPLICATE_EDGE_ID", `An edge with id "${id}" already exists.`);
203
- for (const endpoint of [op.edge.source, op.edge.target]) {
204
- if (!nodes.has(endpoint) && !edges.has(endpoint)) {
205
- throw new errors_js_1.ToolInputError("DIAGRAM_EDGE_ENDPOINT_NOT_FOUND", `Edge endpoint "${endpoint}" does not exist.`);
206
- }
207
- }
208
- const semantic = op.edge.semantic ??
209
- ((0, callman_core_1.isBusComponentType)(nodes.get(op.edge.source)?.type) || (0, callman_core_1.isBusComponentType)(nodes.get(op.edge.target)?.type) ? "bus" : undefined);
210
- edges.set(id, {
211
- id,
212
- source: op.edge.source,
213
- target: op.edge.target,
214
- ...(op.edge.label ? { label: op.edge.label } : {}),
215
- routing: op.edge.routing ?? "orthogonal",
216
- markerEnd: op.edge.markerEnd ?? "classicThin",
217
- ...(op.edge.markerStart && op.edge.markerStart !== "none" ? { markerStart: op.edge.markerStart } : {}),
218
- ...(semantic || op.edge.color ? { style: (0, build_js_1.edgeStyleFor)(semantic, op.edge.color) } : {}),
219
- });
220
- break;
221
- }
222
- case "updateEdge": {
223
- const edge = edges.get(op.id);
224
- if (!edge)
225
- throw new errors_js_1.ToolInputError("DIAGRAM_EDGE_NOT_FOUND", `Edge "${op.id}" does not exist.`);
226
- const changes = op.changes;
227
- const next = { ...edge };
228
- if (changes.source !== undefined)
229
- next.source = changes.source;
230
- if (changes.target !== undefined)
231
- next.target = changes.target;
232
- if (changes.label !== undefined) {
233
- if (changes.label)
234
- next.label = changes.label;
235
- else
236
- delete next.label;
237
- }
238
- if (changes.routing !== undefined)
239
- next.routing = changes.routing;
240
- if (changes.markerEnd !== undefined)
241
- next.markerEnd = changes.markerEnd;
242
- if (changes.markerStart !== undefined) {
243
- if (changes.markerStart === "none")
244
- delete next.markerStart;
245
- else
246
- next.markerStart = changes.markerStart;
247
- }
248
- // The semantic owns the line tuple (dash + width); the color is kept
249
- // unless it is changed explicitly.
250
- if (changes.semantic !== undefined || changes.color !== undefined) {
251
- const stroke = changes.color === null ? undefined : changes.color !== undefined ? (0, style_js_1.toTokenRef)(changes.color) : edge.style?.stroke;
252
- const line = changes.semantic !== undefined ? (0, callman_core_1.semanticStyle)(changes.semantic) : { ...(edge.style ?? {}) };
253
- delete line.stroke;
254
- const style = { ...line, ...(stroke ? { stroke } : {}) };
255
- if (Object.keys(style).length > 0)
256
- next.style = style;
257
- else
258
- delete next.style;
259
- }
260
- for (const endpoint of [next.source, next.target]) {
261
- if (endpoint && !nodes.has(endpoint) && !edges.has(endpoint)) {
262
- throw new errors_js_1.ToolInputError("DIAGRAM_EDGE_ENDPOINT_NOT_FOUND", `Edge endpoint "${endpoint}" does not exist.`);
263
- }
264
- }
265
- if (changes.source !== undefined || changes.target !== undefined)
266
- clearRoute(next);
267
- edges.set(op.id, next);
268
- break;
269
- }
270
- case "removeEdge": {
271
- if (!edges.delete(op.id))
272
- throw new errors_js_1.ToolInputError("DIAGRAM_EDGE_NOT_FOUND", `Edge "${op.id}" does not exist.`);
273
- removedEdgeIds.push(op.id);
274
- break;
275
- }
276
- }
81
+ const taken = [...doc.nodes.map((node) => node.id), ...doc.edges.map((edge) => edge.id)];
82
+ try {
83
+ const lowered = (0, callman_core_1.lowerPatchOps)(doc, ops, {
84
+ strictTypes: options.strictTypes !== false,
85
+ genNodeId: (0, callman_core_1.slugIdFactory)(taken, "n"),
86
+ genEdgeId: (0, callman_core_1.slugIdFactory)(taken, "e"),
87
+ });
88
+ // MCP keeps today's rule: a moved node loses the manual bends of every
89
+ // edge leaving it (the desktop keeps them — a person is watching there).
90
+ const applied = (0, callman_core_1.applyDiagramOps)(doc, lowered.ops, { moveClearsWaypoints: true, now: new Date().toISOString() });
91
+ return {
92
+ document: applied.doc,
93
+ addedNodeIds: lowered.unpositionedNodeIds,
94
+ removedNodeIds: applied.removed.nodes,
95
+ removedEdgeIds: applied.removed.edges,
96
+ reroutedEdgeIds: applied.rerouted,
97
+ warnings: [...lowered.warnings, ...applied.warnings],
98
+ ...(lowered.relayout ? { relayout: lowered.relayout } : {}),
99
+ idMap: lowered.idMap,
100
+ };
277
101
  }
278
- const reroutedEdgeIds = [];
279
- if (moved.size > 0) {
280
- // A moved container carries its children: their edges are stale too.
281
- const affected = new Set(moved);
282
- let grew = true;
283
- while (grew) {
284
- grew = false;
285
- for (const node of nodes.values()) {
286
- if (node.parentId && affected.has(node.parentId) && !affected.has(node.id)) {
287
- affected.add(node.id);
288
- grew = true;
289
- }
290
- }
291
- }
292
- for (const edge of edges.values()) {
293
- const touches = (edge.source && affected.has(edge.source)) || (edge.target && affected.has(edge.target));
294
- if (touches && (edge.waypoints || edge.sourceAnchor || edge.targetAnchor || edge.routing === "straight")) {
295
- clearRoute(edge);
296
- reroutedEdgeIds.push(edge.id);
297
- }
298
- }
102
+ catch (error) {
103
+ return (0, build_js_1.toToolError)(error);
299
104
  }
300
- const document = (0, callman_core_1.normalizeDiagramStyles)({
301
- ...doc,
302
- meta: { ...doc.meta, title, updatedAt: new Date().toISOString() },
303
- nodes: [...nodes.values()],
304
- edges: [...edges.values()],
305
- });
306
- const parsed = (0, callman_core_1.safeParseDiagramDocument)(document);
307
- if (!parsed.ok) {
308
- throw new errors_js_1.ToolInputError("DIAGRAM_DOCUMENT_INVALID", `The patched document is invalid: ${parsed.errors.join("; ")}`, { errors: parsed.errors });
309
- }
310
- return { document: parsed.document, addedNodeIds, removedNodeIds, removedEdgeIds, reroutedEdgeIds, warnings };
311
105
  };
312
106
  exports.applyDiagramPatch = applyDiagramPatch;
313
- // Drop a saved route so the studio re-routes the edge orthogonally.
314
- const clearRoute = (edge) => {
315
- delete edge.waypoints;
316
- delete edge.sourceAnchor;
317
- delete edge.targetAnchor;
318
- edge.routing = "orthogonal";
319
- };
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.styleView = exports.applyStyleInput = exports.fromTokenRef = exports.toTokenRef = exports.nodeStylePatchSchema = exports.nodeStyleInputSchema = exports.STYLE_KEYS = exports.colorInputSchema = void 0;
3
+ exports.applyStyleInput = exports.toTokenRef = exports.styleView = exports.fromTokenRef = exports.nodeStylePatchSchema = exports.nodeStyleInputSchema = exports.CORE_STYLE_KEYS = exports.STYLE_KEYS = exports.colorInputSchema = void 0;
4
+ const callman_core_1 = require("callman-core");
5
+ Object.defineProperty(exports, "fromTokenRef", { enumerable: true, get: function () { return callman_core_1.fromTokenRef; } });
6
+ Object.defineProperty(exports, "styleView", { enumerable: true, get: function () { return callman_core_1.styleView; } });
7
+ Object.defineProperty(exports, "toTokenRef", { enumerable: true, get: function () { return callman_core_1.toTokenRef; } });
4
8
  const zod_1 = require("zod");
5
9
  const strict_js_1 = require("../../lib/strict.js");
6
10
  // The visual style a model may set on a node — exactly the DiagramStyle
@@ -8,7 +12,9 @@ const strict_js_1 = require("../../lib/strict.js");
8
12
  // ("strokeColor", "borderColor", "fontWeight") is REJECTED with the list of
9
13
  // valid keys instead of being dropped by core's normalizer while the tool
10
14
  // reports success. Colors are plain CSS strings on the way in and on the way
11
- // out; the stored form is core's TokenRef (`{ raw: "#3B82F6" }`).
15
+ // out; the stored form is core's TokenRef (`{ raw: "#3B82F6" }`). The
16
+ // conversion itself lives in callman-core (styleInput.ts) — shared with the
17
+ // desktop's AI edits — so the two never disagree.
12
18
  exports.colorInputSchema = zod_1.z
13
19
  .union([
14
20
  zod_1.z.string().trim().min(1).max(64),
@@ -16,9 +22,6 @@ exports.colorInputSchema = zod_1.z
16
22
  zod_1.z.object({ token: zod_1.z.string().trim().min(1).max(64) }).strict(),
17
23
  ])
18
24
  .describe('CSS color: "#3B82F6", "rgb(59,130,246)", "rgba(59,130,246,0.08)" or a name like "tomato"');
19
- const FONT_BOLD = 1;
20
- const FONT_ITALIC = 2;
21
- const FONT_UNDERLINE = 4;
22
25
  const styleFields = {
23
26
  fill: exports.colorInputSchema.describe('Background CSS color, e.g. "#EFF6FF". A translucent "rgba(59,130,246,0.08)" tints a container without hiding its children'),
24
27
  stroke: exports.colorInputSchema.describe('Border CSS color, e.g. "#3B82F6"'),
@@ -39,92 +42,12 @@ const styleFields = {
39
42
  opacity: zod_1.z.number().min(0).max(100).describe("0 (invisible) – 100 (opaque)"),
40
43
  };
41
44
  exports.STYLE_KEYS = Object.keys(styleFields);
45
+ // The schema and core must agree on the key list; a drift is a test failure.
46
+ exports.CORE_STYLE_KEYS = callman_core_1.STYLE_INPUT_KEYS;
42
47
  exports.nodeStyleInputSchema = (0, strict_js_1.strictObject)(Object.fromEntries(Object.entries(styleFields).map(([key, schema]) => [key, schema.optional()])), "style key").describe(`Visual style. Keys: ${exports.STYLE_KEYS.join(", ")}. Colors are CSS strings.`);
43
48
  // On updateNode a key set to null removes it; `style: null` clears the style.
44
49
  exports.nodeStylePatchSchema = (0, strict_js_1.strictObject)(Object.fromEntries(Object.entries(styleFields).map(([key, schema]) => [key, schema.nullable().optional()])), "style key")
45
50
  .nullable()
46
51
  .describe(`Style changes, MERGED into the node's current style: only the keys you pass change, a key set to null is removed, style: null clears it. Keys: ${exports.STYLE_KEYS.join(", ")}.`);
47
- const toTokenRef = (color) => (typeof color === "string" ? { raw: color.trim() } : color);
48
- exports.toTokenRef = toTokenRef;
49
- const fromTokenRef = (ref) => {
50
- if (!ref)
51
- return undefined;
52
- return "raw" in ref ? ref.raw : { token: ref.token };
53
- };
54
- exports.fromTokenRef = fromTokenRef;
55
- // Input (plain colors, bold/italic/underline flags) → stored DiagramStyle.
56
- // `base` is the node's current style for a merge; null values delete.
57
- const applyStyleInput = (base, input) => {
58
- const out = { ...(base ?? {}) };
59
- const assign = (key, value) => {
60
- if (value === undefined)
61
- return;
62
- if (value === null)
63
- delete out[key];
64
- else
65
- out[key] = value;
66
- };
67
- for (const key of ["fill", "stroke", "textColor"]) {
68
- const value = input[key];
69
- assign(key, value === undefined || value === null ? value : (0, exports.toTokenRef)(value));
70
- }
71
- assign("strokeWidth", input.strokeWidth);
72
- assign("dashed", input.dashed);
73
- assign("dashPattern", input.dashPattern);
74
- assign("radius", input.radius);
75
- assign("rounded", input.rounded);
76
- assign("fontSize", input.fontSize);
77
- assign("fontFamily", input.fontFamily);
78
- assign("align", input.align);
79
- assign("verticalAlign", input.verticalAlign);
80
- assign("shadow", input.shadow);
81
- assign("opacity", input.opacity);
82
- // The studio stores bold/italic/underline as ONE maxGraph bitmask.
83
- const flags = [
84
- [input.bold, FONT_BOLD],
85
- [input.italic, FONT_ITALIC],
86
- [input.underline, FONT_UNDERLINE],
87
- ];
88
- if (flags.some(([value]) => value !== undefined)) {
89
- let mask = out.fontStyle ?? 0;
90
- for (const [value, bit] of flags) {
91
- if (value === true)
92
- mask |= bit;
93
- else if (value === false || value === null)
94
- mask &= ~bit;
95
- }
96
- if (mask === 0)
97
- delete out.fontStyle;
98
- else
99
- out.fontStyle = mask;
100
- }
101
- // fontWeight is not rendered by the studio (bold lives in fontStyle).
102
- delete out.fontWeight;
103
- return Object.keys(out).length > 0 ? out : undefined;
104
- };
52
+ const applyStyleInput = (base, input) => (0, callman_core_1.applyStyleInput)(base, input);
105
53
  exports.applyStyleInput = applyStyleInput;
106
- // Stored DiagramStyle → the same compact shape a model writes, so what
107
- // get_diagram returns can be sent straight back in a patch.
108
- const styleView = (style) => {
109
- if (!style)
110
- return undefined;
111
- const out = {};
112
- for (const key of ["fill", "stroke", "textColor"]) {
113
- const value = (0, exports.fromTokenRef)(style[key]);
114
- if (value !== undefined)
115
- out[key] = value;
116
- }
117
- for (const key of ["strokeWidth", "dashed", "dashPattern", "radius", "rounded", "fontSize", "fontFamily", "align", "verticalAlign", "shadow", "opacity"]) {
118
- if (style[key] !== undefined)
119
- out[key] = style[key];
120
- }
121
- const mask = style.fontStyle ?? 0;
122
- if (mask & FONT_BOLD)
123
- out.bold = true;
124
- if (mask & FONT_ITALIC)
125
- out.italic = true;
126
- if (mask & FONT_UNDERLINE)
127
- out.underline = true;
128
- return Object.keys(out).length > 0 ? out : undefined;
129
- };
130
- exports.styleView = styleView;
@@ -1,14 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.diagramFullView = exports.diagramGraphView = exports.diagramListView = void 0;
3
+ exports.diagramFullView = exports.diagramSourceView = exports.diagramMermaidView = exports.diagramGraphView = exports.diagramListView = void 0;
4
4
  const callman_core_1 = require("callman-core");
5
5
  const truncate_js_1 = require("../../lib/truncate.js");
6
- const style_js_1 = require("./style.js");
7
6
  // Compact views of a stored diagram: the list row, the graph (what is
8
- // connected to what, with the edge meaning read back from its style) and
7
+ // connected to what, with the edge meaning read back from its style), the
8
+ // Mermaid rendering, the Mermaid source a source-backed diagram keeps, and
9
9
  // the full document behind a size gate.
10
10
  const diagramListView = (diagram) => {
11
11
  const parsed = (0, callman_core_1.safeParseDiagramDocument)(diagram.document);
12
+ const doc = parsed.ok ? parsed.document : null;
12
13
  return {
13
14
  id: diagram.id,
14
15
  name: diagram.name,
@@ -16,43 +17,29 @@ const diagramListView = (diagram) => {
16
17
  owner: diagram.owner?.email ?? diagram.owner?.displayName ?? null,
17
18
  sharedWorkspaceIds: diagram.sharedWorkspaceIds ?? [],
18
19
  editorCount: (diagram.editorIds ?? []).length,
19
- nodeCount: parsed.ok ? parsed.document.nodes.length : null,
20
- edgeCount: parsed.ok ? parsed.document.edges.length : null,
20
+ nodeCount: doc ? doc.nodes.length : null,
21
+ edgeCount: doc ? doc.edges.length : null,
22
+ ...(doc?.source ? { source: { format: doc.source.format, diagramType: doc.source.diagramType, valid: doc.source.valid !== false } } : {}),
23
+ ...(typeof doc?.meta.revision === "number" ? { revision: doc.meta.revision } : {}),
21
24
  updatedAt: diagram.updatedAt ?? null,
22
25
  };
23
26
  };
24
27
  exports.diagramListView = diagramListView;
25
- // Style and flags come back in the same compact shape the tools accept, so a
26
- // model can read a node's look and send a patch without the full document.
27
- const diagramGraphView = (doc) => ({
28
- title: doc.meta.title,
29
- nodes: doc.nodes.map((node) => {
30
- const style = (0, style_js_1.styleView)(node.style);
31
- return {
32
- id: node.id,
33
- type: node.type,
34
- label: node.label ?? null,
35
- ...(node.parentId ? { parent: node.parentId } : {}),
36
- ...(style ? { style } : {}),
37
- ...(node.rotation ? { rotation: node.rotation } : {}),
38
- ...(node.locked ? { locked: true } : {}),
39
- ...(node.hidden ? { hidden: true } : {}),
40
- };
41
- }),
42
- edges: doc.edges.map((edge) => {
43
- const color = (0, style_js_1.fromTokenRef)(edge.style?.stroke);
44
- return {
45
- id: edge.id,
46
- source: edge.source ?? null,
47
- target: edge.target ?? null,
48
- label: edge.label ?? null,
49
- semantic: (0, callman_core_1.semanticFromStyle)(edge.style),
50
- ...(color ? { color } : {}),
51
- ...(edge.markerStart ? { markerStart: edge.markerStart } : {}),
52
- };
53
- }),
54
- });
28
+ // Style, flags, sides and routing come back in the same compact shape the
29
+ // tools accept, so a model can read a node's look and send a patch without
30
+ // the full document. The view itself is core's (shared with the AI prompt).
31
+ const diagramGraphView = (doc) => (0, callman_core_1.diagramGraphView)(doc);
55
32
  exports.diagramGraphView = diagramGraphView;
33
+ // Mermaid flowchart text rendered from the model (presentation is not part
34
+ // of it — Mermaid lays out by itself).
35
+ const diagramMermaidView = (doc) => ({ format: "mermaid", text: (0, callman_core_1.exportMermaid)(doc) });
36
+ exports.diagramMermaidView = diagramMermaidView;
37
+ // The Mermaid source a source-backed diagram keeps (structure truth), or the
38
+ // rendering when the diagram has none.
39
+ const diagramSourceView = (doc) => doc.source
40
+ ? { format: doc.source.format, diagramType: doc.source.diagramType, valid: doc.source.valid !== false, text: doc.source.text, stored: true }
41
+ : { ...(0, exports.diagramMermaidView)(doc), diagramType: "flowchart", valid: true, stored: false, note: "This diagram keeps no Mermaid source; the text is rendered from its model." };
42
+ exports.diagramSourceView = diagramSourceView;
56
43
  const diagramFullView = (doc) => {
57
44
  const size = (0, truncate_js_1.byteLength)(doc);
58
45
  if (size <= truncate_js_1.LIMITS.DIAGRAM_FULL)