@eventmodelers/cli 1.0.33 → 1.0.35

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.33",
3
+ "version": "1.0.35",
4
4
  "description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, Cratis, or modeling-only)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,13 +1,15 @@
1
1
  ---
2
2
  name: discover-storyboard
3
- description: Navigate an existing web app using browser automation MCP (Puppeteer/Playwright/Chrome DevTools), capture screenshots at each step, and build one or more storyboard timelines on the board with real screenshots as SCREEN nodes — ready for event modeling
3
+ description: Navigate an existing web app using browser automation MCP (Puppeteer/Playwright/Chrome DevTools), capture each step, and build one or more storyboard timelines on the board as HTML_SCREEN nodes reconstructed from the real page markup — ready for event modeling
4
4
  ---
5
5
 
6
6
  # Discover Storyboard
7
7
 
8
8
  > **FIRST — before invoking `connect` or anything else**: check for a browser automation MCP (Step 0). If none is found, stop immediately.
9
9
 
10
- You are discovering the UI flows of an existing system by navigating it with a browser, taking screenshots, and uploading them to the eventmodelers board as SCREEN nodes — one per column, arranged chronologically in timelines so the team can build an event model from the real application.
10
+ You are discovering the UI flows of an existing system by navigating it with a browser, capturing each screen, and uploading it to the eventmodelers board as a SCREEN node — one per column, arranged chronologically in timelines so the team can build an event model from the real application.
11
+
12
+ **Default rendering mode is HTML, not raw screenshots.** Every SCREEN node this skill places is by default an `HTML_SCREEN` — a real, editable HTML/CSS reconstruction of the captured page (built from `CONTENT_TOOL`'s markup, cleaned up with Bulma classes per the `html-screen` skill's conventions), not a flat image. Raw screenshot images (`contentType: "image"`) are used **only** when the user explicitly asks for screenshots/images during Step 2 — never as the default, and never as a fallback chosen for convenience. An HTML reconstruction is editable, themeable, and consistent with every other screen produced elsewhere in this toolkit; a screenshot is an inert picture. See Step 7b for the two paths.
11
13
 
12
14
  ---
13
15
 
@@ -77,6 +79,10 @@ Ask the user:
77
79
 
78
80
  Wait for the answer (or a blank/skip). Save as `discoveryGuidance`. If blank, set to `"explore all visible flows"`.
79
81
 
82
+ ### Rendering mode
83
+
84
+ Default `renderMode` to `"html"` — screens are placed as `HTML_SCREEN` reconstructions, never raw images, unless the user's answer to Question 2 (or `$ARGUMENTS`) explicitly asks for screenshots/images (e.g. "use screenshots", "capture images", "I want the actual pixels"). Only in that case set `renderMode = "image"`. Do not ask a separate question for this — infer it from the guidance already given, defaulting to `"html"` when nothing image-related was said.
85
+
80
86
  ### Additional parameters (from `$ARGUMENTS` only — do not ask)
81
87
 
82
88
  | Field | How to find it | Default |
@@ -107,8 +113,8 @@ Use the browser tools identified in Step 0. Process screens **one at a time**
107
113
 
108
114
  1. Navigate to `startUrl` using `NAVIGATE_TOOL`
109
115
  2. Wait briefly for the page to settle
110
- 3. Take a screenshot using `SCREENSHOT_TOOL` — save to `/tmp/discover-storyboard/screen-001.png`
111
- 4. Use `CONTENT_TOOL` to read the page structure and compose a `description` for this screen covering three things:
116
+ 3. If `renderMode == "image"`: take a screenshot using `SCREENSHOT_TOOL` — save to `/tmp/discover-storyboard/screen-001.png`. If `renderMode == "html"` (default): skip the screenshot — `CONTENT_TOOL`'s markup (next step) is what gets placed.
117
+ 4. Use `CONTENT_TOOL` to read the page structure. When `renderMode == "html"`, also capture the markup needed to reconstruct this screen (Step 7b) — the visible content, layout structure, form fields/labels, buttons, and any status/data values shown, enough to rebuild a faithful HTML mockup, not just a text summary. Compose a `description` for this screen covering three things:
112
118
  - **What it shows**: the main content and purpose of this screen
