@eventmodelers/cli 1.0.61 → 1.0.63

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "1.0.61",
3
+ "version": "1.0.63",
4
4
  "description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, Cratis, OpenCQRS, UmaDB, Kurrent, or modeling-only)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,6 +9,8 @@ description: Resolve eventmodelers connection config (token, boardId, baseUrl) f
9
9
 
10
10
  **This should happen once per session, not once per skill.** If `TOKEN`/`BOARD_ID`/`ORG_ID`/`BASE_URL` are already resolved and verified from earlier in the current session — including earlier in the *same turn*, e.g. one skill internally invoking a second skill (`add-next-slice` → `html-screen`) — every subsequent "invoke `connect`" instruction is satisfied immediately by reusing those values. Do not re-run Steps 0–4 below. Only re-run this skill from scratch when a value actually needs to change: a fresh `401`/`403`/access-denied response from some other call, a different `board_id` on this turn, or a new inline param that overrides what's already resolved.
11
11
 
12
+ **Subagents are a fresh session — hand them the resolved values.** When you spawn a subagent to do board work, put the already-resolved credentials inline in its prompt (`token=… board=… org=… baseUrl=…`). Its `connect` then satisfies everything at Step 0 and skips Steps 1–4 entirely: no config-file walk, no MCP re-registration, no verify call. Spawning three subagents without passing them down means paying the whole resolve-and-verify round three more times for values you already have.
13
+
12
14
  This skill also registers the **eventmodelers MCP server** for the project (Step 3.5) so other skills can call MCP tools (`mcp__eventmodelers__*`) instead of raw curl. MCP is the preferred transport; curl remains a fallback for hosts without MCP support, or for the one or two endpoints (documented in `learn-eventmodelers-api`) the MCP server doesn't expose.
13
15
 
14
16
  ---
@@ -214,6 +216,23 @@ curl -s -o /dev/null -w "%{http_code}" \
214
216
 
215
217
  ---
216
218
 
219
+ ## Step 5 — Read the board once
220
+
221
+ Connecting is also where the session's read discipline starts. Every skill that runs after this one shares the same board, so **fetch it once and index it in memory** instead of re-deriving it per step:
222
+
223
+ - **Orientation** (what is where, how is it wired) — `get_board_outline { boardId, chapterId }`. One compact call per chapter: per-column node lists plus a flat edge list, no HTML pages or field bodies.
224
+ - **Working set** (you need `meta.fields`, examples, descriptions) — `get_nodes { boardId, chapterId }`. One call returns every node in the chapter with full `meta`, plus `node.position` and `node.parentId`. A whole 70-node board is well under 100 KB unscoped; scoped to a chapter it is smaller still.
225
+ - **A known, scattered subset** — `get_nodes { boardId, nodeIds: [...] }`. One call, not one per id.
226
+ - **Just names/types** — add `projection: "line"`. **Just a chapter's grid** — `get_node { nodeId: <chapterId>, projection: "cells" }`. **Just one node's wiring** — `get_node { nodeId, projection: "edges" }`.
227
+
228
+ `get_node` without a projection is for **one** node you did not already load — most often re-reading a node right after writing it. A step that issues it in a loop over nodes that were already in a list response is doing the same fetch N times; collapse it to the single chapter-scoped read above.
229
+
230
+ The same discipline applies to writes: `submit_node_events` takes `events[]`, so a multi-node edit is **one** call carrying every event, not one call per node. Pass `compact: true` when you don't need the per-node hash map back.
231
+
232
+ Where per-node calls genuinely can't be avoided, issue them together in one message so they run concurrently rather than in sequence.
233
+
234
+ ---
235
+
217
236
  ## Config file format
218
237
 
219
238
  `.eventmodelers/config.json`:
@@ -48,6 +48,7 @@ Server name: `eventmodelers`. Every tool takes `boardId` explicitly; none need `
48
48
  | `get_slice_data` | `boardId`, `contextName?`, `contextId?`, `sliceId?` | Full element graph for slices in a context | §8 `GET /slicedata` |
49
49
  | `get_spec_info` | `boardId`, `timelineId`, `elementTypes?` | EVENT/COMMAND/READMODEL nodes valid in GWT steps. Pass `elementTypes` (subset of `EVENT`/`COMMAND`/`READMODEL`) to avoid pulling the full element list when only one or two types are needed — filtered server-side, not just after a full fetch | §6 `GET .../spec-info` |
50
50
  | `get_board_outline` | `boardId`, `chapterId` | One chapter's structure, compact: per-column node lists (`{id, type, title, lane}`) + a flat edge list, no HTML pages / field bodies / meta. The cheap "what is where and how is it wired" read — prefer over `get_nodes` (no projection) for orientation checks | — (MCP-only convenience) |
51
+ | `get_connected_nodes` | `boardId`, `nodeId`, `chapterId?`, `direction?` (`inbound`/`outbound`/`both`), `depth?`, `types?`, `includeFields?` | Neighbours of **one** node — what feeds it and what it feeds. Answers from a single anchor, unlike `get_attribute_chain` (which needs both ends of the chain as cell names up front). `depth` follows a whole chain; `types` filters the result only, never the traversal. Each neighbour carries `via`: `"edge"` for a real connection, `"layout"` when the node has none in that direction and the neighbour was inferred from the grid using auto-connect's own window (own column + adjacent one, forward-only pairs). Real edges always win. The `layout` fallback is what makes hand-built/imported chapters — which routinely carry **zero** edges — readable instead of falsely empty | — (MCP-only convenience) |
51
52
  | `validate_model` | `boardId`, `chapterId`, `checks?[]` | Server-side Event Modeling structural checklist over one chapter — compact `findings` only. Checks: unplaced nodes, backward arrows (with the todo-list `EVENT→READMODEL` exception), zero/multi-issuer commands, sourceless read models, two-screens-in-a-column, missing scenarios. Replaces the manual per-type `get_nodes` + `get_node projection=edges` validation pass | — (MCP-only convenience) |
52
53
  | `add_scenario` | `boardId`, `timelineId`, `columnId`, `scenarios[]`, `compact?` | Append GWT scenario(s) to a column's spec node. `compact: true` returns `{specNodeId, added, scenarioCount, isNewNode}` instead of echoing every scenario back | §6 `POST .../scenarios` |
53
54
  | `add_storyline` | `boardId`, `timelineId`, `columnId`, `storylines[]`, `compact?` | Append storyline(s) (ordered, branchable beats over existing elements) to a column's spec node. Use whenever `eventmodeling-elaborating-scenarios`'s GWT-vs-storyline decision rule calls for one (e.g. a todo list's open→close lifecycle) — not only when a user explicitly names "storyline"; that skill's own per-read-model judgment is the trigger, this catalog entry isn't a stricter gate on top of it. `compact: true` suppresses the full storyline echo | §6 `POST .../storylines` |
@@ -59,24 +59,40 @@ Each node may have an `edges` array:
59
59
  ```json
60
60
  edges: [{ id, source, target, sourceHandle, targetHandle }]
61
61
  ```
62
- An **inbound** edge is one where `edge.target === currentNode.id`. For each inbound edge, fetch the source node:
62
+ An **inbound** edge is one where `edge.target === currentNode.id`.
63
63
 
64
- **Prefer MCP:**
64
+ **Prefer `get_connected_nodes`** — it does this entire walk server-side, in one call, from the target alone:
65
65
  ```
