@eventmodelers/cli 1.0.61 → 1.0.62

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.62",
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`:
@@ -59,24 +59,26 @@ 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
+ Resolve the whole walk from **one** chapter-scoped read rather than a `get_node` per hop — `get_board_outline { "boardId": "$BOARD_ID", "chapterId": "$TIMELINE_ID" }` returns every node in the chapter (`{id, type, title, lane}` per column) *plus* a flat edge list, which is exactly what the traversal needs. Index it in memory and walk it locally; you only need the per-node `meta.fields` (Step 4), which one `get_nodes { "boardId": "$BOARD_ID", "chapterId": "$TIMELINE_ID" }` returns for the whole chapter in a single call.
65
+
66
+ Reach for a single-node fetch only for a node genuinely outside that chapter:
65
67
  ```
66
- mcp__eventmodelers__get_node { "boardId": "$BOARD_ID", "nodeId": "$EDGE_SOURCE_ID" }
68
+ mcp__eventmodelers__get_node { "boardId": "$BOARD_ID", "nodeId": "$EDGE_SOURCE_ID", "projection": "edges" }
67
69
  ```
68
70
 
69
71
  **Fallback (no MCP):** see `references/api-fallback.md` — "3a — Use Node Edges".
70
72
 
71
73
  ### 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:
74
+ 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
75
 
74
76
  In a standard event modeling layout:
75
77
  - **READMODEL** in the interaction row → its inbound EVENT is in the swimlane row of the **same column**
76
78
  - **EVENT** in the swimlane row → its inbound COMMAND is in the interaction row of the **same column**
77
79
  - **COMMAND** in the interaction row → its inbound READMODEL is in the swimlane row of the **previous column**
78
80
 
79
- Fetch candidate nodes by their cellId (live), skip any that don't exist or are already in the chain.
81
+ 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
82
 
81
83
  ### 3c — Stop condition
82
84
  Stop traversal when:
@@ -86,11 +88,11 @@ Stop traversal when:
86
88
 
87
89
  ---
88
90
 
89
- ## Step 4 — Apply the change to each node in the chain
91
+ ## Step 4 — Apply the change to the whole chain in one write
90
92
 
91
- Process nodes in order: TARGET_NODE first, then backwards to SOURCE_NODE.
93
+ 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
94
 
93
- For each node:
95
+ For each node, compute (don't write yet):
94
96
 
95
97
  ### If operation is `add`:
96
98
  - Check if a field with that name already exists in `meta.fields` — if so, skip this node (log it).
@@ -114,27 +116,35 @@ For each node:
114
116
  - Find the field where `name === oldName` (case-insensitive). If not found in this node, skip it (log it).
115
117
  - Update only the `name` property to `newName`. Leave all other field properties unchanged.
116
118
 
117
- Build the updated `fields` array and send a `node:changed` event.
119
+ Collect one `node:changed` event per affected node, then send them together.
118
120
 
119
- **Prefer MCP** — same event body, passed as a tool arg instead of `-d`:
121
+ **Prefer MCP** — one call for the entire chain, one event per node in `events[]`:
120
122
  ```
121
123
  mcp__eventmodelers__submit_node_events {
122
124
  "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
- }]
125
+ "compact": true,
126
+ "events": [
127
+ {
128
+ "id": "<uuid>",
129
+ "eventType": "node:changed",
130
+ "nodeId": "<NODE_ID_1>",
131
+ "boardId": "$BOARD_ID",
132
+ "timestamp": <epoch-ms>,
133
+ "changedAttributes": ["meta.fields"],
134
+ "meta": { "fields": "<updated_fields_array_1>" }
135
+ },
136
+ { "…one more event per remaining node in the chain…" }
137
+ ]
132
138
  }
133
139
  ```
134
140
 
135
- **Fallback (no MCP):** see `references/api-fallback.md` "Step 4 Apply the Change to Each Node in the Chain".
141
+ Nodes that are skipped (field already exists / field not found) simply contribute no event don't send a no-op change for them.
142
+
143
+ **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.
144
+
145
+ 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
146
 
137
- Verify HTTP 200 before proceeding to the next node. If a node fails, report the error and stop.
147
+ 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
148
 
139
149
  ---
140
150
 
@@ -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`: