@eventmodelers/cli 1.0.93 → 1.0.95

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.93",
3
+ "version": "1.0.95",
4
4
  "description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, OpenCQRS, UmaDB, Kurrent, or modeling-only)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,7 +9,7 @@ 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=…`, plus `agent=…` when you have an `AGENT_ID` — a subagent's writes are still this agent's writes, and it cannot read your environment). 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.
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=…`, plus `agent=…` when you have an `AGENT_ID` — a subagent's writes are still this agent's writes, and it cannot read your environment — and `session=…` when you have a `CHAT_SESSION_ID`, so its writes are traced back to the same chat). 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
13
 
14
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.
15
15
 
@@ -26,12 +26,14 @@ After running, the following variables are available for the rest of the session
26
26
  | `ORG_ID` | — | Organization UUID (used in all board-scoped URLs) |
27
27
  | `BASE_URL` | — | Base URL, e.g. `http://localhost:3000` |
28
28
  | `AGENT_ID` | `x-agent-id` | This agent process's own id, when running as one (Step 0.5). Optional — skip the header when there is no value. |
29
+ | `CHAT_SESSION_ID` | `x-chat-session-id` | The chat session the current work was made for — a prompt turn's `origin_session_id`, or an inline `session=`. Optional and **per turn**: unlike the values above it changes from one prompt to the next, is never persisted, and is absent for work that did not come from a chat — skip the header then. |
29
30
 
30
31
  Every curl-fallback call in every skill must include these headers:
31
32
  ```
32
33
  x-token: <TOKEN>
33
34
  x-user-id: <skill-name> ← set by each skill individually
34
35
  x-agent-id: <AGENT_ID> ← only when AGENT_ID resolved; omit the line entirely otherwise
36
+ x-chat-session-id: <CHAT_SESSION_ID> ← only on writes for work that came from a chat; omit otherwise
35
37
  ```
36
38
 
37
39
  `x-agent-id` is what makes the board say *which* agent did something: the platform stamps it on
@@ -39,9 +41,15 @@ every change the call writes, so the canvas shows this agent by name (rather tha
39
41
  robot standing in for every agent at once), and a prompt a user addressed to one preferred agent
40
42
  is only handed to the agent that claims it with that id.
41
43
 
44
+ `x-chat-session-id` is what ties a change to the conversation that asked for it: the platform stores
45
+ it on every board event the call writes, so the board's history can show which chat led to each
46
+ change. Only an active session of the call's own board is stored — one that doesn't exist, was
47
+ deleted or belongs to another board is silently dropped and the write goes ahead without it, so
48
+ there is nothing to handle on your side.
49
+
42
50
  All board-scoped URLs follow the pattern: `<BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/...`
43
51
 
44
- When calling MCP tools instead, no `x-*` headers are needed per call — the MCP server resolves `ORG_ID` from `TOKEN` itself and every tool takes `boardId` as an explicit argument. (`x-agent-id` rides along with the registered server config from Step 3.5, so MCP writes are attributed too.) See `learn-eventmodelers-api` for the full tool catalog.
52
+ When calling MCP tools instead, no `x-*` headers are needed per call — the MCP server resolves `ORG_ID` from `TOKEN` itself and every tool takes `boardId` as an explicit argument. (`x-agent-id` rides along with the registered server config from Step 3.5, so MCP writes are attributed too.) The chat session can't ride along that way — the server config is fixed for the whole session while `CHAT_SESSION_ID` changes per prompt — so every MCP **write** tool takes an optional `sessionId` argument instead: pass `CHAT_SESSION_ID` there on every write call while you have one, same rules as the header. See `learn-eventmodelers-api` for the full tool catalog.
45
53
 
46
54
  ---
47
55
 
@@ -56,10 +64,11 @@ Before reading the config file, scan the prompt/arguments that invoked this skil
56
64
  | `org=<uuid>` | `org=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` |
57
65
  | `baseUrl=<url>` | `baseUrl=http://localhost:3000` |
