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.
- package/CHANGELOG.md +28 -0
- package/README.md +2 -2
- package/dist/client/api.d.ts +1 -0
- package/dist/client/api.d.ts.map +1 -1
- package/dist/client/api.js +2 -0
- package/dist/client/api.js.map +1 -1
- package/dist/domain/diagram/build.d.ts +112 -63
- package/dist/domain/diagram/build.d.ts.map +1 -1
- package/dist/domain/diagram/build.js +49 -165
- package/dist/domain/diagram/build.js.map +1 -1
- package/dist/domain/diagram/layout.d.ts +3 -4
- package/dist/domain/diagram/layout.d.ts.map +1 -1
- package/dist/domain/diagram/layout.js +12 -45
- package/dist/domain/diagram/layout.js.map +1 -1
- package/dist/domain/diagram/patch.d.ts +298 -80
- package/dist/domain/diagram/patch.d.ts.map +1 -1
- package/dist/domain/diagram/patch.js +60 -273
- package/dist/domain/diagram/patch.js.map +1 -1
- package/dist/domain/diagram/style.d.ts +4 -12
- package/dist/domain/diagram/style.d.ts.map +1 -1
- package/dist/domain/diagram/style.js +8 -84
- package/dist/domain/diagram/style.js.map +1 -1
- package/dist/domain/diagram/summarize.d.ts +25 -24
- package/dist/domain/diagram/summarize.d.ts.map +1 -1
- package/dist/domain/diagram/summarize.js +20 -35
- package/dist/domain/diagram/summarize.js.map +1 -1
- package/dist/domain/requestIntegrations.d.ts +20 -20
- package/dist/domain/requestShape.d.ts +32 -32
- package/dist/resources/docs/diagramSchema.d.ts +1 -1
- package/dist/resources/docs/diagramSchema.d.ts.map +1 -1
- package/dist/resources/docs/diagramSchema.js +55 -10
- package/dist/resources/docs/diagramSchema.js.map +1 -1
- package/dist/resources/generated/aiPacks.d.ts +1 -1
- package/dist/resources/generated/aiPacks.js +2 -2
- package/dist/tools/diagrams.d.ts.map +1 -1
- package/dist/tools/diagrams.js +244 -42
- package/dist/tools/diagrams.js.map +1 -1
- package/dist-cjs/client/api.js +2 -0
- package/dist-cjs/domain/diagram/build.js +53 -169
- package/dist-cjs/domain/diagram/layout.js +11 -44
- package/dist-cjs/domain/diagram/patch.js +57 -270
- package/dist-cjs/domain/diagram/style.js +11 -88
- package/dist-cjs/domain/diagram/summarize.js +22 -35
- package/dist-cjs/resources/docs/diagramSchema.js +55 -10
- package/dist-cjs/resources/generated/aiPacks.js +2 -2
- package/dist-cjs/tools/diagrams.js +240 -38
- package/package.json +2 -2
|
@@ -92,39 +92,84 @@ asks for a specific arrangement. Keep diagrams readable: 8–25 nodes, short lab
|
|
|
92
92
|
per relationship.
|
|
93
93
|
|
|
94
94
|
## Editing: patch_diagram ops
|
|
95
|
+
Ops describe CHANGES; the rest of the diagram stays exactly as it is. The op list below is the
|
|
96
|
+
same vocabulary the desktop's AI assistant uses — one implementation (callman-core) applies both.
|
|
95
97
|
\`\`\`jsonc
|
|
96
98
|
{ "op": "addNode", "node": { "type": "db.redis", "label": "cache", "parent": "k8s" } }
|
|
97
99
|
{ "op": "updateNode", "id": "orders", "changes": { "label": "Orders API", "style": { "fill": "#EFF6FF" } } }
|
|
98
100
|
{ "op": "updateNode", "id": "orders", "changes": { "style": { "fill": null } } } // remove one style key
|
|
99
101
|
{ "op": "updateNode", "id": "orders", "changes": { "style": null } } // clear the style
|
|
100
102
|
{ "op": "removeNode", "id": "bus" } // its edges are removed, its children move to the top level
|
|
101
|
-
{ "op": "addEdge", "edge": { "source": "orders", "target": "
|
|
103
|
+
{ "op": "addEdge", "edge": { "source": "orders", "target": "cache", "semantic": "async", "color": "#F59E0B" } }
|
|
102
104
|
{ "op": "updateEdge", "id": "e2", "changes": { "label": "gRPC", "semantic": "fallback" } } // color is kept
|
|
103
105
|
{ "op": "updateEdge", "id": "e2", "changes": { "color": null } } // back to the default color
|
|
104
106
|
{ "op": "removeEdge", "id": "e3" }
|
|
105
107
|
{ "op": "setTitle", "title": "New title" }
|
|
108
|
+
{ "op": "moveNode", "id": "orders", "position": { "x": 320, "y": 80 } }
|
|
109
|
+
{ "op": "reparent", "id": "orders", "parent": "k8s" } // null = top level
|
|
110
|
+
{ "op": "setEdgeRoute", "id": "e2", "routing": "smart", "sourceSide": "right", "targetSide": "left" }
|
|
111
|
+
{ "op": "reorder", "id": "k8s", "index": "back" }
|
|
112
|
+
{ "op": "setCanvas", "canvas": { "background": "grid", "defaultRouting": "orthogonal" } }
|
|
113
|
+
{ "op": "group", "ids": ["orders", "db"], "label": "Order domain" } // a new frame around them
|
|
114
|
+
{ "op": "ungroup", "id": "k8s" } // members stay where they are
|
|
115
|
+
{ "op": "relayout", "scope": "new" } // hint: place only the new nodes (or "all")
|
|
106
116
|
\`\`\`
|
|
107
117
|
- updateNode \`changes\` accepts: type, label, parent (null/"" = top level), position, size,
|
|
108
|
-
style, rotation, locked, hidden, data. \`style\` and \`data\` MERGE into what the
|
|
109
|
-
a key set to null is removed.
|
|
118
|
+
style, rotation, locked, hidden, collapsed, data. \`style\` and \`data\` MERGE into what the
|
|
119
|
+
node has; a key set to null is removed.
|
|
110
120
|
- updateEdge \`changes\` accepts: source, target, label ("" removes it), semantic, color,
|
|
111
|
-
routing, markerEnd, markerStart.
|
|
121
|
+
routing, markerEnd, markerStart, sourceSide, targetSide (null = floating).
|
|
122
|
+
- Ids: a node written without an id gets a slug of its label (\`orders-service\`, \`-2\` on a
|
|
123
|
+
clash); the result's \`idMap\` reports any id that had to change.
|
|
112
124
|
- Moving a node (position, size or parent) or re-pointing an edge drops the saved route of
|
|
113
125
|
the edges involved; the studio re-routes them (\`reroutedEdges\` in the result).
|
|
114
126
|
- \`relayout\`: \`new\` (default — only new nodes get a place), \`all\` (re-run layout), \`none\`.
|
|
115
127
|
- \`get_diagram\` (view \`graph\`) returns nodes and edges in this same shape — ids, parents,
|
|
116
|
-
styles, colors — so read it before a patch.
|
|
128
|
+
styles, colors, routing, sides — so read it before a patch.
|
|
129
|
+
- If someone saved the diagram between your read and your patch, the ops are re-applied on
|
|
130
|
+
the newer version automatically (they describe intent, not a whole document).
|
|
131
|
+
|
|
132
|
+
## Mermaid
|
|
133
|
+
- \`get_diagram\` view \`mermaid\` renders any diagram as a Mermaid flowchart (\`id@{ shape, label }\`
|
|
134
|
+
nodes, \`subgraph\` containers, \`e1@-->\` edge ids, \`-->\`/\`-.->\`/\`==>\` for sync/async/bus).
|
|
135
|
+
- \`create_diagram\` / \`update_diagram\` accept \`source: { format: "mermaid", text }\` instead of
|
|
136
|
+
nodes: a flowchart/graph becomes nodes and edges (laid out automatically) and the text stays
|
|
137
|
+
attached as the diagram's source — later ops keep it in sync (the desktop shows both).
|
|
138
|
+
Bracket shapes map to component types (\`[( )]\` → database, \`{ }\` → decision, \`([ ])\` →
|
|
139
|
+
terminator …); \`click\`, \`%%\` comments and unknown statements are kept untouched.
|
|
140
|
+
- Any other Mermaid type (sequenceDiagram, classDiagram, erDiagram …) is stored as source only:
|
|
141
|
+
\`get_diagram\` view \`source\` returns it, the desktop previews it, and structural ops are
|
|
142
|
+
refused with \`DIAGRAM_SOURCE_UNSUPPORTED_TYPE\`.
|
|
143
|
+
|
|
144
|
+
## BPMN
|
|
145
|
+
- A BPMN diagram keeps its truth in BPMN 2.0 XML: \`create_diagram\` with \`kind: "bpmn"\` (or nodes
|
|
146
|
+
typed bpmn.*) writes the XML from your nodes and edges; \`source: { format: "bpmn", text }\` stores a
|
|
147
|
+
file you already have (from Camunda Modeler, for example). The desktop opens it in its BPMN editor.
|
|
148
|
+
- Node \`type\` is one of the bpmn.* types listed under DIAGRAM OPS above (bpmn.start-event,
|
|
149
|
+
bpmn.user-task, bpmn.service-task, bpmn.exclusive-gateway, bpmn.parallel-gateway, bpmn.subprocess,
|
|
150
|
+
bpmn.pool, bpmn.lane, bpmn.boundary-event …). \`data.bpmn\` carries the BPMN facts:
|
|
151
|
+
\`eventDefinition\` (message | timer | error | signal | …), \`attachedTo\` (boundary events),
|
|
152
|
+
\`cancelActivity\`, \`isExpanded\`. Edges are sequence flows; \`data.bpmn\` may set
|
|
153
|
+
\`flow: "message"\` (between pools), \`conditionExpression\` and \`isDefault\`.
|
|
154
|
+
- Pools and lanes are containers (\`parent\`); a boundary event's parent is its host's parent.
|
|
155
|
+
New nodes need no position: they are placed after their predecessor.
|
|
156
|
+
- \`patch_diagram\` works as on any diagram: every op lands on the XML as a minimal edit — extension
|
|
157
|
+
elements, annotations and the modeler's formatting stay untouched. \`get_diagram\` view \`source\`
|
|
158
|
+
returns the XML; view \`graph\` shows the derived nodes/edges with their \`bpmn\` facts.
|
|
117
159
|
|
|
118
160
|
## Stored document (get_diagram view: "full")
|
|
119
161
|
\`\`\`ts
|
|
120
162
|
interface DiagramDocument {
|
|
121
163
|
schemaVersion: 1; id: string;
|
|
122
|
-
meta: { title: string; createdAt: string; updatedAt: string };
|
|
123
|
-
canvas?: { background?: "grid" | "dots" | "blank"; gridSize?: number
|
|
164
|
+
meta: { title: string; createdAt: string; updatedAt: string; revision?: number };
|
|
165
|
+
canvas?: { background?: "grid" | "dots" | "blank"; gridSize?: number;
|
|
166
|
+
defaultRouting?: "orthogonal" | "straight" | "curved" | "smart";
|
|
167
|
+
layout?: { algorithm; direction }; viewport?: { x; y; zoom } };
|
|
124
168
|
nodes: { id; type; position: {x,y}; size: {width,height}; label?; parentId?; rotation?;
|
|
125
|
-
locked?; hidden?; style?: DiagramStyle; data? }[];
|
|
126
|
-
edges: { id; source?; target?; label?; routing?;
|
|
127
|
-
|
|
169
|
+
locked?; hidden?; collapsed?; collapsedSize?; style?: DiagramStyle; data? }[];
|
|
170
|
+
edges: { id; source?; target?; label?; labelPosition?; routing?; sourceHandle?; targetHandle?;
|
|
171
|
+
style?: DiagramStyle; markerStart?; markerEnd?; waypoints? }[];
|
|
172
|
+
source?: { format: "mermaid"; text; diagramType; grammar?; valid? };
|
|
128
173
|
}
|
|
129
174
|
// Stored colors are { raw: "#3B82F6" } (or a theme { token }); bold/italic/underline are
|
|
130
175
|
// the bitmask fontStyle (1 | 2 | 4). The tools convert both ways — write plain colors.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AI_PACKS = exports.AI_PACKS_CORE_VERSION = void 0;
|
|
4
|
-
// GENERATED by scripts/build-static-resources.mjs from callman-core@1.
|
|
5
|
-
exports.AI_PACKS_CORE_VERSION = "1.
|
|
4
|
+
// GENERATED by scripts/build-static-resources.mjs from callman-core@1.29.0 ai-packs — do not edit.
|
|
5
|
+
exports.AI_PACKS_CORE_VERSION = "1.29.0";
|
|
6
6
|
exports.AI_PACKS = {
|
|
7
7
|
"db-queries": "<!-- GENERATED FILE — do not edit by hand. Source: callman_core (npm run ai:packs). -->\n\n# Callman data access from scripts (`pm.db.query` / `pm.redis.command`)\n\n`pm.db.query({ connectionId, query })` — ALWAYS `await`. `query` is coerced to a string, `{{tokens}}` are resolved, then dispatched by the connection's type. `connectionId` may be omitted to use the request's configured DB connection. IMPORTANT: `pm.db.query` does NOT accept a `params`/bind array — inline values (quote strings) or use `{{env.X}}` tokens; SQL placeholders like `$1` or `?` are NOT bound.\n\n## MongoDB\n`query` MUST be a JSON STRING of a command object (use `JSON.stringify`), NOT a `db.coll.find(...)` shell string. The match key is `filter` (a top-level `query` key is rejected).\n\n```js\nconst body = pm.response.json();\nconst res = await pm.db.query({\n connectionId: \"…\",\n query: JSON.stringify({\n collection: \"users\",\n operation: \"findOne\", // find | findOne | aggregate | countDocuments | insertOne | insertMany | updateOne | updateMany | deleteOne | deleteMany | findOneAndUpdate | findOneAndReplace | findOneAndDelete\n filter: { email: body.data.user.email },\n }),\n});\npm.expect(res.rowCount).to.be.above(0); // existence check\n```\n\nPer-operation fields: `find`/`findOne` use `filter` (+ optional `projection`,`sort`,`limit`,`skip`); `aggregate` uses `pipeline` (array); `insertOne` uses `document`, `insertMany` uses `documents`; `updateOne`/`updateMany`/`findOneAndUpdate` use `filter`+`update` (Mongo update operators, e.g. `{ $set: { … } }`); `deleteOne`/`deleteMany` use `filter`; `findOneAndUpdate`/`findOneAndReplace` accept `returnDocument: \"before\"|\"after\"`. A 24-hex string in a filter is auto-coerced to ObjectId (or use `{ \"$oid\": \"…\" }`). Rows are in `res.rows`; write ops surface counts in `res.rowCount`.\n\n## PostgreSQL / MySQL / Oracle (SQL)\n`query` is plain SQL text. Because `pm.db.query` binds no params, inline the value and quote strings yourself (or use a `{{token}}`):\n\n```js\nconst email = pm.response.json().data.user.email;\nconst res = await pm.db.query({\n connectionId: \"…\",\n query: `SELECT id FROM users WHERE email = '${email}'`,\n});\npm.expect(res.rowCount).to.be.above(0);\n```\n\nNotes: a trailing `;` is stripped (except Oracle PL/SQL blocks, which keep it). **Oracle** returns column names UPPERCASE — read `res.rows[0].EMAIL`, not `.email`. `res.rowCount` is the row count for SELECT and the affected-row count for INSERT/UPDATE/DELETE.\n\n## SQL Server (mssql)\nSingle statement per query; a trailing `;` is stripped; `GO` batch separators are REJECTED (client-tool syntax, not T-SQL). Positional params bind as named inputs: `params[0]` → `@p1`, `params[1]` → `@p2`… (scenario db node only — `pm.db.query` has no params).\n\n```sql\nSELECT TOP 1 id, amount FROM dbo.orders WHERE customer_id = @p1 ORDER BY created_at DESC\n```\n\n## Redis (`pm.redis.command`)\n`command` is a single string. Supported commands: **GET, SET, DEL, EXISTS, TTL, HGET, HSET, HGETALL, HDEL, LPUSH, RPUSH, LRANGE, LLEN, SADD, SMEMBERS, SISMEMBER, INCR, DECR, INCRBY, EXPIRE, PERSIST, TYPE, ZADD, ZRANGE, ZSCORE, SCAN**. Quote a value that contains spaces.\n\nStructured replies land in `res.result` as real values: `HGETALL` → object, `LRANGE`/`SMEMBERS`/`ZRANGE` → array, `EXISTS`/`SISMEMBER`/`EXPIRE`/`PERSIST` → boolean; `res.resultText` is the stringified form. `SCAN pattern [count]` is Callman-managed: the cursor loop runs internally (capped at 1000 keys / 100 iterations) and returns `{ keys, count, truncated }` — no raw cursors.\n\n```js\nconst res = await pm.redis.command({ connectionId: \"…\", command: `GET session:${id}` });\npm.expect(res.success).to.be.true;\n// res.result / res.resultText hold the value; res.commandName, res.key are set.\n```\n",
|
|
8
8
|
"diagram-components": "<!-- GENERATED FILE — do not edit by hand. Source: callman_core (npm run ai:packs). -->\n\n# Diagram components and edge notation\n\nA diagram node's `type` MUST be one of the keys below (`callman-core` `DIAGRAM_COMPONENT_CATALOG`). Sizes are the defaults the studio uses; `container` types hold children via `parentId`.\n\n## Edge notation\n\n| Semantic | Meaning | Style tuple |\n| --- | --- | --- |\n| `sync` | Request / response call | dashed=false, strokeWidth=1.5 |\n| `async` | Fire-and-forget, event or callback | dashed=true, dashPattern=\"6 4\", strokeWidth=1.5 |\n| `fallback` | Retry, fallback or optional path | dashed=true, dashPattern=\"1 3\", strokeWidth=1.25 |\n| `bus` | Message bus or stream | dashed=false, strokeWidth=3 |\n\n## Generic (`generic`)\n\n| Type | Label | Default size | Aliases |\n| --- | --- | --- | --- |\n| `line` | Line | 160×0 | connector, edge, link |\n| `arrow` | Arrow | 160×0 | connector, directed, link |\n| `line.dashed` | Dashed Line | 160×0 | dotted, connector |\n| `rect` | Rectangle | 140×64 | box, card |\n| `square` | Square | 90×90 | |\n| `ellipse` | Ellipse | 140×64 | oval |\n| `circle` | Circle | 90×90 | |\n| `diamond` | Diamond | 140×64 | rhombus |\n| `triangle` | Triangle | 140×64 | |\n| `hexagon` | Hexagon | 140×64 | |\n| `pentagon` | Pentagon | 140×64 | |\n| `octagon` | Octagon | 140×64 | |\n| `parallelogram` | Parallelogram | 140×64 | |\n| `trapezoid` | Trapezoid | 140×64 | |\n| `cylinder` | Cylinder | 140×64 | can, tube |\n| `cloud-shape` | Cloud | 140×64 | |\n| `callout` | Callout | 140×64 | speech, bubble |\n| `text` | Text | 120×36 | |\n| `sticky` | Sticky note | 140×100 | postit, comment |\n| `card` | Card | 140×64 | |\n| `frame` | Frame / Container (container) | 220×160 | group, container, boundary |\n| `pool.horizontal` | Pool (horizontal) (swimlane) | 360×220 | swimlane, lane, container, subsystem |\n| `pool.vertical` | Pool (vertical) (swimlane) | 220×360 | swimlane, lane, container, subsystem |\n| `image` | Image | 140×64 | picture |\n\n## Flowchart (`flowchart`)\n\n| Type | Label | Default size | Aliases |\n| --- | --- | --- | --- |\n| `flow.process` | Process | 140×64 | step, action |\n| `flow.alt-process` | Alternate Process | 140×64 | |\n| `flow.decision` | Decision | 140×100 | if, branch |\n| `flow.terminator` | Start / End | 130×56 | terminator, begin, stop |\n| `flow.data` | Data (I/O) | 140×64 | input, output, io |\n| `flow.document` | Document | 140×64 | |\n| `flow.multi-document` | Multi-Document | 140×64 | |\n| `flow.predefined` | Predefined Process | 140×64 | subroutine |\n| `flow.manual-input` | Manual Input | 140×64 | |\n| `flow.manual-op` | Manual Operation | 140×64 | |\n| `flow.preparation` | Preparation | 140×64 | |\n| `flow.stored-data` | Stored Data | 140×64 | |\n| `flow.database` | Database | 140×64 | db, store |\n| `flow.display` | Display | 140×64 | |\n| `flow.delay` | Delay | 140×64 | |\n| `flow.merge` | Merge | 140×64 | |\n| `flow.or` | Or | 140×64 | |\n| `flow.connector` | On-page Connector | 48×48 | |\n| `flow.off-page` | Off-page Connector | 140×64 | |\n| `flow.annotation` | Annotation | 140×64 | |\n\n## UML (`uml`)\n\n| Type | Label | Default size | Aliases |\n| --- | --- | --- | --- |\n| `uml.class` | Class | 140×64 | entity |\n| `uml.abstract` | Abstract Class | 140×64 | |\n| `uml.interface` | Interface | 140×64 | |\n| `uml.enum` | Enum | 140×64 | |\n| `uml.object` | Object | 140×64 | |\n| `uml.package` | Package | 140×64 | |\n| `uml.component` | Component | 140×64 | |\n| `uml.node` | Node / Deployment | 140×64 | |\n| `uml.actor` | Actor | 60×100 | user, role |\n| `uml.usecase` | Use Case | 140×64 | |\n| `uml.boundary` | Boundary | 140×64 | |\n| `uml.control` | Control | 140×64 | |\n| `uml.entity` | Entity | 140×64 | |\n| `uml.state` | State | 140×64 | |\n| `uml.initial` | Initial State | 36×36 | |\n| `uml.final` | Final State | 40×40 | |\n| `uml.decision` | Decision | 60×60 | |\n| `uml.note` | Note / Comment | 140×64 | comment |\n\n## ERD (`erd`)\n\n| Type | Label | Default size | Aliases |\n| --- | --- | --- | --- |\n| `erd.entity` | Entity | 140×64 | table |\n| `erd.weak-entity` | Weak Entity | 140×64 | |\n| `erd.attribute` | Attribute | 140×64 | |\n| `erd.key-attribute` | Key Attribute | 140×64 | pk, primary key |\n| `erd.multivalued` | Multivalued Attribute | 140×64 | |\n| `erd.derived` | Derived Attribute | 140×64 | |\n| `erd.relationship` | Relationship | 140×64 | |\n| `erd.identifying` | Identifying Relationship | 140×64 | |\n\n## Architecture (`networking`)\n\n| Type | Label | Default size | Aliases |\n| --- | --- | --- | --- |\n| `net.user` | User / Client | 60×100 | client, person |\n| `net.admin` | Admin | 60×100 | |\n| `net.browser` | Browser | 140×64 | |\n| `net.mobile` | Mobile Device | 90×130 | phone |\n| `net.desktop` | Desktop | 140×64 | |\n| `net.iot` | IoT Device | 140×64 | |\n| `net.server` | Server | 140×64 | |\n| `net.service` | Service | 140×64 | microservice |\n| `net.monolith` | Monolith | 140×64 | |\n| `net.api-gateway` | API Gateway | 140×64 | api, gateway |\n| `net.load-balancer` | Load Balancer | 140×64 | lb |\n| `net.reverse-proxy` | Reverse Proxy | 140×64 | proxy |\n| `net.firewall` | Firewall | 140×64 | waf |\n| `net.router` | Router | 140×64 | |\n| `net.switch` | Switch | 140×64 | |\n| `net.dns` | DNS | 140×64 | |\n| `net.cdn` | CDN | 140×64 | edge |\n| `net.cache` | Cache | 140×64 | |\n| `net.queue` | Queue | 140×64 | mq |\n| `net.websocket` | Web Socket | 140×64 | ws |\n| `net.rest` | REST endpoint | 140×64 | http, api |\n| `net.internet` | Internet / Cloud | 140×64 | wan |\n| `net.datacenter` | Datacenter | 140×64 | |\n| `net.container` | Container | 140×64 | |\n| `net.pod` | Pod | 140×64 | |\n| `net.vm` | Virtual Machine | 140×64 | vm |\n\n## Databases (`database`)\n\n| Type | Label | Default size | Aliases |\n| --- | --- | --- | --- |\n| `db.postgres` | PostgreSQL | 176×56 | postgres, pg |\n| `db.mysql` | MySQL | 176×56 | |\n| `db.mariadb` | MariaDB | 176×56 | |\n| `db.mongodb` | MongoDB | 176×56 | mongo |\n| `db.redis` | Redis | 176×56 | |\n| `db.sqlite` | SQLite | 176×56 | |\n| `db.mssql` | MS SQL Server | 176×56 | sqlserver, mssql |\n| `db.oracle` | Oracle | 176×56 | |\n| `db.cassandra` | Cassandra | 176×56 | |\n| `db.elasticsearch` | Elasticsearch | 176×56 | elastic, es |\n| `db.neo4j` | Neo4j | 176×56 | graph |\n| `db.couchdb` | CouchDB | 176×56 | couch |\n| `db.couchbase` | Couchbase | 176×56 | |\n| `db.clickhouse` | ClickHouse | 176×56 | |\n| `db.cockroachdb` | CockroachDB | 176×56 | cockroach |\n| `db.influxdb` | InfluxDB | 176×56 | influx, tsdb |\n| `db.snowflake` | Snowflake | 176×56 | |\n| `db.supabase` | Supabase | 176×56 | |\n\n## Messaging (`messaging`)\n\n| Type | Label | Default size | Aliases |\n| --- | --- | --- | --- |\n| `mq.kafka` | Apache Kafka | 176×56 | kafka |\n| `mq.rabbitmq` | RabbitMQ | 176×56 | rabbit, amqp |\n| `mq.nats` | NATS | 176×56 | |\n| `mq.pulsar` | Apache Pulsar | 176×56 | pulsar |\n| `mq.mqtt` | MQTT | 176×56 | |\n| `mq.pubsub` | Pub/Sub | 176×56 | pubsub |\n| `mq.activemq` | ActiveMQ | 176×56 | jms |\n\n## Languages (`language`)\n\n| Type | Label | Default size | Aliases |\n| --- | --- | --- | --- |\n| `lang.node` | Node.js | 176×56 | node, js runtime |\n| `lang.python` | Python | 176×56 | py |\n| `lang.java` | Java | 176×56 | jvm |\n| `lang.go` | Go | 176×56 | golang |\n| `lang.rust` | Rust | 176×56 | |\n| `lang.dotnet` | C# / .NET | 176×56 | csharp, c# |\n| `lang.typescript` | TypeScript | 176×56 | ts |\n| `lang.javascript` | JavaScript | 176×56 | js |\n| `lang.ruby` | Ruby | 176×56 | |\n| `lang.php` | PHP | 176×56 | |\n| `lang.kotlin` | Kotlin | 176×56 | |\n| `lang.swift` | Swift | 176×56 | |\n| `lang.scala` | Scala | 176×56 | |\n| `lang.elixir` | Elixir | 176×56 | |\n| `lang.cpp` | C++ | 176×56 | cpp |\n\n## Frameworks (`framework`)\n\n| Type | Label | Default size | Aliases |\n| --- | --- | --- | --- |\n| `fw.express` | Express | 176×56 | |\n| `fw.nestjs` | NestJS | 176×56 | nest |\n| `fw.spring` | Spring Boot | 176×56 | spring |\n| `fw.django` | Django | 176×56 | |\n| `fw.flask` | Flask | 176×56 | |\n| `fw.fastapi` | FastAPI | 176×56 | |\n| `fw.rails` | Rails | 176×56 | ruby on rails |\n| `fw.laravel` | Laravel | 176×56 | |\n| `fw.dotnet` | .NET | 176×56 | |\n| `fw.react` | React | 176×56 | |\n| `fw.vue` | Vue | 176×56 | vuejs |\n| `fw.angular` | Angular | 176×56 | |\n| `fw.next` | Next.js | 176×56 | nextjs |\n| `fw.nuxt` | Nuxt | 176×56 | nuxtjs |\n| `fw.svelte` | Svelte | 176×56 | sveltekit |\n| `fw.electron` | Electron | 176×56 | |\n| `fw.tailwind` | Tailwind | 176×56 | tailwindcss |\n| `fw.graphql` | GraphQL | 176×56 | |\n\n## DevOps / CI-CD (`devops`)\n\n| Type | Label | Default size | Aliases |\n| --- | --- | --- | --- |\n| `ops.docker` | Docker | 176×56 | |\n| `ops.k8s` | Kubernetes | 176×56 | k8s |\n| `ops.nginx` | Nginx | 176×56 | |\n| `ops.haproxy` | HAProxy | 176×56 | lb |\n| `ops.traefik` | Traefik | 176×56 | traefik |\n| `ops.terraform` | Terraform | 176×56 | iac |\n| `ops.ansible` | Ansible | 176×56 | |\n| `ops.jenkins` | Jenkins | 176×56 | ci |\n| `ops.gha` | GitHub Actions | 176×56 | ci, actions |\n| `ops.gitlab` | GitLab CI | 176×56 | ci |\n| `ops.argocd` | ArgoCD | 176×56 | gitops |\n| `ops.helm` | Helm | 176×56 | |\n| `ops.istio` | Istio | 176×56 | service mesh |\n| `ops.envoy` | Envoy | 176×56 | proxy |\n| `ops.consul` | Consul | 176×56 | |\n| `ops.vault` | Vault | 176×56 | secrets |\n| `ops.pm2` | PM2 | 176×56 | |\n| `ops.maven` | Maven | 176×56 | build |\n\n## Auth / Identity (`auth`)\n\n| Type | Label | Default size | Aliases |\n| --- | --- | --- | --- |\n| `auth.oidc` | OpenID Connect | 176×56 | oidc |\n| `auth.jwt` | JWT | 176×56 | token |\n| `auth.keycloak` | Keycloak | 176×56 | |\n| `auth.auth0` | Auth0 | 176×56 | |\n| `auth.okta` | Okta | 176×56 | |\n| `auth.firebase` | Firebase Auth | 176×56 | firebase |\n| `auth.oauth` | OAuth 2.0 | 176×56 | oauth |\n| `auth.saml` | SAML | 176×56 | |\n| `auth.ldap` | LDAP | 176×56 | |\n\n## Observability (`observability`)\n\n| Type | Label | Default size | Aliases |\n| --- | --- | --- | --- |\n| `obs.prometheus` | Prometheus | 176×56 | metrics |\n| `obs.grafana` | Grafana | 176×56 | dashboard |\n| `obs.datadog` | Datadog | 176×56 | |\n| `obs.sentry` | Sentry | 176×56 | errors |\n| `obs.elasticsearch` | Elasticsearch | 176×56 | elk |\n| `obs.logstash` | Logstash | 176×56 | elk |\n| `obs.kibana` | Kibana | 176×56 | elk |\n| `obs.jaeger` | Jaeger | 176×56 | tracing |\n| `obs.otel` | OpenTelemetry | 176×56 | otel, tracing |\n| `obs.newrelic` | New Relic | 176×56 | |\n\n## AWS (`aws`)\n\n| Type | Label | Default size | Aliases |\n| --- | --- | --- | --- |\n| `aws.ec2` | EC2 | 176×56 | aws |\n| `aws.lambda` | Lambda | 176×56 | aws |\n| `aws.s3` | S3 | 176×56 | aws |\n| `aws.ebs` | EBS | 176×56 | aws |\n| `aws.efs` | EFS | 176×56 | aws |\n| `aws.rds` | RDS | 176×56 | aws |\n| `aws.aurora` | Aurora | 176×56 | aws |\n| `aws.dynamodb` | DynamoDB | 176×56 | aws |\n| `aws.elasticache` | ElastiCache | 176×56 | aws |\n| `aws.redshift` | Redshift | 176×56 | aws |\n| `aws.vpc` | VPC | 176×56 | aws |\n| `aws.subnet` | Subnet | 176×56 | aws |\n| `aws.internet-gateway` | Internet Gateway | 176×56 | aws |\n| `aws.nat-gateway` | NAT Gateway | 176×56 | aws |\n| `aws.elb` | ELB | 176×56 | aws |\n| `aws.alb` | ALB | 176×56 | aws |\n| `aws.nlb` | NLB | 176×56 | aws |\n| `aws.api-gateway` | API Gateway | 176×56 | aws |\n| `aws.cloudfront` | CloudFront | 176×56 | aws |\n| `aws.route-53` | Route 53 | 176×56 | aws |\n| `aws.sqs` | SQS | 176×56 | aws |\n| `aws.sns` | SNS | 176×56 | aws |\n| `aws.eventbridge` | EventBridge | 176×56 | aws |\n| `aws.kinesis` | Kinesis | 176×56 | aws |\n| `aws.step-functions` | Step Functions | 176×56 | aws |\n| `aws.ecs` | ECS | 176×56 | aws |\n| `aws.eks` | EKS | 176×56 | aws |\n| `aws.fargate` | Fargate | 176×56 | aws |\n| `aws.ecr` | ECR | 176×56 | aws |\n| `aws.iam` | IAM | 176×56 | aws |\n| `aws.cognito` | Cognito | 176×56 | aws |\n| `aws.secrets-manager` | Secrets Manager | 176×56 | aws |\n| `aws.kms` | KMS | 176×56 | aws |\n| `aws.cloudwatch` | CloudWatch | 176×56 | aws |\n| `aws.cloudtrail` | CloudTrail | 176×56 | aws |\n| `aws.glue` | Glue | 176×56 | aws |\n| `aws.athena` | Athena | 176×56 | aws |\n| `aws.sagemaker` | SageMaker | 176×56 | aws |\n| `aws.appsync` | AppSync | 176×56 | aws |\n| `aws.amplify` | Amplify | 176×56 | aws |\n\n## Azure (`azure`)\n\n| Type | Label | Default size | Aliases |\n| --- | --- | --- | --- |\n| `azure.virtual-machine` | Virtual Machine | 176×56 | azure |\n| `azure.vm-scale-set` | VM Scale Set | 176×56 | azure |\n| `azure.app-service` | App Service | 176×56 | azure |\n| `azure.functions` | Functions | 176×56 | azure |\n| `azure.container-instances` | Container Instances | 176×56 | azure |\n| `azure.aks` | AKS | 176×56 | azure |\n| `azure.container-registry` | Container Registry | 176×56 | azure |\n| `azure.blob-storage` | Blob Storage | 176×56 | azure |\n| `azure.files` | Files | 176×56 | azure |\n| `azure.disk` | Disk | 176×56 | azure |\n| `azure.sql-database` | SQL Database | 176×56 | azure |\n| `azure.cosmos-db` | Cosmos DB | 176×56 | azure |\n| `azure.cache-for-redis` | Cache for Redis | 176×56 | azure |\n| `azure.synapse` | Synapse | 176×56 | azure |\n| `azure.virtual-network` | Virtual Network | 176×56 | azure |\n| `azure.load-balancer` | Load Balancer | 176×56 | azure |\n| `azure.application-gateway` | Application Gateway | 176×56 | azure |\n| `azure.front-door` | Front Door | 176×56 | azure |\n| `azure.service-bus` | Service Bus | 176×56 | azure |\n| `azure.event-hub` | Event Hub | 176×56 | azure |\n| `azure.event-grid` | Event Grid | 176×56 | azure |\n| `azure.key-vault` | Key Vault | 176×56 | azure |\n| `azure.entra-id` | Entra ID | 176×56 | azure |\n| `azure.monitor` | Monitor | 176×56 | azure |\n| `azure.application-insights` | Application Insights | 176×56 | azure |\n| `azure.logic-apps` | Logic Apps | 176×56 | azure |\n| `azure.api-management` | API Management | 176×56 | azure |\n\n## GCP (`gcp`)\n\n| Type | Label | Default size | Aliases |\n| --- | --- | --- | --- |\n| `gcp.compute-engine` | Compute Engine | 176×56 | gcp, google |\n| `gcp.app-engine` | App Engine | 176×56 | gcp, google |\n| `gcp.cloud-functions` | Cloud Functions | 176×56 | gcp, google |\n| `gcp.cloud-run` | Cloud Run | 176×56 | gcp, google |\n| `gcp.gke` | GKE | 176×56 | gcp, google |\n| `gcp.artifact-registry` | Artifact Registry | 176×56 | gcp, google |\n| `gcp.cloud-storage` | Cloud Storage | 176×56 | gcp, google |\n| `gcp.persistent-disk` | Persistent Disk | 176×56 | gcp, google |\n| `gcp.cloud-sql` | Cloud SQL | 176×56 | gcp, google |\n| `gcp.cloud-spanner` | Cloud Spanner | 176×56 | gcp, google |\n| `gcp.firestore` | Firestore | 176×56 | gcp, google |\n| `gcp.bigtable` | Bigtable | 176×56 | gcp, google |\n| `gcp.bigquery` | BigQuery | 176×56 | gcp, google |\n| `gcp.memorystore` | Memorystore | 176×56 | gcp, google |\n| `gcp.vpc` | VPC | 176×56 | gcp, google |\n| `gcp.cloud-load-balancing` | Cloud Load Balancing | 176×56 | gcp, google |\n| `gcp.cloud-cdn` | Cloud CDN | 176×56 | gcp, google |\n| `gcp.cloud-dns` | Cloud DNS | 176×56 | gcp, google |\n| `gcp.pub-sub` | Pub/Sub | 176×56 | gcp, google |\n| `gcp.dataflow` | Dataflow | 176×56 | gcp, google |\n| `gcp.cloud-tasks` | Cloud Tasks | 176×56 | gcp, google |\n| `gcp.iam` | IAM | 176×56 | gcp, google |\n| `gcp.secret-manager` | Secret Manager | 176×56 | gcp, google |\n| `gcp.cloud-kms` | Cloud KMS | 176×56 | gcp, google |\n| `gcp.cloud-monitoring` | Cloud Monitoring | 176×56 | gcp, google |\n| `gcp.vertex-ai` | Vertex AI | 176×56 | gcp, google |\n",
|
|
@@ -14,13 +14,116 @@ const pagination_js_1 = require("../lib/pagination.js");
|
|
|
14
14
|
const lookups_js_1 = require("./lookups.js");
|
|
15
15
|
const shared_js_1 = require("./shared.js");
|
|
16
16
|
const directionArg = zod_1.z.enum(["DOWN", "RIGHT", "UP", "LEFT"]).optional().describe("Layout flow direction (default DOWN)");
|
|
17
|
+
const sourceArg = zod_1.z
|
|
18
|
+
.object({
|
|
19
|
+
format: zod_1.z.enum(["mermaid", "bpmn"]).describe('"mermaid": Mermaid text; "bpmn": a BPMN 2.0 XML file'),
|
|
20
|
+
text: zod_1.z
|
|
21
|
+
.string()
|
|
22
|
+
.min(1)
|
|
23
|
+
.max(2_000_000)
|
|
24
|
+
.describe("Mermaid: a flowchart/graph becomes nodes and edges (laid out automatically) and stays attached as the diagram's source; any other Mermaid type is stored as source only. BPMN: the XML is the diagram (its shapes, flows, pools and lanes are derived from it with their positions)"),
|
|
25
|
+
})
|
|
26
|
+
.strict()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Create the diagram from Mermaid text or BPMN 2.0 XML instead of nodes/edges");
|
|
29
|
+
// A BPMN diagram keeps its truth in BPMN 2.0 XML: the model is derived from it.
|
|
30
|
+
const bpmnSource = (text) => ({ format: "bpmn", text, diagramType: "bpmn", grammar: callman_core_1.BPMN_GRAMMAR_VERSION });
|
|
31
|
+
// Typed bpmn.* nodes → a BPMN diagram: the model becomes XML, the XML becomes
|
|
32
|
+
// the diagram's source, and the shapes are derived back from it.
|
|
33
|
+
const bpmnDocumentFromModel = (model, unpositionedNodeIds, base) => {
|
|
34
|
+
const placed = unpositionedNodeIds.length > 0 ? (0, callman_core_1.applyDiagramOps)(model, (0, callman_core_1.placeBpmnNodesOps)(model, unpositionedNodeIds)).doc : model;
|
|
35
|
+
const xml = (0, callman_core_1.bpmnXmlFromDocument)({ ...placed, source: undefined });
|
|
36
|
+
const document = (0, callman_core_1.applyDiagramOps)({ ...base, nodes: [], edges: [], source: undefined }, [{ op: "setSource", source: bpmnSource(xml), resync: true }], { now: now() }).doc;
|
|
37
|
+
return { document, layoutNote: unpositionedNodeIds.length > 0 ? `Written as BPMN 2.0 XML; ${unpositionedNodeIds.length} node(s) placed left to right after their predecessors.` : "Written as BPMN 2.0 XML with the positions given." };
|
|
38
|
+
};
|
|
39
|
+
// Every document entering a tool goes through the shared gate (legacy fields
|
|
40
|
+
// stripped, ids deduped, parents healed) — the same one the studio applies.
|
|
17
41
|
const loadDocument = (diagram) => {
|
|
18
|
-
const
|
|
42
|
+
const result = (0, callman_core_1.normalizeDocument)(diagram.document);
|
|
43
|
+
if (!result.document.nodes && !result.document.edges) {
|
|
44
|
+
throw new errors_js_1.ToolInputError("DIAGRAM_DOCUMENT_INVALID", `The stored diagram "${diagram.name}" is not a valid document.`);
|
|
45
|
+
}
|
|
46
|
+
return result.document;
|
|
47
|
+
};
|
|
48
|
+
const now = () => new Date().toISOString();
|
|
49
|
+
// A source-backed diagram whose Mermaid type is not a flowchart has no model
|
|
50
|
+
// to edit: structural tools refuse it clearly instead of guessing. (BPMN
|
|
51
|
+
// diagrams are editable: their ops patch the XML.)
|
|
52
|
+
const assertEditableStructure = (doc) => {
|
|
53
|
+
if (doc.source && doc.source.format === "mermaid" && doc.source.diagramType !== "flowchart") {
|
|
54
|
+
throw new errors_js_1.ToolInputError("DIAGRAM_SOURCE_UNSUPPORTED_TYPE", `This diagram is a Mermaid ${doc.source.diagramType} kept as source only; nodes and edges cannot be edited through ops. Update its source with update_diagram { source } or read it with get_diagram view:"source".`, { diagramType: doc.source.diagramType });
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
// Mermaid text → the document it describes: a flowchart is parsed into
|
|
58
|
+
// nodes/edges (positions come from layout); anything else is kept as
|
|
59
|
+
// source only (the desktop previews it, tools read it back).
|
|
60
|
+
const documentFromSource = async (title, text, existing, direction, format = "mermaid") => {
|
|
61
|
+
if (format === "bpmn") {
|
|
62
|
+
if (!(0, callman_core_1.looksLikeBpmn)(text))
|
|
63
|
+
throw new errors_js_1.ToolInputError("DIAGRAM_SOURCE_INVALID", "The text is not a BPMN 2.0 document (no <definitions> root).");
|
|
64
|
+
const parsed = (0, callman_core_1.parseBpmn)(text);
|
|
65
|
+
if (!parsed.ok) {
|
|
66
|
+
throw new errors_js_1.ToolInputError("DIAGRAM_SOURCE_INVALID", "The BPMN XML could not be read.", { diagnostics: parsed.structure.diagnostics.filter((entry) => entry.severity === "error").slice(0, 20) });
|
|
67
|
+
}
|
|
68
|
+
const base = existing ?? (0, callman_core_1.createEmptyDocument)(title);
|
|
69
|
+
let document;
|
|
70
|
+
try {
|
|
71
|
+
document = (0, callman_core_1.applyDiagramOps)(base, [{ op: "setSource", source: bpmnSource(text), resync: true }], { now: now() }).doc;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return (0, build_js_1.toToolError)(error);
|
|
75
|
+
}
|
|
76
|
+
// Shapes the XML has no diagram interchange for are placed after their predecessors.
|
|
77
|
+
const unplaced = parsed.structure.nodes.filter((node) => !node.position).map((node) => node.id).filter((id) => document.nodes.some((node) => node.id === id));
|
|
78
|
+
if (unplaced.length > 0)
|
|
79
|
+
document = (0, callman_core_1.applyDiagramOps)(document, (0, callman_core_1.placeBpmnNodesOps)(document, unplaced), { now: now() }).doc;
|
|
80
|
+
const warnings = parsed.structure.diagnostics.filter((entry) => entry.severity !== "error").map((entry) => entry.message);
|
|
81
|
+
return { document, layoutNote: unplaced.length > 0 ? `Positions from the BPMN diagram interchange; ${unplaced.length} shape(s) without one were placed.` : "Positions from the BPMN diagram interchange.", warnings };
|
|
82
|
+
}
|
|
83
|
+
if (!(0, callman_core_1.looksLikeMermaid)(text)) {
|
|
84
|
+
throw new errors_js_1.ToolInputError("DIAGRAM_SOURCE_INVALID", "The text does not start with a Mermaid diagram header (flowchart, graph, sequenceDiagram, …).");
|
|
85
|
+
}
|
|
86
|
+
const base = existing ?? (0, callman_core_1.createEmptyDocument)(title);
|
|
87
|
+
if (!(0, callman_core_1.isMermaidFlowchart)(text)) {
|
|
88
|
+
const header = text.replace(/^---[\s\S]*?\n---\s*\n/, "").split("\n").map((line) => line.trim()).find((line) => line !== "" && !line.startsWith("%%")) ?? "";
|
|
89
|
+
const diagramType = header.split(/\s+/)[0] ?? "unknown";
|
|
90
|
+
const source = { format: "mermaid", text, diagramType, grammar: callman_core_1.MERMAID_GRAMMAR_VERSION, valid: true };
|
|
91
|
+
const doc = (0, callman_core_1.applyDiagramOps)(base, [{ op: "setSource", source, resync: false }], { now: now() }).doc;
|
|
92
|
+
return { document: doc, layoutNote: `Stored as Mermaid ${diagramType} source (no nodes to lay out).`, warnings: [] };
|
|
93
|
+
}
|
|
94
|
+
const parsed = (0, callman_core_1.parseMermaid)(text);
|
|
19
95
|
if (!parsed.ok) {
|
|
20
|
-
throw new errors_js_1.ToolInputError("
|
|
96
|
+
throw new errors_js_1.ToolInputError("DIAGRAM_SOURCE_INVALID", "The Mermaid flowchart has syntax errors.", {
|
|
97
|
+
diagnostics: parsed.structure.diagnostics.filter((entry) => entry.severity === "error").slice(0, 20),
|
|
98
|
+
});
|
|
21
99
|
}
|
|
22
|
-
|
|
100
|
+
const source = { format: "mermaid", text, diagramType: "flowchart", grammar: callman_core_1.MERMAID_GRAMMAR_VERSION };
|
|
101
|
+
let applied;
|
|
102
|
+
try {
|
|
103
|
+
applied = (0, callman_core_1.applyDiagramOps)(base, [{ op: "setSource", source }], { now: now() });
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
return (0, build_js_1.toToolError)(error);
|
|
107
|
+
}
|
|
108
|
+
const warnings = parsed.structure.diagnostics.filter((entry) => entry.severity !== "error").map((entry) => entry.message);
|
|
109
|
+
const newIds = applied.added.nodes;
|
|
110
|
+
let document = applied.doc;
|
|
111
|
+
let layoutNote;
|
|
112
|
+
if (newIds.length > 0) {
|
|
113
|
+
if (existing && existing.nodes.length > 0) {
|
|
114
|
+
document = (0, layout_js_1.placeNewNodes)(document, newIds);
|
|
115
|
+
layoutNote = `Placed ${newIds.length} new node(s) below the existing ones.`;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const laid = await (0, layout_js_1.layoutDocument)(document, { direction: direction ?? parsed.structure.direction });
|
|
119
|
+
document = laid.document;
|
|
120
|
+
layoutNote = laid.warning ?? `Laid out with ${laid.engine}.`;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { document, layoutNote, warnings };
|
|
23
124
|
};
|
|
125
|
+
// Save with the revision we read; on a 409 the caller reloads and reapplies.
|
|
126
|
+
const isConflict = (error) => error instanceof errors_js_1.CallmanApiError && error.status === 409;
|
|
24
127
|
const registerDiagramTools = (server, ctx) => {
|
|
25
128
|
server.registerTool("list_diagrams", {
|
|
26
129
|
title: "List diagrams",
|
|
@@ -36,52 +139,88 @@ const registerDiagramTools = (server, ctx) => {
|
|
|
36
139
|
}));
|
|
37
140
|
server.registerTool("get_diagram", {
|
|
38
141
|
title: "Get a diagram",
|
|
39
|
-
description: "Read a diagram: `summary` (counts, owner, sharing), `graph` (default — nodes with type/label/parent/style and edges with semantic/color, in the same shape the write tools accept: enough to describe, restyle or edit it) or `full` (the complete stored DiagramDocument with positions, sizes and routes, up to 512 KB).",
|
|
40
|
-
inputSchema: { diagram: (0, shared_js_1.refArg)("Diagram"), view: zod_1.z.enum(["summary", "graph", "full"]).default("graph") },
|
|
142
|
+
description: "Read a diagram: `summary` (counts, owner, sharing), `graph` (default — nodes with type/label/parent/style and edges with semantic/color/routing/sides, in the same shape the write tools accept: enough to describe, restyle or edit it), `mermaid` (the diagram rendered as Mermaid flowchart text), `source` (the Mermaid text or BPMN 2.0 XML a source-backed diagram keeps) or `full` (the complete stored DiagramDocument with positions, sizes and routes, up to 512 KB).",
|
|
143
|
+
inputSchema: { diagram: (0, shared_js_1.refArg)("Diagram"), view: zod_1.z.enum(["summary", "graph", "mermaid", "source", "full"]).default("graph") },
|
|
41
144
|
annotations: shared_js_1.READ,
|
|
42
145
|
}, (0, shared_js_1.guarded)(async ({ diagram, view }) => {
|
|
43
146
|
const resolved = await (0, lookups_js_1.resolveDiagram)(ctx, diagram);
|
|
44
147
|
const doc = loadDocument(resolved.item);
|
|
45
148
|
const summary = (0, summarize_js_1.diagramListView)(resolved.item);
|
|
46
|
-
const body = view === "summary"
|
|
149
|
+
const body = view === "summary"
|
|
150
|
+
? summary
|
|
151
|
+
: view === "graph"
|
|
152
|
+
? { ...summary, ...(0, summarize_js_1.diagramGraphView)(doc) }
|
|
153
|
+
: view === "mermaid"
|
|
154
|
+
? { ...summary, ...(0, summarize_js_1.diagramMermaidView)(doc) }
|
|
155
|
+
: view === "source"
|
|
156
|
+
? { ...summary, ...(0, summarize_js_1.diagramSourceView)(doc) }
|
|
157
|
+
: { ...summary, ...(0, summarize_js_1.diagramFullView)(doc) };
|
|
47
158
|
return (0, shared_js_1.ok)({ diagram: body, ...(resolved.annotation ? { resolved: [(0, resolve_js_1.resolvedSummary)(diagram, resolved)] } : {}) });
|
|
48
159
|
}));
|
|
49
160
|
server.registerTool("create_diagram", {
|
|
50
161
|
title: "Create a system-design diagram",
|
|
51
|
-
description: "Create a diagram from typed nodes and edges. Node `type` must come from callman://diagram/components (e.g. net.service, net.api-gateway, db.postgres, mq.kafka, aws.lambda, flow.process); unknown types are rejected with suggestions unless strictTypes: false. Group nodes inside a container node — frame, pool.horizontal or pool.vertical — with `parent`. Style nodes with `style` (fill / stroke / textColor as CSS colors, strokeWidth, dashed, radius, bold, fontSize, opacity…) and edges with `semantic` (sync default, async, fallback, bus)
|
|
162
|
+
description: "Create a diagram from typed nodes and edges — or from Mermaid text / BPMN 2.0 XML (`source`). A BPMN process (`kind: \"bpmn\"`, or nodes typed bpmn.*) is stored as BPMN 2.0 XML the desktop opens in its BPMN editor. Node `type` must come from callman://diagram/components (e.g. net.service, net.api-gateway, db.postgres, mq.kafka, aws.lambda, flow.process); unknown types are rejected with suggestions unless strictTypes: false. Group nodes inside a container node — frame, pool.horizontal or pool.vertical — with `parent`. Style nodes with `style` (fill / stroke / textColor as CSS colors, strokeWidth, dashed, radius, bold, fontSize, opacity…) and edges with `semantic` (sync default, async, fallback, bus), an optional `color`, `routing` and fixed `sourceSide`/`targetSide`. Layout is automatic when nodes have no positions. Read callman://docs/diagram-schema for every field. Keep it readable: 8–25 nodes, short labels.",
|
|
52
163
|
inputSchema: {
|
|
53
164
|
name: zod_1.z.string().min(1).max(200).describe("Diagram name (library entry)"),
|
|
54
|
-
|
|
165
|
+
title: build_js_1.diagramInputSchema.shape.title.optional().describe("Diagram title (defaults to the name)"),
|
|
166
|
+
nodes: build_js_1.diagramInputSchema.shape.nodes.optional(),
|
|
167
|
+
edges: build_js_1.diagramInputSchema.shape.edges.optional(),
|
|
168
|
+
canvas: build_js_1.diagramInputSchema.shape.canvas,
|
|
169
|
+
source: sourceArg,
|
|
170
|
+
kind: zod_1.z.enum(["canvas", "bpmn"]).optional().describe('"bpmn": model a business process — nodes use bpmn.* types and the diagram is stored as BPMN 2.0 XML (default: canvas; inferred as bpmn when every node type starts with "bpmn.")'),
|
|
55
171
|
autoLayout: zod_1.z.boolean().optional().describe("Force layout even when positions were given"),
|
|
56
172
|
direction: directionArg,
|
|
57
173
|
strictTypes: zod_1.z.boolean().optional(),
|
|
58
174
|
dryRun: shared_js_1.dryRunArg,
|
|
59
175
|
},
|
|
60
176
|
annotations: shared_js_1.WRITE,
|
|
61
|
-
}, (0, shared_js_1.guarded)(async ({ name, autoLayout, direction, strictTypes, dryRun
|
|
62
|
-
|
|
63
|
-
|
|
177
|
+
}, (0, shared_js_1.guarded)(async ({ name, title, nodes, edges, canvas, source, kind, autoLayout, direction, strictTypes, dryRun }) => {
|
|
178
|
+
if (!nodes && !source)
|
|
179
|
+
throw new errors_js_1.ToolInputError("NOTHING_TO_CREATE", "Pass nodes (+ edges) or source { format: \"mermaid\" | \"bpmn\", text }.");
|
|
180
|
+
if (nodes && source)
|
|
181
|
+
throw new errors_js_1.ToolInputError("CONFLICTING_INPUT", "Pass either nodes or source, not both.");
|
|
182
|
+
let document;
|
|
64
183
|
let layoutNote;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
184
|
+
let warnings = [];
|
|
185
|
+
const bpmnKind = kind === "bpmn" || (kind === undefined && Boolean(nodes && nodes.length > 0 && nodes.every((node) => node.type.startsWith("bpmn."))));
|
|
186
|
+
if (source) {
|
|
187
|
+
const fromSource = await documentFromSource(title ?? name, source.text, null, direction, source.format);
|
|
188
|
+
document = canvas ? { ...fromSource.document, canvas: { ...fromSource.document.canvas, ...canvas } } : fromSource.document;
|
|
189
|
+
layoutNote = fromSource.layoutNote;
|
|
190
|
+
warnings = fromSource.warnings;
|
|
191
|
+
}
|
|
192
|
+
else if (bpmnKind) {
|
|
193
|
+
const built = (0, build_js_1.buildDiagramDocument)({ title: title ?? name, nodes: nodes ?? [], edges: edges ?? [], canvas }, { strictTypes, kind: "bpmn" });
|
|
194
|
+
const bpmn = bpmnDocumentFromModel(built.document, built.unpositionedNodeIds, (0, callman_core_1.createEmptyDocument)(title ?? name));
|
|
195
|
+
document = bpmn.document;
|
|
196
|
+
layoutNote = bpmn.layoutNote;
|
|
197
|
+
warnings = built.warnings;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const built = (0, build_js_1.buildDiagramDocument)({ title: title ?? name, nodes: nodes ?? [], edges: edges ?? [], canvas }, { strictTypes });
|
|
201
|
+
document = built.document;
|
|
202
|
+
warnings = built.warnings;
|
|
203
|
+
if (autoLayout || built.unpositionedNodeIds.length > 0) {
|
|
204
|
+
const laid = await (0, layout_js_1.layoutDocument)(document, { direction });
|
|
205
|
+
document = laid.document;
|
|
206
|
+
layoutNote = laid.warning ?? `Laid out with ${laid.engine}.`;
|
|
207
|
+
}
|
|
69
208
|
}
|
|
70
209
|
if (dryRun) {
|
|
71
|
-
return (0, shared_js_1.ok)({ dryRun: true, diagram: { name, ...(0, summarize_js_1.diagramGraphView)(document) }, warnings
|
|
210
|
+
return (0, shared_js_1.ok)({ dryRun: true, diagram: { name, ...(0, summarize_js_1.diagramGraphView)(document) }, warnings, ...(layoutNote ? { layout: layoutNote } : {}) });
|
|
72
211
|
}
|
|
73
212
|
const created = await ctx.api.createDiagram({ name: name.trim(), document });
|
|
74
213
|
ctx.invalidate("diagrams");
|
|
75
214
|
return (0, shared_js_1.ok)({
|
|
76
215
|
diagram: { ...(0, summarize_js_1.diagramListView)(created), ...(0, summarize_js_1.diagramGraphView)(document) },
|
|
77
|
-
warnings
|
|
216
|
+
warnings,
|
|
78
217
|
...(layoutNote ? { layout: layoutNote } : {}),
|
|
79
218
|
note: "Saved. Open it in the desktop app (Diagrams → Refresh). Edit with patch_diagram; share with share_diagram.",
|
|
80
219
|
});
|
|
81
220
|
}));
|
|
82
221
|
server.registerTool("update_diagram", {
|
|
83
222
|
title: "Replace a diagram's content",
|
|
84
|
-
description: "Replace a diagram's whole document (nodes + edges, same fields as create_diagram) and/or rename it, retitle it or change its canvas
|
|
223
|
+
description: "Replace a diagram's whole document (nodes + edges, same fields as create_diagram), replace or attach its Mermaid text or BPMN 2.0 XML `source`, and/or rename it, retitle it or change its canvas — keeping its identity and sharing. Passing nodes replaces ALL nodes and edges (omitted edges are removed). Passing `source` re-derives nodes and edges from the text (existing ones keep their place; new ones are placed). For small edits — including restyling — prefer patch_diagram.",
|
|
85
224
|
inputSchema: {
|
|
86
225
|
diagram: (0, shared_js_1.refArg)("Diagram"),
|
|
87
226
|
name: zod_1.z.string().min(1).max(200).optional(),
|
|
@@ -89,15 +228,18 @@ const registerDiagramTools = (server, ctx) => {
|
|
|
89
228
|
nodes: build_js_1.diagramInputSchema.shape.nodes.optional(),
|
|
90
229
|
edges: build_js_1.diagramInputSchema.shape.edges.optional(),
|
|
91
230
|
canvas: build_js_1.diagramInputSchema.shape.canvas,
|
|
231
|
+
source: sourceArg,
|
|
92
232
|
autoLayout: zod_1.z.boolean().optional(),
|
|
93
233
|
direction: directionArg,
|
|
94
234
|
strictTypes: zod_1.z.boolean().optional(),
|
|
95
235
|
},
|
|
96
236
|
annotations: shared_js_1.IDEMPOTENT_WRITE,
|
|
97
|
-
}, (0, shared_js_1.guarded)(async ({ diagram, name, title, nodes, edges, canvas, autoLayout, direction, strictTypes }) => {
|
|
98
|
-
if (name === undefined && nodes === undefined && title === undefined && canvas === undefined) {
|
|
99
|
-
throw new errors_js_1.ToolInputError("NOTHING_TO_UPDATE", "Pass name, title, canvas and/or nodes (+ edges).");
|
|
237
|
+
}, (0, shared_js_1.guarded)(async ({ diagram, name, title, nodes, edges, canvas, source, autoLayout, direction, strictTypes }) => {
|
|
238
|
+
if (name === undefined && nodes === undefined && title === undefined && canvas === undefined && source === undefined) {
|
|
239
|
+
throw new errors_js_1.ToolInputError("NOTHING_TO_UPDATE", "Pass name, title, canvas, source and/or nodes (+ edges).");
|
|
100
240
|
}
|
|
241
|
+
if (nodes && source)
|
|
242
|
+
throw new errors_js_1.ToolInputError("CONFLICTING_INPUT", "Pass either nodes or source, not both.");
|
|
101
243
|
const resolved = await (0, lookups_js_1.resolveDiagram)(ctx, diagram);
|
|
102
244
|
if (!resolved.item.canEdit)
|
|
103
245
|
throw new errors_js_1.ToolInputError("DIAGRAM_READ_ONLY", "You have read-only access to this diagram.");
|
|
@@ -105,33 +247,60 @@ const registerDiagramTools = (server, ctx) => {
|
|
|
105
247
|
let document;
|
|
106
248
|
let layoutNote;
|
|
107
249
|
let warnings = [];
|
|
108
|
-
if (nodes) {
|
|
250
|
+
if (nodes && existing.source?.format === "bpmn") {
|
|
251
|
+
// A BPMN diagram stays BPMN: the new model is written as fresh XML.
|
|
252
|
+
const built = (0, build_js_1.buildDiagramDocument)({ title: title ?? existing.meta.title, nodes, edges: edges ?? [], canvas: canvas ?? existing.canvas }, { strictTypes, existingId: existing.id, createdAt: existing.meta.createdAt, kind: "bpmn" });
|
|
253
|
+
const bpmn = bpmnDocumentFromModel(built.document, built.unpositionedNodeIds, existing);
|
|
254
|
+
document = bpmn.document;
|
|
255
|
+
layoutNote = bpmn.layoutNote;
|
|
256
|
+
warnings = [...built.warnings, "The BPMN XML was rewritten from the input; anything in the file that is not a flow node, flow, pool or lane was dropped. Prefer patch_diagram for edits."];
|
|
257
|
+
}
|
|
258
|
+
else if (nodes) {
|
|
259
|
+
assertEditableStructure(existing);
|
|
109
260
|
const built = (0, build_js_1.buildDiagramDocument)({ title: title ?? existing.meta.title, nodes, edges: edges ?? [], canvas: canvas ?? existing.canvas }, { strictTypes, existingId: existing.id, createdAt: existing.meta.createdAt });
|
|
110
261
|
document = built.document;
|
|
111
262
|
warnings = built.warnings;
|
|
112
263
|
if (edges === undefined && existing.edges.length > 0) {
|
|
113
264
|
warnings.push(`edges was omitted, so the ${existing.edges.length} existing edge(s) were removed. Pass edges to keep connections, or use patch_diagram for partial edits.`);
|
|
114
265
|
}
|
|
266
|
+
if (existing.source)
|
|
267
|
+
warnings.push("The diagram's Mermaid source was detached: nodes and edges were replaced from the input.");
|
|
115
268
|
if (autoLayout || built.unpositionedNodeIds.length > 0) {
|
|
116
269
|
const laid = await (0, layout_js_1.layoutDocument)(document, { direction });
|
|
117
270
|
document = laid.document;
|
|
118
271
|
layoutNote = laid.warning ?? `Laid out with ${laid.engine}.`;
|
|
119
272
|
}
|
|
120
273
|
}
|
|
274
|
+
else if (source) {
|
|
275
|
+
const fromSource = await documentFromSource(title ?? existing.meta.title, source.text, existing, direction, source.format);
|
|
276
|
+
document = fromSource.document;
|
|
277
|
+
layoutNote = fromSource.layoutNote;
|
|
278
|
+
warnings = fromSource.warnings;
|
|
279
|
+
if (title !== undefined || canvas !== undefined) {
|
|
280
|
+
document = {
|
|
281
|
+
...document,
|
|
282
|
+
...(canvas !== undefined ? { canvas: { ...document.canvas, ...canvas } } : {}),
|
|
283
|
+
meta: { ...document.meta, ...(title !== undefined ? { title } : {}) },
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
121
287
|
else if (title !== undefined || canvas !== undefined) {
|
|
122
288
|
document = {
|
|
123
289
|
...existing,
|
|
124
290
|
...(canvas !== undefined ? { canvas: { ...existing.canvas, ...canvas } } : {}),
|
|
125
|
-
meta: { ...existing.meta, ...(title !== undefined ? { title } : {}), updatedAt:
|
|
291
|
+
meta: { ...existing.meta, ...(title !== undefined ? { title } : {}), updatedAt: now() },
|
|
126
292
|
};
|
|
127
293
|
}
|
|
128
|
-
const updated = await ctx.api.updateDiagram(resolved.item.id, {
|
|
294
|
+
const updated = await ctx.api.updateDiagram(resolved.item.id, {
|
|
295
|
+
...(name !== undefined ? { name: name.trim() } : {}),
|
|
296
|
+
...(document ? { document, ...(typeof existing.meta.revision === "number" ? { baseRevision: existing.meta.revision } : {}) } : {}),
|
|
297
|
+
});
|
|
129
298
|
ctx.invalidate("diagrams");
|
|
130
299
|
return (0, shared_js_1.ok)({ diagram: { ...(0, summarize_js_1.diagramListView)(updated), ...(0, summarize_js_1.diagramGraphView)(document ?? existing) }, warnings, ...(layoutNote ? { layout: layoutNote } : {}), resolved: [(0, resolve_js_1.resolvedSummary)(diagram, resolved)] });
|
|
131
300
|
}));
|
|
132
301
|
server.registerTool("patch_diagram", {
|
|
133
302
|
title: "Edit a diagram incrementally",
|
|
134
|
-
description: "Apply operations to a diagram without resending it: addNode, updateNode, removeNode (drops its edges), addEdge, updateEdge, removeEdge, setTitle. updateNode `changes` MERGE: style and data keys you pass change, a key set to null is removed. updateEdge can change semantic, color, label, arrows and ends. Moving a node
|
|
303
|
+
description: "Apply operations to a diagram without resending it: addNode, updateNode, removeNode (drops its edges, lifts its children out), addEdge, updateEdge, removeEdge, setTitle, moveNode, reparent, setEdgeRoute (routing, fixed sides, bends), reorder (stacking), setCanvas, group (wrap nodes in a container), ungroup, relayout (a hint: new | all). updateNode `changes` MERGE: style and data keys you pass change, a key set to null is removed. updateEdge can change semantic, color, label, arrows, sides and ends. Moving a node re-routes its edges. `relayout`: none | new (place only new nodes, default) | all (re-run automatic layout). Call get_diagram (view: graph) first to see node ids and current styles. Ops are documented in callman://docs/diagram-schema.",
|
|
135
304
|
inputSchema: {
|
|
136
305
|
diagram: (0, shared_js_1.refArg)("Diagram"),
|
|
137
306
|
ops: zod_1.z.array(patch_js_1.diagramPatchOpSchema).min(1).max(200),
|
|
@@ -144,21 +313,53 @@ const registerDiagramTools = (server, ctx) => {
|
|
|
144
313
|
const resolved = await (0, lookups_js_1.resolveDiagram)(ctx, diagram);
|
|
145
314
|
if (!resolved.item.canEdit)
|
|
146
315
|
throw new errors_js_1.ToolInputError("DIAGRAM_READ_ONLY", "You have read-only access to this diagram.");
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
document =
|
|
154
|
-
layoutNote
|
|
316
|
+
// Apply on the freshest copy; a 409 (someone saved meanwhile) reloads
|
|
317
|
+
// and reapplies once — ops describe intent, so they replay cleanly.
|
|
318
|
+
const attempt = async (record) => {
|
|
319
|
+
const existing = loadDocument(record);
|
|
320
|
+
assertEditableStructure(existing);
|
|
321
|
+
const patched = (0, patch_js_1.applyDiagramPatch)(existing, ops, { strictTypes });
|
|
322
|
+
let document = patched.document;
|
|
323
|
+
let layoutNote;
|
|
324
|
+
const wantsAll = relayout === "all" || patched.relayout?.scope === "all";
|
|
325
|
+
const layoutDirection = direction ?? patched.relayout?.direction;
|
|
326
|
+
const bpmn = document.source?.format === "bpmn";
|
|
327
|
+
if (wantsAll) {
|
|
328
|
+
// A process reads left to right; the BPMN XML follows the moves.
|
|
329
|
+
const laid = await (0, layout_js_1.layoutDocument)(document, { direction: layoutDirection ?? (bpmn ? "RIGHT" : undefined) });
|
|
330
|
+
document = laid.document;
|
|
331
|
+
layoutNote = laid.warning ?? `Laid out with ${laid.engine}.`;
|
|
332
|
+
}
|
|
333
|
+
else if (relayout === "new" && patched.addedNodeIds.length > 0) {
|
|
334
|
+
const ids = patched.addedNodeIds.filter((id) => document.nodes.some((node) => node.id === id));
|
|
335
|
+
if (bpmn) {
|
|
336
|
+
document = (0, callman_core_1.applyDiagramOps)(document, (0, callman_core_1.placeBpmnNodesOps)(document, ids), { now: now() }).doc;
|
|
337
|
+
layoutNote = `Placed ${ids.length} new node(s) after their predecessors.`;
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
document = (0, layout_js_1.placeNewNodes)(document, ids);
|
|
341
|
+
layoutNote = `Placed ${ids.length} new node(s) below the existing ones.`;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const updated = await ctx.api.updateDiagram(record.id, {
|
|
345
|
+
document,
|
|
346
|
+
...(typeof existing.meta.revision === "number" ? { baseRevision: existing.meta.revision } : {}),
|
|
347
|
+
});
|
|
348
|
+
return { updated, document, patched, layoutNote };
|
|
349
|
+
};
|
|
350
|
+
let outcome;
|
|
351
|
+
let retried = false;
|
|
352
|
+
try {
|
|
353
|
+
outcome = await attempt(resolved.item);
|
|
155
354
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
355
|
+
catch (error) {
|
|
356
|
+
if (!isConflict(error))
|
|
357
|
+
throw error;
|
|
358
|
+
retried = true;
|
|
359
|
+
outcome = await attempt(await ctx.api.getDiagram(resolved.item.id));
|
|
159
360
|
}
|
|
160
|
-
const updated = await ctx.api.updateDiagram(resolved.item.id, { document });
|
|
161
361
|
ctx.invalidate("diagrams");
|
|
362
|
+
const { updated, document, patched, layoutNote } = outcome;
|
|
162
363
|
return (0, shared_js_1.ok)({
|
|
163
364
|
diagram: { ...(0, summarize_js_1.diagramListView)(updated), ...(0, summarize_js_1.diagramGraphView)(document) },
|
|
164
365
|
applied: ops.length,
|
|
@@ -166,7 +367,8 @@ const registerDiagramTools = (server, ctx) => {
|
|
|
166
367
|
removedNodes: patched.removedNodeIds,
|
|
167
368
|
removedEdges: patched.removedEdgeIds,
|
|
168
369
|
...(patched.reroutedEdgeIds.length > 0 ? { reroutedEdges: patched.reroutedEdgeIds } : {}),
|
|
169
|
-
|
|
370
|
+
...(Object.keys(patched.idMap).length > 0 ? { idMap: patched.idMap } : {}),
|
|
371
|
+
warnings: [...patched.warnings, ...(retried ? ["The diagram had been saved by someone else meanwhile; your ops were re-applied on the newer version."] : [])],
|
|
170
372
|
...(layoutNote ? { layout: layoutNote } : {}),
|
|
171
373
|
resolved: [(0, resolve_js_1.resolvedSummary)(diagram, resolved)],
|
|
172
374
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "callman-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "MCP server for Callman: lets Claude Code, Claude Desktop, Cursor, Codex and Gemini CLI manage collections, requests, environments, test scenarios and system-design diagrams in your Callman workspace — cloud or on-prem.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"callman",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
65
|
-
"callman-core": "^1.
|
|
65
|
+
"callman-core": "^1.29.0",
|
|
66
66
|
"elkjs": "^0.12.0",
|
|
67
67
|
"js-yaml": "^4.1.0",
|
|
68
68
|
"zod": "^3.25.0"
|