66
- mcp__eventmodelers__get_node { "boardId": "$BOARD_ID", "nodeId": "$EDGE_SOURCE_ID" }
66
+ mcp__eventmodelers__get_connected_nodes {
67
+ "boardId": "$BOARD_ID",
68
+ "nodeId": "<TARGET_NODE.id>",
69
+ "direction": "inbound",
70
+ "depth": 10,
71
+ "includeFields": true
72
+ }
67
73
  ```
74
+ The result is already ordered by `hops` (nearest first) and carries each node's `cellName` and `fields[]`, which is everything Step 4 needs — so this replaces both the edge walk and the per-node field fetch. Stop at the node matching `SOURCE_NODE`; anything beyond it is outside the requested chain.
75
+
76
+ Each neighbour reports `via`. `"edge"` means a real connection. `"layout"` means that node had no edge in that direction and the neighbour was inferred from the grid — correct, but worth a line in your Step 5 report so the user knows the chain was read off the layout rather than off wiring. A `chapterHasEdges: false` in the summary means the whole chapter is unwired.
68
77
 
69
- **Fallback (no MCP):** see `references/api-fallback.md` "3a Use Node Edges".
78
+ Reach for a single-node fetch only for a node genuinely outside the anchor's chapter:
79
+ ```
80
+ mcp__eventmodelers__get_node { "boardId": "$BOARD_ID", "nodeId": "$EDGE_SOURCE_ID", "projection": "edges" }
81
+ ```
82
+
83
+ **Fallback (no MCP):** see `references/api-fallback.md` — "3a — Use Node Edges". Resolve the walk from **one** chapter-scoped read rather than a `get_node` per hop: `GET .../nodes?chapterId=` returns every node with full `meta`, `node.position` and `node.parentId` in a single response — index it and walk it locally.
70
84
 
71
85
  ### 3b — Column-based fallback (if no edges)
72
- If a node has no edges, use the chapter cell layout (already in memory) to find plausible inbound neighbours:
86
+ Only needed without MCP; `get_connected_nodes` already applies this rule itself and labels the result `via: "layout"`.
87
+
88
+ Hand-built or imported chapters frequently have **no edges at all** — every node comes back with `edges: []` and `get_board_outline`'s edge list is empty. That is not an error and not a reason to stop: in that case grid geometry *is* the chain. Use the chapter cell layout (already in memory from 3a) to find inbound neighbours:
73
89
 
74
90
  In a standard event modeling layout:
75
91
  - **READMODEL** in the interaction row → its inbound EVENT is in the swimlane row of the **same column**
76
92
  - **EVENT** in the swimlane row → its inbound COMMAND is in the interaction row of the **same column**
77
93
  - **COMMAND** in the interaction row → its inbound READMODEL is in the swimlane row of the **previous column**
78
94
 
79
- Fetch candidate nodes by their cellId (live), skip any that don't exist or are already in the chain.
95
+ Resolve candidates from the chapter read you already have — do **not** issue a `?cellId=` lookup per candidate. If the chapter's `meta.timelineData.cells` is sparse or absent, derive each node's (column, row) from `node.position.x/y` bucketed against `meta.timelineData.columns[].width` and `rows[].height`; that mapping is enough to apply the three rules above. Skip candidates that don't exist or are already in the chain.
80
96
 
81
97
  ### 3c — Stop condition
82
98
  Stop traversal when:
@@ -86,11 +102,11 @@ Stop traversal when:
86
102
 
87
103
  ---
88
104
 
89
- ## Step 4 — Apply the change to each node in the chain
105
+ ## Step 4 — Apply the change to the whole chain in one write
90
106
 
91
- Process nodes in order: TARGET_NODE first, then backwards to SOURCE_NODE.
107
+ Compute the updated `fields` array for **every** node in the chain first, in order (TARGET_NODE first, then backwards to SOURCE_NODE), then submit them all in a **single** `submit_node_events` call. Do not write one node, check it, and move to the next — the chain is one logical edit and `events[]` takes the whole batch.
92
108
 
93
- For each node:
109
+ For each node, compute (don't write yet):
94
110
 
95
111
  ### If operation is `add`:
96
112
  - Check if a field with that name already exists in `meta.fields` — if so, skip this node (log it).
@@ -114,27 +130,35 @@ For each node:
114
130
  - Find the field where `name === oldName` (case-insensitive). If not found in this node, skip it (log it).
115
131
  - Update only the `name` property to `newName`. Leave all other field properties unchanged.
116
132
 
117
- Build the updated `fields` array and send a `node:changed` event.
133
+ Collect one `node:changed` event per affected node, then send them together.
118
134
 
119
- **Prefer MCP** — same event body, passed as a tool arg instead of `-d`:
135
+ **Prefer MCP** — one call for the entire chain, one event per node in `events[]`:
120
136
  ```