113
119
  - **How the user got here**: `"Initial load"` for the entry screen
114
120
  - **What actions are possible**: list the primary user actions available (buttons, forms, links that lead somewhere meaningful) — expressed as intent, not UI labels (e.g. "user can submit a new order", "user can filter products by category")
@@ -120,7 +126,8 @@ screens = [
120
126
  index: 1,
121
127
  title: "<page title or heading>",
122
128
  url: "<current url>",
123
- filepath: "/tmp/discover-storyboard/screen-001.png",
129
+ filepath: "/tmp/discover-storyboard/screen-001.png", // only populated when renderMode == "image"
130
+ capturedMarkup: "<captured content/structure used to rebuild the HTML mockup>", // only populated when renderMode == "html"
124
131
  flowHint: "<which flow this belongs to, e.g. 'Navigation / Home'>",
125
132
  description: "Shows <what>. Arrived via: initial load. Actions: <user can do X>, <user can do Y>, <user can do Z>."
126
133
  }
@@ -151,15 +158,16 @@ For each subsequent screen (up to `maxScreenshots`):
151
158
  - Repeated UI patterns with identical structure
152
159
 
153
160
  After each interaction:
154
- 1. Take a screenshot → save to `/tmp/discover-storyboard/screen-NNN.png` (increment counter)
155
- 2. Use `CONTENT_TOOL` to read the page and compose a `description` covering:
161
+ 1. If `renderMode == "image"`: take a screenshot → save to `/tmp/discover-storyboard/screen-NNN.png` (increment counter). If `renderMode == "html"` (default): skip the screenshot.
162
+ 2. Use `CONTENT_TOOL` to read the page and, when `renderMode == "html"`, capture the markup needed to rebuild this screen (same bar as Step 4a — enough to reconstruct a faithful mockup, not just a summary). Compose a `description` covering:
156
163
  - **What it shows**: the main content and purpose of this screen
157
164
  - **How the user got here**: the action taken to reach this screen (e.g. "clicked 'Add to cart'", "submitted login form")
158
165
  - **What actions are possible**: primary user actions expressed as intent (e.g. "user can confirm the order", "user can apply a discount code")
159
166
  3. Append to `screens` array with:
160
167
  - `title` — the page/modal title
161
168
  - `url` — current URL
162
- - `filepath` — local file path
169
+ - `filepath` — local file path (only when `renderMode == "image"`)
170
+ - `capturedMarkup` — captured content/structure (only when `renderMode == "html"`)
163
171
  - `flowHint` — which logical flow this screen belongs to
164
172
  - `description` — "Shows <what>. Arrived via: <interaction>. Actions: <user can do X>, <user can do Y>."
165
173
 
@@ -316,7 +324,7 @@ Response includes `{ columnId, index, totalColumns }` — `index` is the new `co
316
324
 
317
325
  Extract `columnId`. Compute `CELL_ID = actorRowId + "-" + columnId` and `CELL_NAME` per the convention above.
318
326
 
319
- ### 7b — Create the SCREEN node with its screenshot, atomically
327
+ ### 7b — Create the SCREEN node, atomically
320
328
 
321
329
  Generate a UUID for `SCREEN_NODE_ID`:
322
330
 
@@ -324,9 +332,28 @@ Generate a UUID for `SCREEN_NODE_ID`:
324
332
  python3 -c "import uuid; print(uuid.uuid4())"
325
333
  ```
326
334
 