58
66
  | `agent=<uuid>` | `agent=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` (the agent id a parent agent hands a subagent, so the subagent's board writes are attributed to the same agent) |
67
+ | `session=<uuid>` | `session=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` (the chat session a parent agent hands a subagent, as `CHAT_SESSION_ID`, so the subagent's writes are traced back to the same chat) |
59
68
 
60
69
  If an inline `board=<uuid>` is found, use it as `BOARD_ID` — **it takes priority over the config file**. Same for `token`, `org`, `baseUrl`, and `agent` (as `AGENT_ID`, which then makes Step 0.5 a no-op). Record which values came from inline params so they are not overwritten in Step 3.
61
70
 
62
- (`agent=<uuid>` is not one of the four required values — it is optional, and its absence never makes this skill ask anything.)
71
+ (`agent=<uuid>` and `session=<uuid>` are not among the four required values — both are optional, and their absence never makes this skill ask anything. Never persist `session=`: it belongs to the one piece of work it was handed with.)
63
72
 
64
73
  **`token=$EVENTMODELERS_TOKEN` is an inline token too.** The build and modeling loops pass the env var's *name*, not its value, so the secret never sits in the prompt. Treat it exactly like a literal inline token: `TOKEN` is `$EVENTMODELERS_TOKEN` (it counts toward "all four inline" below). Keep using the reference, never its value — write curl headers as `-H "x-token: $EVENTMODELERS_TOKEN"` (double quotes, so the shell expands it), don't `echo` or print it, and hand it to a subagent as `token=$EVENTMODELERS_TOKEN` (it inherits the same environment). MCP calls need nothing extra — `.mcp.json` already reads the same variable. Never write the reference into a file either: if a later step would persist `TOKEN` (Step 3's `config.json`, Step 3.5's `settings.local.json`), leave the token out — the process that launched you already set the variable, and a file holding the literal `$EVENTMODELERS_TOKEN` text breaks auth for the next session.
65
74
 
@@ -19,6 +19,8 @@ Server name: `eventmodelers`. Every tool takes `boardId` explicitly; none need `
19
19
 
20
20
  Write tools take their items as an array (`nodeIds[]`, `elements[]`, `connections[]`, …) and there are no separate plural tools — put several items in one call rather than looping. The array form always answers `{results: [...]}`, one entry per item in request order, even for a single item. The older flat form (e.g. `delete_node { nodeId }`) still works and answers with just that one result, but use the array form in new instructions.
21
21
 
22
+ **Every write tool also takes an optional `sessionId`** — the chat session the change is made for (`CHAT_SESSION_ID`: a prompt turn's `origin_session_id`). Pass it on every write while working a prompt that came from a chat; the server stores it on each board event the call writes (`session_id`), so the board's history traces the change back to that conversation. Omit it otherwise. Only an active session of the same board is stored — anything else is dropped and the write goes ahead without it. It is not in the per-tool Args below, to keep them short.
23
+
22
24
  **Deprecated tools — don't call them:** `drop_node_to_cell` and `move_node_in_timeline` → `place_element` with `action: "move"` (`nodeId` + `cellName`); `clear_cell` → `place_element` with `action: "clear"` (`nodeId`, or `cellName`). They still work so older kits don't break, but they will be removed.
23
25
 
24
26
  | Tool | Args | Purpose | REST equivalent |
@@ -30,7 +32,7 @@ Write tools take their items as an array (`nodeIds[]`, `elements[]`, `connection
30
32
  | `get_board_events` | `boardId`, `since?`, `seq?` | A page of at most 300 board events, oldest first, as `{events, pageSize, hasMore}`. Not the whole log: while `hasMore` is true, page on by passing the last event's `seq` as `since`. `seq` returns only the event of exactly that seq | §1 `GET .../events` |
31
33
  | `submit_node_events` | `boardId`, `events[]`, `autoConnect?`, `compact?` | Create/update nodes (raw `NodeChangeEvent`/edge events). Every event property is described on the tool's own `events[]` schema — read that rather than this skill when all you need is the event shape. An event needs its own `id` (required — a fresh uuid per event, never the node id) and its `eventType`, plus the keys of its kind (`nodeId`/`meta`/`node`, or `edgeId`/`source`/`target`). The `id` keys the returned `hashes` map and makes a resubmit idempotent — the same event sent twice under one id writes a single board event. Don't send `boardId` or `timestamp` per event — the server takes the board from the call and stamps the time itself, and ignores both if sent. `autoConnect: false` places freshly-created nodes without wiring them to their own/previous-column neighbors (avoids a stray nearest-left edge); `compact: true` returns `{persisted: <count>}` instead of the per-node hash map | §3 `POST .../nodes/events` |
32
34
  | `delete_node` | `boardId`, `nodeIds[]` | Delete nodes, applied in order — one failure doesn't stop the rest; answers `{results}`. Deleting a chapter (timeline) cascades — every node placed in one of its cells, plus any node parented to it (e.g. SLICE_BORDER), is deleted too, along with all their edges | (via `node:deleted` event, §3) |
33
- | `create_drawing` | `boardId`, `drawings[]` (each `kind`, `x`, `y`, `width`, `height`, ...) | Freehand canvas annotation (path/rect/text/sticky) — never placed in a cell. An annotation is rarely one stroke: an arrow plus its label is one call with several `drawings` entries, applied in order, not several calls | — (REST `POST .../drawing/draw` accepts a single drawing or an array) |
35
+ | `create_drawing` | `boardId`, `drawings[]` (each `kind`, `x`, `y`, `width`, `height`, ...) | Freehand canvas annotation (path/rect/text/sticky) — never placed in a cell. An annotation is rarely one stroke: an arrow plus its label is one call with several `drawings` entries, applied in order, not several calls. **"Hotspot"** means a red feedback note: `kind: "sticky"` with `fill: "#ef4444"` (a sticky with no `fill` renders yellow), `content` = the problem/question, placed right next to the element it concerns | — (REST `POST .../drawing/draw` accepts a single drawing or an array) |
34
36
  | `create_chapter` | `boardId`, `x?`, `y?`, `title?`, `columns?`, `lanes?: [{type, label?, height?}]` | Create a timeline. Omitting `x`/`y` auto-stacks it below the lowest existing chapter (by its *actual current* row-height total, not the height it was created with — safe even after `add_lane` growth), plus a fixed margin. Pass `title` to name it, `columns` for a known column count, and `lanes` to create named lanes (or several of one type, e.g. one swimlane per context) — none of the three needs a follow-up call, and the lanes are sorted into the required order automatically. The response carries every `columnId` and lane id, so the chapter needn't be read back before placing into it | §2 `POST .../chapters` |
35
37
  | `get_chapter_bounds` | `boardId` | Absolute canvas bounding box `{id, title, x, y, width, height}` of every chapter on the board — width/height derived from each chapter's current row/column layout, not a guessed default. Use before picking explicit `x`/`y` for `create_chapter` (e.g. placing below the chapter with the largest `y + height`) to avoid overlapping one that grew since it was created | §2 `GET .../chapters/bounds` |
36
38
  | `add_column` | `boardId`, `timelineId`, `index?`, `beforeNodeId?`, `afterNodeId?`, `count?` | Add one or more columns in one call. `count` inserts that many contiguously starting at the insertion point (default 1). Position with at most one of `index` (0-based), `beforeNodeId`, or `afterNodeId` (resolves the index from where that already-placed node currently sits) — omit all three to append | §2 `POST .../timelines/:id/columns` |
@@ -107,6 +109,7 @@ Two cheap projected calls (e.g. `outline` + `specs`) beat one full call when the
107
109
  | `Authorization` | Some routes | Supabase JWT bearer token |
108
110
  | `x-user-id` | Node operations | User identifier |
109
111
  | `x-agent-id` | Optional | The agent process making the call — the uuid it also sends as `agent_id` on its heartbeat (`$EVENTMODELERS_AGENT_ID`, resolved by the `connect` skill). Stored on the `board_events` rows a write produces and broadcast with them, so the board shows *which* agent touched an element instead of one anonymous agent for all of them, and it decides who may claim a prompt addressed to a preferred agent. Send it on every call; a value that isn't uuid-shaped is ignored. |
112
+ | `x-chat-session-id` | Optional | The chat session the change is made for (`CHAT_SESSION_ID` — a prompt turn's `origin_session_id`). Send it on every **write** under `/api/org/:orgId/boards/:boardId/...` while working a prompt that came from a chat; omit it otherwise. Stored as `session_id` on every board event the write produces, so the board's history traces the change back to that conversation. Agents only — ignored on a browser login. Only an active session of the call's own board is stored; anything else (not a uuid, unknown, deleted, another board's) is dropped and the write goes ahead without it. MCP tools take it as their `sessionId` argument instead. |
110
113
  | `x-causation-id` | Optional | Event causation tracing |
111
114
  | `x-correlation-id` | Optional | Correlation tracing |
112
115
 
@@ -381,7 +384,7 @@ The node's content lives in `meta.description` (a plain string of markdown sourc
381
384
 
382
385
  All node endpoints require header: `x-user-id`
383
386
 
384
- Also send `x-agent-id` (`$EVENTMODELERS_AGENT_ID`) on every write — that is what attributes the change to this agent by name on the board. See **Authentication & Headers** above.
387
+ Also send `x-agent-id` (`$EVENTMODELERS_AGENT_ID`) on every write — that is what attributes the change to this agent by name on the board — and, while working a prompt that came from a chat, `x-chat-session-id` (`CHAT_SESSION_ID`), which ties the change to that conversation. See **Authentication & Headers** above.
385
388
 
386
389
  ### POST `/api/org/:orgId/boards/:boardId/nodes/events`
387
390
  Submit node change events.
@@ -252,6 +252,8 @@ An event left without a chapter and cell reference will never appear in any time
252
252
 
253
253
  **The core move, in brief**: ask participants for any event they can think of, capture everything without filtering first, then gently introduce the state-changing test ("did this actually change something?") to separate real events from actions/notifications/internal checks — see the reference for the full example dialogue and phrasing tips.
254
254
 
255
+ **Hotspots**: a "hotspot" (a pain point, open question or disagreement raised during the brainstorm) is a red feedback note, not an event. Place it as a red sticky next to the event it concerns: `mcp__eventmodelers__create_drawing` with `kind: "sticky"`, `fill: "#ef4444"`, `content` = the issue.
256
+
255
257
  ## Workflow
256
258
 
257
259
  When given domain requirements, perform the following analysis:
@@ -377,3 +377,4 @@ Apply silently — never correct the user out loud.
377
377
  - **No theory.** Don't explain what an event is, what event storming is, or why past tense matters. Just do it.
378
378
  - **Follow the domain, not a template.** Every process is different. Don't force a shape. Let the events emerge from what the user describes.
379
379
  - **Any input is useful.** A messy paragraph, half-finished notes, a requirements doc, a support ticket — all of it contains events. Extract what's there.
380
+ - **Hotspot = red feedback note.** When the user mentions a hotspot (a pain point, open question or dispute), put it on the board as a red sticky next to the event it concerns: `mcp__eventmodelers__create_drawing` with `kind: "sticky"`, `fill: "#ef4444"`, `content` = the issue. Don't turn it into an event, and don't use a yellow sticky (what you get without `fill`).
@@ -172,6 +172,8 @@ Every arrow is paired with a comment on the relevant node(s) from 4.1 — the ar
172
172
 
173
173
  A finding about a single element, or about a cluster, gets a comment only — don't manufacture an arrow for it just to add a drawing.
174
174
 
175
+ **Hotspots.** When the user asks for hotspots, or says to mark something as one, that means a red feedback note, not an arrow: `create_drawing` with `kind: "sticky"`, `fill: "#ef4444"`, and `content` = the problem or question, placed right next to the element it concerns. Without `fill` a sticky renders yellow, which doesn't count as a hotspot.
176
+
175
177
  ---
176
178
 
177
179
  ## Step 5 — Report back to the user
@@ -135,13 +135,14 @@ mention it in the `DONE` comment, and leave it for a self-directed turn (or for
135
135
 
136
136
  This also applies **inside** a turn: when the skill you invoke in step 5 internally calls a second skill (e.g. `/add-next-slice` calling `/html-screen` to fill in the new screen), that second skill's own "invoke `connect` first" preamble is already satisfied by the connect you ran this turn — don't run it again just because the sub-skill's instructions say to.
137
137
 
138
- And it applies **downwards**, to any subagent you dispatch. A subagent is a fresh session that inherits none of this one's state, so hand it `token=`, `org=`, `baseUrl=` and `board=` inline as already-resolved values (the token exactly as the header gave it, `token=$EVENTMODELERS_TOKEN` — the reference, never its value; the subagent inherits the same environment) and tell it explicitly not to invoke `/connect`: all four inline satisfy that skill outright at its Step 0. Three agents that each resolve and verify the same credentials pay for the connect you already did, three more times over.
138
+ And it applies **downwards**, to any subagent you dispatch. A subagent is a fresh session that inherits none of this one's state, so hand it `token=`, `org=`, `baseUrl=` and `board=` inline as already-resolved values (the token exactly as the header gave it, `token=$EVENTMODELERS_TOKEN` — the reference, never its value; the subagent inherits the same environment) and tell it explicitly not to invoke `/connect`: all four inline satisfy that skill outright at its Step 0. When this turn has a `CHAT_SESSION_ID` (step 3), hand that down too, as `session=` — the subagent's writes are part of the same chat's work. Three agents that each resolve and verify the same credentials pay for the connect you already did, three more times over.
139
139
 
140
140
  The same "don't reload what's already loaded" logic applies to `/learn-eventmodelers-api`: it's a lookup reference, not a mandatory preamble. Every skill already documents the exact API calls it needs inline — only invoke `/learn-eventmodelers-api` on demand, for a specific endpoint/field/type a skill's own instructions don't cover, and only once per session even then.
141
141
  3. **Resolve `BOARD_ID`** from this turn's `board_id` field; if absent, fall back to `boardId` in `.eventmodelers/config.json`.
142
142
  **Resolve `TIMELINE_ID`** from this turn's `context.timelineId`, if present and non-null; otherwise use this turn's `timeline_id` field. `context.timelineId` reflects the chapter the user was actually pointing at on the canvas (a selected cell or node) when they issued the prompt, which can differ from `timeline_id` — the chapter the voice/prompt session happened to be scoped to — so it wins whenever both are present.
143
143
  **Resolve `NODE_ID`** from the first entry of this turn's `context.selectedNodes`, if that array is present and non-empty; otherwise use this turn's `node_id` field. `context.selectedNodes` reflects what was actually selected on the canvas when the prompt was issued, which can differ from `node_id` — set only when the prompt originated from a specific node/comment — so it wins whenever both are present.
144
144
  **Resolve `CELL_ID`** from this turn's `context.selectedCell.id`, if present and non-null. When present, it overrules any cell reference (e.g. `"A2"`) parsed from the prompt text itself — it reflects the actual cell the user had selected on the canvas when they issued the prompt, and is more reliable than free-text parsing.
145
+ **Resolve `CHAT_SESSION_ID`** from this turn's `origin_session_id`, if present — the chat this work came from. Unlike the values above it is **this turn's only**: a later prompt without `origin_session_id` has none, never reuse one from an earlier turn. While you have one, **every board write this turn carries it** — the `sessionId` argument on every `mcp__eventmodelers__*` write tool, and the `x-chat-session-id` header on every REST write (see `connect`). That is what lets the board trace each change back to the conversation that asked for it. (A session the person cleared meanwhile is simply dropped by the platform — the write still goes through.)
145
146
  **Resolve `FOCUS_AREA`** from this turn's `context.focusArea`, if present. `nodes` are the elements that were on screen when the prompt was submitted, each with its `id`, `title` and `type`, ordered by how much it says about where the user is: chapters first, then the model itself (`COMMAND`, `READMODEL`, `QUERY`, `EVENT`), then specs (`SCENARIO`, `SPEC_*`), then the rest (screens, notes, drawings, slice frames) — nearest the centre of the view first within each of those groups. `truncated` says more was visible than the list holds (it caps at 15), so read a `truncated` list as "and more around it", not as the whole area. This is orientation, not an instruction — it tells you where the user's attention was, and it is what "this", "here" or "these" in the prompt refer to when nothing is selected. It never overrules an explicit target in the prompt text.
146
147
  4. **Mark the prompt as started** — invoke `/update-prompt-status` with this turn's `prompt_id` and `newStatus=IN_PROGRESS`, before doing any of the actual work below. This is what makes the board UI show the prompt as being actively worked on.
147
148
  5. **Invoke the matched skill — never substitute direct tool calls for it.** Execute the prompt using the skill matched in the Skill Selection table below, passing the resolved `TIMELINE_ID`, `NODE_ID`, and `CELL_ID` from step 3 as that skill's `timelineId`/node-reference/`cellName` arguments (not the raw `timeline_id`/`node_id` fields, and not a cell reference parsed from the prompt text). For a skill like `/place-element` that accepts a `cellName`, pass the resolved `CELL_ID` as `cellName` whenever it's present — skip parsing the prompt text for a cell reference entirely in that case.
@@ -231,7 +232,8 @@ Then reply `<promise>DONE</promise>` and wait for the next turn. Nothing is writ
231
232
  ### Prompts that came from a chat
232
233
 
233
234
  A prompt turn whose header carries `origin_session_id=` / `origin_message_id=` is work you created
234
- in a chat turn. Work it like any prompt, and after step 6's `DONE`, post one short chat message
235
+ in a chat turn. Work it like any prompt — every board write carrying `origin_session_id` as its chat
236
+ session (step 3's `CHAT_SESSION_ID`) — and after step 6's `DONE`, post one short chat message
235
237
  into that conversation — `post_chat_message` with `sessionId` = `origin_session_id` (no `replyTo`:
236
238
  the message was already answered) — one or two sentences on what you did, element names included,
237
239
  same style as a chat reply (*"Added the read model Dancing Queen, fed by Dance Class Booked. Want