121
137
  mcp__eventmodelers__submit_node_events {
122
138
  "boardId": "$BOARD_ID",
123
- "events": [{
124
- "id": "<uuid>",
125
- "eventType": "node:changed",
126
- "nodeId": "<NODE_ID>",
127
- "boardId": "$BOARD_ID",
128
- "timestamp": <epoch-ms>,
129
- "changedAttributes": ["meta.fields"],
130
- "meta": { "fields": "<updated_fields_array>" }
131
- }]
139
+ "compact": true,
140
+ "events": [
141
+ {
142
+ "id": "<uuid>",
143
+ "eventType": "node:changed",
144
+ "nodeId": "<NODE_ID_1>",
145
+ "boardId": "$BOARD_ID",
146
+ "timestamp": <epoch-ms>,
147
+ "changedAttributes": ["meta.fields"],
148
+ "meta": { "fields": "<updated_fields_array_1>" }
149
+ },
150
+ { "…one more event per remaining node in the chain…" }
151
+ ]
132
152
  }
133
153
  ```
134
154
 
135
- **Fallback (no MCP):** see `references/api-fallback.md` "Step 4 Apply the Change to Each Node in the Chain".
155
+ Nodes that are skipped (field already exists / field not found) simply contribute no event don't send a no-op change for them.
156
+
157
+ **Fallback (no MCP):** see `references/api-fallback.md` — "Step 4 — Apply the Change to Each Node in the Chain". The REST endpoint takes the same `NodeChangeEvent[]` body, so it batches identically — one POST, not one per node.
158
+
159
+ Verify the response is HTTP 200. If the batch fails, report the error and stop; nothing was partially applied from your side, so re-run after fixing the cause rather than retrying node by node.
136
160
 
137
- Verify HTTP 200 before proceeding to the next node. If a node fails, report the error and stop.
161
+ If you also need to verify the result, re-read the whole chain in one call — `get_nodes { "boardId": "$BOARD_ID", "nodeIds": [<every node id you just wrote>] }` — never one `get_node` per node.
138
162
 
139
163
  ---
140
164
 
@@ -25,6 +25,18 @@ From `$ARGUMENTS`, extract:
25
25
 
26
26
  ---
27
27
 
28
+ ## Step 1b — Many targets at once
29
+
30
+ `target` is singular, but the common real request is "fill in the examples across this chapter". When you have more than one target, **do not run this skill once per element and do not fetch context a node at a time.**
31
+
32
+ 1. Read the chapter **once**: `mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "chapterId": "$CHAPTER_ID" }`. That single response carries every element's `meta.fields` *including the `example` values already filled in* — which is exactly the canonical-value pool Step 3c asks for, for the whole chapter, in one call.
33
+ 2. Pick the canonical value per field name from that pool (e.g. `customerId: "cust-123"`, `email: "jane@example.com"`) before writing anything, so every element ends up consistent.
34
+ 3. Then fill the targets. `add_field_examples` is still the preferred writer, one call per target — but it is the *only* per-target call you should be making. If you are generating the values yourself instead, batch every element's update into a single `submit_node_events { events: [...] }` call.
35
+
36
+ A run that opens each element with its own `get_node` to "read existing examples first" is doing step 1 N times over.
37
+
38
+ ---
39
+
28
40
  ## Step 2 — Resolve and generate examples (prefer MCP)
29
41
 
30
42
  `add_field_examples` is a whole-algorithm convenience tool: it resolves the node, loads linked neighbours for cross-element consistency, fills any empty field examples, and writes the result back — collapsing the entire "find node → find linked nodes → build examples → submit_node_events" flow (Steps 2–5 below) into one call. Call it with whichever identifier matches `target`:
@@ -7,6 +7,8 @@ description: Business analyst exploration of an event model board. Reads all sli
7
7
 
8
8
  > **Before doing anything else**, invoke the `connect` skill — if not already connected — to resolve `TOKEN`, `BOARD_ID`, and `BASE_URL`. Do not proceed until the connect skill has completed.
9
9
 
10
+ Prefer `mcp__eventmodelers__*` tools when available (registered by the `connect` skill) — the REST calls below are the fallback for sessions without MCP connected.
11
+
10
12
  This step applies the shared element rules in **`eventmodeling-core-rules`** — read it once per session if you haven't already; it defines what a COMMAND/EVENT/READMODEL/SCREEN/AUTOMATION is, the anti-patterns to reject, and the four Structural Shapes (Category I below), so this step doesn't restate them.
11
13
 
12
14
  You are a **sharp business analyst** reviewing an event model. You don't know the domain yet — you're seeing it fresh. Your job is to read the model, understand the intended flows, and ask the hard questions that developers and domain experts tend to overlook because they're too close to the problem.
@@ -26,17 +28,27 @@ If the user already provided context name in their message, use it directly.
26
28
 
27
29
  ## Step 2 — Load the model
28
30
 
29
- **A. Get all slices:**
31
+ **Prefer MCP — one call loads the whole context.** `sliceId` is optional; omitting it returns the full element graph for *every* slice in the context at once. Do **not** list slices and then fetch them one by one:
32
+
30
33
  ```
31
- GET /api/org/{orgId}/boards/{boardId}/slicedata/slices
34
+ mcp__eventmodelers__get_slice_data {
35
+ "boardId": "$BOARD_ID",
36
+ "contextName": "<context name from Step 1>",
37
+ "format": "textual"
38
+ }
32
39
  ```
33
- This returns `{ slices: [{ id, title, status }] }`.
34
40
 
35
- **B. For each slice, load its full data:**
41
+ `format` matters here because this skill reads the entire model before it writes anything. `"textual"` is a compact markdown dump and `"toon"` a token-efficient tabular encoding — both carry the same graph as `"json"` in a fraction of the tokens. Use `"json"` only when you need to read exact `x`/`y`/`width`/`height` values for the drawings in Step 4.2.
42
+
43
+ Each response contains screens, commands, events, read models, specs, scenarios, actors, automations, and the edges between them.
44
+
45
+ `mcp__eventmodelers__list_slices { "boardId": "$BOARD_ID" }` is only needed when you must scope to **one** slice and don't know its id, or to read slice statuses — not as a precursor to loading the model.
46
+
47
+ **Fallback (no MCP):**
36
48
  ```
37
- GET /api/org/{orgId}/boards/{boardId}/slicedata?contextName={contextName}&sliceId={sliceId}
49
+ GET /api/org/{orgId}/boards/{boardId}/slicedata?contextName={contextName}
38
50
  ```
39
- Load all slices in parallel. Each response contains the full element graph for that slice: screens, commands, events, read models, specs, scenarios, actors, automations.
51
+ Same shape the whole context in one request. `&sliceId={sliceId}` narrows it to one slice; `GET .../slicedata/slices` lists `{ slices: [{ id, title, status }] }`.
40
52
 
41
53
  Keep track of:
42
54
  - All slice titles and their element types
@@ -132,10 +144,30 @@ Only post questions that are **genuinely unclear or missing** — don't post obs
132
144
 
133
145
  ### 4.2 Drawings (every relational or clustered finding, always)
134
146
 
135
- Use `POST /api/org/{orgId}/boards/{boardId}/drawing/draw` (auth headers same as every other call — `x-token`, `x-board-id`, `x-user-id: wdyt`). There are two kinds no text-callout kind; a drawing never carries the question itself, only the shape of the concern:
147
+ **Prefer MCP:** `mcp__eventmodelers__create_drawing` — one call per drawing, no auth headers needed.
148
+
149
+ **Fallback (no MCP):** `POST /api/org/{orgId}/boards/{boardId}/drawing/draw` (auth headers same as every other call — `x-token`, `x-board-id`, `x-user-id: wdyt`). Same fields as the tool args below.
136
150
 
137
- - **Arrow** (`kind: "path"`, `arrowEnd: true`) the concern is about a missing or unclear relationship *between two elements* (e.g. "does this event actually reach this automation?"). Draw a straight line from one element's position to the other's. `path` is `M 0 0 L <dx> <dy>` in the box's own local coordinates; `x`/`y`/`width`/`height` describe that box in canvas space (so `width`/`height` = the delta between the two elements' positions). Get element positions from the slice data already loaded in Step 2 (or `GET .../nodes/{nodeId}` if not present).
138
- - **Group loop** (`kind: "rect"`, drawn around a computed bounding box) — the concern spans a *cluster* of elements together (e.g. "this whole flow assumes nothing ever fails"). There's no dedicated group endpoint — union the elements' own `x`/`y`/`width`/`height` (plus some padding) yourself and draw one `rect` around that box via `.../drawing/draw`. This is a visual grouping only — unrelated to the `MODEL_CONTEXT` node type; never touch a `modelContext` field to satisfy this.
151
+ There are two kindsno text-callout kind; a drawing never carries the question itself, only the shape of the concern:
152
+
153
+ - **Arrow** (`kind: "path"`, `arrowEnd: true`) — the concern is about a missing or unclear relationship *between two elements* (e.g. "does this event actually reach this automation?"). Draw a straight line from one element's position to the other's. `path` is `M 0 0 L <dx> <dy>` in the box's own local coordinates; `x`/`y`/`width`/`height` describe that box in canvas space (so `width`/`height` = the delta between the two elements' positions).
154
+ ```
155
+ mcp__eventmodelers__create_drawing {
156
+ "boardId": "$BOARD_ID", "kind": "path",
157
+ "x": <sourceX>, "y": <sourceY>, "width": <dx>, "height": <dy>,
158
+ "path": "M 0 0 L <dx> <dy>", "arrowEnd": true
159
+ }
160
+ ```
161
+ Get element positions from the slice data already loaded in Step 2. If a position is missing, fetch the nodes you need in **one** call — `mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "nodeIds": [<the ids>] }` — not `get_node` per element.
162
+ - **Group loop** (`kind: "rect"`, drawn around a computed bounding box) — the concern spans a *cluster* of elements together (e.g. "this whole flow assumes nothing ever fails"). There's no dedicated group endpoint — union the elements' own `x`/`y`/`width`/`height` (plus some padding) yourself and draw one `rect` around that box:
163
+ ```
164
+ mcp__eventmodelers__create_drawing {
165
+ "boardId": "$BOARD_ID", "kind": "rect",
166
+ "x": <minX - pad>, "y": <minY - pad>,
167
+ "width": <maxX - minX + 2*pad>, "height": <maxY - minY + 2*pad>
168
+ }
169
+ ```
170
+ This is a visual grouping only — unrelated to the `MODEL_CONTEXT` node type; never touch a `modelContext` field to satisfy this.
139
171
 
140
172
  Every arrow/group loop is paired with a comment on the relevant node(s) from 4.1 — the drawing makes the concern visible at a glance on the canvas itself, the comment carries the actual worded question. Post both; neither replaces the other.
141
173