327
- This step must create the node and attach the real screenshot in a single call — never split into "upload image" then "create node" (or vice versa), which leaves a window where the node exists with no image or an image with no node.
335
+ This step must create the node and attach its content (HTML pages, or the screenshot) in a single call — never split into "upload content" then "create node" (or vice versa), which leaves a window where the node exists with no content.
336
+
337
+ **Default path — `renderMode == "html"` (use unless the user explicitly asked for screenshots/images in Step 2):**
338
+
339
+ Reconstruct the captured screen as a real HTML/CSS fragment from `screen.capturedMarkup`, following the `html-screen` skill's conventions: full-size markup (16px body text, generous padding), one self-contained fragment per page (no `<html>`/`<head>`/`<body>` wrapper), no `<script>`/inline handlers, Bulma CSS classes (`title`, `button`, `is-primary`, `field`/`control`/`input`, tables, tags for status badges, etc.). Reproduce the real layout, labels, form fields, buttons, and any live data/status values seen on the actual page — this is a faithful reconstruction of the discovered screen, not a generic mockup.
340
+
341
+ ```
342
+ mcp__eventmodelers__create_screen {
343
+ "boardId": "<BOARD_ID>",
344
+ "contentType": "html",
345
+ "nodeId": "<SCREEN_NODE_ID>",
346
+ "chapterId": "<CHAPTER_ID>",
347
+ "cellId": "<CELL_ID>",
348
+ "pages": ["<reconstructed HTML fragment for this screen>"],
349
+ "description": "<screen.description — 'Shows X. Arrived via: Y. Actions: user can do A, user can do B.'>"
350
+ }
351
+ ```
352
+
353
+ **Never pass an empty `pages` array** — an empty array produces a blank placeholder, same as in `html-screen`/`eventmodeling-storyboarding-events`.
354
+
355
+ **Image path — `renderMode == "image"` only, when the user explicitly asked for screenshots:**
328
356
 
329
- **Prefer MCP:**
330
357
  ```
331
358
  mcp__eventmodelers__create_screen {
332
359
  "boardId": "<BOARD_ID>",
@@ -340,7 +367,7 @@ mcp__eventmodelers__create_screen {
340
367
  }
341
368
  ```
342
369
 
343
- **Fallback (no MCP)** — the same atomic operation via the `image-nodes` endpoint (not the plain `images/:id` endpoint, which only updates an existing node's image and does not place it):
370
+ **Fallback (no MCP)** — the same atomic operation via the `image-nodes` endpoint (not the plain `images/:id` endpoint, which only updates an existing node's image and does not place it) — image path only:
344
371
  ```bash
345
372
  curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/image-nodes/$SCREEN_NODE_ID" \
346
373
  -H "x-token: $TOKEN" \
@@ -348,8 +375,9 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/image-nodes/$SCREEN_
348
375
  -F "chapterId=$CHAPTER_ID" \
349
376
  -F "cellName=$CELL_NAME"
350
377
  ```
378
+ For the HTML path with no MCP, use the `html-screen-nodes` endpoint per the `html-screen` skill's fallback mechanics instead.
351
379
 
352
- Response: `204` on success. Log failures in the final report but continue to the next screen — do not stop the entire run.
380
+ Response: `204`/success on success. Log failures in the final report but continue to the next screen — do not stop the entire run.
353
381
 
354
382
  ### 7d — Report per-screen progress
355
383
 
@@ -13,31 +13,40 @@ allowed-tools:
13
13
 
14
14
  Prefer `mcp__eventmodelers__*` tools when available (registered by the `connect` skill) — the curl blocks below are the fallback for sessions without MCP connected.
15
15
 
16
- ## Storylines an On-Demand Alternative to GWT Scenarios (Experimental)
17
-
18
- **By default, this skill always produces GWT scenarios.** Storylines are a separate, experimental
19
- artifact only build one when the user's request explicitly asks for a "storyline", "walkthrough",
20
- or "narrative". Never generate a storyline as a side effect of ordinary scenario elaboration.
21
-
22
- **Exception — the mandatory read-model scenario pass** (see the "Per read model" section of the Quality Checklist below): when the user's request already frames read-model coverage as open to either form — e.g. "add scenarios for the read models, GWT or storylines", or the orchestrating skill's Step 7 gate, which requires exactly that — the explicit-ask condition is satisfied at that request level, not per read model. Within that pass, decide GWT vs. storyline **per read model** based on domain fit (does this read model's row genuinely walk through multiple states worth narrating?), not by asking again for every individual read model and not by defaulting to GWT for all of them just because that's the baseline elsewhere in this skill. Outside that pass — an ad-hoc "write scenarios for X" with no mention of storylines — the strict on-demand rule above still applies unchanged.
23
-
24
- A GWT scenario asserts one isolated transition: a single precondition, a single action, a single
25
- resulting outcome. A storyline instead narrates one specific use case as an ordered sequence of
26
- **beats**, where the *same* element (usually a read model) is walked through multiple states across
27
- one flow something no single GWT scenario can express, since GWT only ever asserts one
28
- before/after pair at a time.
16
+ ## GWT vs. Storyline Decision Rule
17
+
18
+ A GWT scenario asserts one isolated transition (one precondition one action → one outcome). A
19
+ storyline instead narrates one use case as an ordered sequence of **beats**, walking the *same*
20
+ element (usually a read model) through multiple states in one flow something no single GWT can
21
+ express.
22
+
23
+ **Commands: always GWT.** A command has no state progression to narrate — it validates one
24
+ input against one state and either succeeds or is rejected. Never write a storyline for a command.
25
+
26
+ **Read models: storyline when the read model goes through a series of clear state transitions
27
+ driven by events; GWT otherwise.** Decide this **per read model**, not once for the whole pass:
28
+ does replaying this read model's actually-connected event(s) more than once produce an interesting
29
+ accumulated/changed state worth narrating? A single event type recurring with different data counts
30
+ just as much as a multi-event lifecycle — e.g. `AccountFunded($40)` then `AccountFunded($70)`
31
+ walking a balance from $40 to $110 is a genuine storyline driver. **Todo lists are a prime
32
+ candidate**: an item appears when opened and disappears when completed, which is exactly the kind
33
+ of state progression a storyline is for — as is any read model whose rows accumulate, update, or
34
+ get removed across a sequence of events (balances, counters, statuses). Yes → storyline. No →
35
+ GWT-only. Make this call before drafting any payload — a batch pass that reuses one schema for
36
+ every command *and* every read model is a sign the per-read-model judgment got skipped.
37
+
38
+ Even where a read model gets a storyline, still write GWTs for the specific scenarios the
39
+ storyline doesn't cover — validation failures, cross-context sourcing, or any transition outside
40
+ the narrated flow. Storyline and GWT are complementary, not exclusive: the storyline covers the
41
+ narrated lifecycle, GWT covers everything else about that read model.
29
42
 
30
43
  **Example** — a customer-activation walkthrough of a Todos read model:
31
- 1. **Beat 1** — the Todos read model, starting empty.
32
- 2. **Beat 2** — after a `CustomerRegistered` event, the same Todos read model now shows one entry
33
- (an "activate your account" todo).
34
- 3. **Beat 3** — after a `CustomerActivated` event, the same Todos read model is empty again (the
35
- todo was completed and removed).
44
+ 1. **Beat 1** — Todos read model, empty.
45
+ 2. **Beat 2** — after `CustomerRegistered`, Todos shows one entry ("activate your account").
46
+ 3. **Beat 3** after `CustomerActivated`, Todos is empty again (todo completed and removed).
36
47
 
37
- Three separate GWT scenarios could each assert one of those transitions in isolation, but a
38
- storyline threads all three together as one narrated walkthrough of the read model's lifecycle
39
- useful for a stakeholder-facing walkthrough of a use case, not for exhaustive GWT coverage. Keep
40
- producing ordinary Given/When/Then scenarios for everything else.
48
+ A storyline threads all three beats into one walkthrough; three separate GWTs could each assert one
49
+ transition but not the lifecycle.
41
50
 
42
51
  ### Storyline data shape
43
52
 
@@ -275,6 +284,14 @@ Work through each question with the domain in mind. If the answer is "that situa
275
284
 
276
285
  ## Workflow
277
286
 
287
+ ### 0. Decide GWT vs. storyline for every READMODEL — before drafting any scenario payload
288
+
289
+ Apply the decision test from "GWT vs. Storyline — Decision Rule" above to every READMODEL on the
290
+ board (or in scope). Produce a visible artifact — one line per READMODEL, "yes → storyline" or
291
+ "no → GWT-only" with a one-clause reason — before writing the first scenario payload, not as a
292
+ checklist review after. Deciding this list up front is what stops a batch pass from silently
293
+ collapsing into one reused schema for every command and read model alike.
294
+
278
295
  For each command and view, write scenarios in Given-When-Then format:
279
296
 
280
297
  ### 1. Command Scenarios (Given-When-Then)
@@ -812,7 +829,7 @@ After posting, tell the user:
812
829
  - [ ] **Every READMODEL on the board has at least one view scenario** — GWT (`given`: source EVENTs, `when`: empty, `then`: the READMODEL) or a storyline. A model with dozens of command scenarios and 0 read-model scenarios is not a complete Step 7 — it's easy to walk away thinking coverage is thorough because the command side looks exhaustive, so check the read-model side explicitly before reporting this step done.
813
830
  - [ ] **Population scenario** — the view shows correct data after its source event(s)
814
831
  - [ ] **Removal/update scenario, where applicable** — a row disappears or changes (`expectEmptyList: true` for list-type views) after an event that supersedes it (expiry, return, archival, withdrawal, status change, etc.). `EVENT → READMODEL` is exempt from column ordering **only when the read model already feeds an AUTOMATION** (`READMODEL → AUTOMATION` edge; see `learn-eventmodelers-api` §3 and `eventmodeling-orchestrating-event-modeling`'s "No backward arrows") — a later event connecting back to an earlier-placed todo-list read model is normal for that accumulator shape, so add the connection if it's missing rather than assuming the scenario is impossible. If the read model has no automation to feed, the backward connection is rejected — by Step 5 this should already have been modeled the forward way (see `eventmodeling-identifying-outputs` Step 5g's copy pattern: a new read model + screen copy in the later event's column, never a link back). If it wasn't, write the scenario against that forward-placed copy rather than the original. Only skip this scenario, with a documented gap (TASK comment), when the superseding event genuinely lives in a different chapter.
815
- - [ ] **GWT vs. storyline decided per read model, not applied uniformly** — reach for a storyline wherever the *same* read model row genuinely walks through multiple states worth narrating; the rest of the read models in the same model may be correctly GWT-only. Don't default to one format for every read model just because it worked for the first one, and don't judge "multiple states" by counting *distinct connected event types* — that undercounts real candidates. **A single event type recurring with different data is just as valid a storyline driver as several different event types**: `AccountFunded($40)` then `AccountFunded($70)` walking a balance read model from $40 to $110 is exactly as strong a storyline as a multi-event lifecycle. In practice this means almost every list/aggregate read model qualifies — a titles list growing from one row to two as the same `TitleAdded`-shaped event recurs, a dashboard's counters incrementing as the same `CopyAdded` event recurs, are both genuine storylines, not "just" GWT territory. Ask "does replaying this read model's *actually connected* event(s) more than once produce an interesting accumulated/changed state?" — not "how many different event types feed this."
832
+ - [ ] **GWT vs. storyline decided per read model, not applied uniformly** — apply the decision test from "GWT vs. Storyline — Decision Rule" above to each read model individually; some may qualify for a storyline while the rest of the same model are correctly GWT-only. Don't default to one format for every read model just because it worked for the first one.
816
833
  - [ ] **No redundancy or contradiction between a read model's GWT scenarios and its storyline** — if both exist for the same read model, read the storyline's beats before finalizing the GWTs. A GWT that asserts the same state a beat already shows is redundant (delete it); a GWT written without tracing the same causal sequence the storyline encodes can end up asserting something the storyline's beats actually contradict (e.g. claiming two entities coexist in a view when the storyline correctly shows one superseding the other) — delete or fix it, never leave a contradiction on the board.
817
834
  - [ ] **Cross-context read models handled honestly** — if a read model's true source events live in a different chapter, `given` can't reference them (same-timeline-only, like connections); write the scenario with an empty `given` and say so explicitly in the scenario title, rather than silently omitting the scenario or fabricating a same-timeline event that isn't the real source
818
835
 
@@ -17,6 +17,15 @@ Coordinates the 11-step Event Modeling workflow. Each step delegates to a
17
17
  specialized skill — this skill holds the sequence, transition conditions, and
18
18
  what to carry forward between steps.
19
19
 
20
+ ## Do not cut corners to save tokens or effort
21
+
22
+ A correct model is the entire point of this workflow — it is not optional scope that can be traded away when a step gets expensive or a placement rule turns out to be inconvenient. If satisfying a rule (a todo-list automation, a missing read model for a command screen, a proper translation-automation chain for an external trigger) requires more columns, more nodes, or more tool calls than expected, that is not a signal to remove the requirement or invent an exemption — it is a signal to spend the additional calls. Budget and token cost are never a valid reason to:
23
+ - delete an automation, event, or read model that the rules call for, in order to avoid a placement conflict — solve the placement conflict instead (see the column-layout patterns throughout this file and in `eventmodeling-identifying-outputs`);
24
+ - label a command screen's missing read model as "session-context" or "accepted debt" when the rules don't actually exempt it — only a genuinely blank creation form is exempt; every other command screen needs a real read model, even a small identity/lookup one;
25
+ - collapse a two-chained-translation-automation requirement (external EVENT → translation automation → internal EVENT → worker automation) into a single direct connection because it's simpler.
26
+
27
+ If you catch yourself reasoning "this would need N more columns/nodes, let me simplify instead" — that is exactly the moment to stop and do the correct, larger version. A model with more nodes that is right is a better outcome than a smaller one that skips required elements. Flag genuine scope trade-offs to the user explicitly rather than resolving them unilaterally by cutting the model.
28
+
20
29
  ---
21
30
 
22
31
  ## Timeline Alignment Rules
@@ -239,6 +239,8 @@ Cell IDs are always `<rowId>-<columnId>` — no cell array search needed.
239
239
 
240
240
  **One EVENT per column (hard rule).** A column represents a single moment in the timeline. When `elementType` is `EVENT`, an occupied column means occupied *at all* — even if the existing EVENT sits in a different swimlane row (e.g. a different system under Conway's Law). Never place two EVENT nodes in the same column just because they're in different swimlanes. Before treating the column as available, check `cells` for **every** row whose `type === "swimlane"`, not only `targetRow` — if any of those cells already holds an EVENT node, treat the column as occupied and insert a new column immediately after (see the table below), not the target column itself.
241
241
 
242
+ **Same swimlane for events emitted by the same command.** When one command emits more than one event, each event still gets its own column (per the hard rule above), but all of them belong in the *same* swimlane row, placed in adjacent columns immediately after the command. Do not scatter a command's sibling events across different swimlane rows — one command producing multiple events is one system doing multiple things in sequence, not several systems reacting independently.
243
+
242
244
  **Check if the cell is already occupied.**
243
245
 
244
246
  **No direct MCP equivalent**: `get_nodes` only filters by `type`, not `cellId` — there is no MCP tool that filters nodes by cell. Instead, use the `meta.timelineData.cells` you already fetched in Step 3 via `get_node` on the chapter/timeline node: `cells` is a sparse array, so a `nodeId` absent from the entry for `CELL_ID` means the cell is empty. Only fall back to the curl call below if you haven't already loaded `timelineData` (e.g. MCP wasn't used in Step 3 either):