@eventmodelers/cli 1.0.151 → 1.0.153

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.
@@ -0,0 +1,201 @@
1
+ <!--
2
+ Example slice blocks — the contract for what the `prototype` skill generates, one block per slice,
3
+ pasted at @@SLICES@@ in index.html. One of each kind: state change, state view, automation.
4
+
5
+ Inside a block:
6
+ - JSX and modern JavaScript; React hooks via React.useState etc.
7
+ - useCommand, useReadModel, useNav, useNotModeled are globals (the kit defines them).
8
+ - EMRuntime.reject('<message>') rejects a command with a scenario's errorDescription.
9
+ - Never the text of a closing script tag, not even in a string or comment.
10
+ - Every element belongs to exactly one slice: a command to its state-change slice, a read model to
11
+ its state-view slice, a todo list and its automation to the automation slice.
12
+ -->
13
+
14
+ <script type="text/plain" data-em-slice="11111111-1111-4111-8111-111111111111" data-em-title="Register Customer" data-em-hash="0f3c9a1d2b4e5f60">
15
+ // State change: the screen part is the form the command's inputs come from, plus its button.
16
+ const ofType = (events, type) => events.filter((e) => e.type === type);
17
+
18
+ function RegisterCustomerForm() {
19
+ const register = useCommand('RegisterCustomer');
20
+ const { go } = useNav();
21
+ const [form, setForm] = React.useState({ name: '', email: '' });
22
+ const set = (k) => (e) => setForm({ ...form, [k]: e.target.value });
23
+ // Markup translated 1:1 from the screen's HTML page; inputs bound to the command's fields.
24
+ return (
25
+ <section className="section">
26
+ <div className="box" style={{ maxWidth: 420 }}>
27
+ <h1 className="title is-4">Register</h1>
28
+ <div className="field">
29
+ <label className="label" htmlFor="name">Name</label>
30
+ <input id="name" className="input" placeholder="Jane Doe" value={form.name} onChange={set('name')} />
31
+ </div>
32
+ <div className="field">
33
+ <label className="label" htmlFor="email">Email</label>
34
+ <input id="email" className="input" placeholder="jane@example.com" value={form.email} onChange={set('email')} />
35
+ </div>
36
+ {register.error && <p className="help is-danger">{register.error}</p>}
37
+ <button id="register" className="button is-primary" onClick={() => register.run(form).ok && go('Customers')}>
38
+ Register Customer
39
+ </button>
40
+ </div>
41
+ </section>
42
+ );
43
+ }
44
+
45
+ EMRuntime.defineSlice({
46
+ id: '11111111-1111-4111-8111-111111111111',
47
+ title: 'Register Customer',
48
+ order: 1,
49
+ commands: {
50
+ RegisterCustomer: {
51
+ decide(history, cmd, ctx) {
52
+ // Scenario "Email must be unique"
53
+ if (ofType(history, 'CustomerRegistered').some((e) => e.data.email === cmd.email)) {
54
+ EMRuntime.reject('Email is already registered');
55
+ }
56
+ return [{ type: 'CustomerRegistered', data: { customerId: ctx.uuid(), name: cmd.name, email: cmd.email } }];
57
+ },
58
+ },
59
+ },
60
+ specs: [
61
+ {
62
+ title: 'Customer registers',
63
+ kind: 'command',
64
+ given: [],
65
+ when: { type: 'RegisterCustomer', data: { name: 'Jane Doe', email: 'jane@example.com' } },
66
+ then: [{ type: 'CustomerRegistered', data: { name: 'Jane Doe', email: 'jane@example.com' } }],
67
+ },
68
+ {
69
+ title: 'Email must be unique',
70
+ kind: 'command',
71
+ given: [{ type: 'CustomerRegistered', data: { customerId: 'c-1', name: 'Jane Doe', email: 'jane@example.com' } }],
72
+ when: { type: 'RegisterCustomer', data: { name: 'John Doe', email: 'jane@example.com' } },
73
+ error: 'Email is already registered',
74
+ },
75
+ ],
76
+ screens: { Registration: { actor: 'Customer', part: RegisterCustomerForm } },
77
+ });
78
+ </script>
79
+
80
+ <script type="text/plain" data-em-slice="22222222-2222-4222-8222-222222222222" data-em-title="Customer List" data-em-hash="7a1e44c09d2b3f81">
81
+ // State view: the read model is one component — the region of the screen that shows its data.
82
+ function CustomerTable() {
83
+ const customers = useReadModel('Customers');
84
+ const notModeled = useNotModeled();
85
+ return (
86
+ <section className="section">
87
+ <h1 className="title is-4">Customers</h1>
88
+ {customers.length === 0 ? (
89
+ <p>No customers yet.</p>
90
+ ) : (
91
+ <table className="table">
92
+ <tbody>
93
+ {customers.map((c) => (
94
+ <tr key={c.customerId}>
95
+ <td>{c.name}</td>
96
+ <td>{c.email}</td>
97
+ <td>{c.mailSent ? 'mail sent' : 'pending'}</td>
98
+ </tr>
99
+ ))}
100
+ </tbody>
101
+ </table>
102
+ )}
103
+ <button className="button" onClick={() => notModeled('Export')}>Export</button>
104
+ </section>
105
+ );
106
+ }
107
+
108
+ EMRuntime.defineSlice({
109
+ id: '22222222-2222-4222-8222-222222222222',
110
+ title: 'Customer List',
111
+ order: 2,
112
+ readModels: {
113
+ Customers: {
114
+ initial: () => [],
115
+ evolve(state, e) {
116
+ if (e.type === 'CustomerRegistered') return [...state, { customerId: e.data.customerId, name: e.data.name, email: e.data.email }];
117
+ if (e.type === 'ConfirmationMailSent') return state.map((c) => (c.customerId === e.data.customerId ? { ...c, mailSent: true } : c));
118
+ return state;
119
+ },
120
+ },
121
+ },
122
+ specs: [
123
+ {
124
+ title: 'Registered customers are listed',
125
+ kind: 'readmodel',
126
+ readModel: 'Customers',
127
+ given: [{ type: 'CustomerRegistered', data: { customerId: 'c-1', name: 'Jane Doe', email: 'jane@example.com' } }],
128
+ expect: [{ customerId: 'c-1', name: 'Jane Doe' }],
129
+ },
130
+ ],
131
+ screens: { Customers: { actor: 'Back Office', part: CustomerTable } },
132
+ });
133
+ </script>
134
+
135
+ <script type="text/plain" data-em-slice="33333333-3333-4333-8333-333333333333" data-em-title="Send Confirmation Mail" data-em-hash="c5d2e8f1a0b39476">
136
+ // Automation: its todo list, the automation and the command it issues. How an item gets onto the
137
+ // list and off it comes from the todo list's scenarios: given CustomerRegistered → listed;
138
+ // given CustomerRegistered + ConfirmationMailSent → empty.
139
+ EMRuntime.defineSlice({
140
+ id: '33333333-3333-4333-8333-333333333333',
141
+ title: 'Send Confirmation Mail',
142
+ order: 3,
143
+ readModels: {
144
+ PendingConfirmationMails: {
145
+ initial: () => ({}),
146
+ evolve(state, e) {
147
+ if (e.type === 'CustomerRegistered') return { ...state, [e.data.customerId]: { customerId: e.data.customerId, email: e.data.email } };
148
+ if (e.type === 'ConfirmationMailSent') {
149
+ const { [e.data.customerId]: _done, ...rest } = state;
150
+ return rest;
151
+ }
152
+ return state;
153
+ },
154
+ query: (state) => Object.values(state),
155
+ },
156
+ },
157
+ commands: {
158
+ SendConfirmationMail: {
159
+ decide: (history, cmd, ctx) => [{ type: 'ConfirmationMailSent', data: { customerId: cmd.customerId, sentAt: ctx.now.toISOString() } }],
160
+ },
161
+ },
162
+ automations: {
163
+ SendConfirmationMail: {
164
+ readModel: 'PendingConfirmationMails',
165
+ trigger: 'auto',
166
+ external: true,
167
+ todo: (items) => items,
168
+ process: (item) => ({
169
+ effect: `E-mail with confirmation link to ${item.email}`,
170
+ command: { type: 'SendConfirmationMail', data: { customerId: item.customerId, email: item.email } },
171
+ }),
172
+ },
173
+ },
174
+ specs: [
175
+ {
176
+ title: 'A new registration is waiting for its mail',
177
+ kind: 'readmodel',
178
+ readModel: 'PendingConfirmationMails',
179
+ given: [{ type: 'CustomerRegistered', data: { customerId: 'c-1', name: 'Jane Doe', email: 'jane@example.com' } }],
180
+ expect: [{ customerId: 'c-1' }],
181
+ },
182
+ {
183
+ title: 'A sent mail leaves the list',
184
+ kind: 'readmodel',
185
+ readModel: 'PendingConfirmationMails',
186
+ given: [
187
+ { type: 'CustomerRegistered', data: { customerId: 'c-1', name: 'Jane Doe', email: 'jane@example.com' } },
188
+ { type: 'ConfirmationMailSent', data: { customerId: 'c-1' } },
189
+ ],
190
+ expect: [],
191
+ },
192
+ {
193
+ title: 'A new registration gets a confirmation mail',
194
+ kind: 'automation',
195
+ automation: 'SendConfirmationMail',
196
+ given: [{ type: 'CustomerRegistered', data: { customerId: 'c-1', name: 'Jane Doe', email: 'jane@example.com' } }],
197
+ expectCommands: [{ type: 'SendConfirmationMail', data: { customerId: 'c-1' } }],
198
+ },
199
+ ],
200
+ });
201
+ </script>
@@ -0,0 +1,30 @@
1
+ # Prototype — curl Fallback Calls
2
+
3
+ Only needed when MCP is not connected. `prototype.mjs status` (Step 2) already uses the REST API directly and needs no MCP.
4
+
5
+ ## Step 1 — Contexts and timelines
6
+
7
+ ```bash
8
+ curl -s -H "x-token: $TOKEN" "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=MODEL_CONTEXT&projection=line"
9
+ curl -s -H "x-token: $TOKEN" "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=CHAPTER&projection=line"
10
+ ```
11
+
12
+ ## Step 3 — One flagged slice
13
+
14
+ ```bash
15
+ # Elements with fields + dependencies
16
+ curl -s -G -H "x-token: $TOKEN" "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/slicedata" \
17
+ --data-urlencode "contextName=<CONTEXT>" -d sliceId=<SLICE_ID> -d projection=fields -d format=json
18
+
19
+ # Scenarios and storylines
20
+ curl -s -G -H "x-token: $TOKEN" "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/slicedata" \
21
+ --data-urlencode "contextName=<CONTEXT>" -d sliceId=<SLICE_ID> -d projection=specs -d format=json
22
+
23
+ # Full screen records (meta.pages, meta.marks, meta.fields)
24
+ curl -s -X POST -H "x-token: $TOKEN" -H "Content-Type: application/json" \
25
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/query" -d '{"ids": ["<id1>", "<id2>"]}'
26
+
27
+ # A sketch SCREEN's elements — 404 when the screen is an uploaded image
28
+ curl -s -H "x-token: $TOKEN" -H "x-user-id: agent" \
29
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/images/<NODE_ID>/description"
30
+ ```
@@ -316,6 +316,7 @@ for it. Instead:
316
316
  `code` block with Mermaid/PlantUML/ASCII), **never** drawn on the board with stickies, arrows or `create_drawing`.
317
317
  Only an explicit request to put it on the board changes that, and then as an image (`/domain-story` § board), not rebuilt from shapes;
318
318
  - **rows and columns** — a comparison, a matrix, a short list with a few attributes per item → `table`.
319
+ - **a prototype you just built** (the `/prototype` skill) → `prototype`, the uploaded page running in the chat with a Full screen button;
319
320
  - anything the customer defined **their own snippet type** for → `custom` with its `ref`: `list_snippet_types`
320
321
  shows them with `instructions` on when to use each; prefer a fitting one over a built-in kind. A click on it arrives
321
322
  with the button's `then` — what the type's author meant the click to do, after the message as a fenced
@@ -422,6 +423,7 @@ nothing to sanitize either, since a board change is not user text.
422
423
  | Find what changed in the code since the last analysis (git history from the chapter note's baseline) that the model doesn't reflect, and propose fixes | `/detect-model-drift` |
423
424
  | Walk someone through a feature slice by slice, or through every slice a planned feature needs (existing, to adjust, new), as a player in the chat | `/walkthrough` |
424
425
  | Turn a document or image (e.g. a tender/RFP) into a chaptered event model with screens, scenarios and a written blueprint summary | `/summarize` |
426
+ | Build or update a clickable prototype of one context — one page uploaded to the platform and played with the timeline ▶ button, built slice by slice (only changed slices are rebuilt), screens as tabs, in-browser event sourcing, logic from the scenarios, automations runnable (read-only on the board) | `/prototype` |
425
427
  | Look up any API endpoint or element type not already covered by the skill you're executing | `/learn-eventmodelers-api` |
426
428
  | Add or rename an attribute across a chain of elements | `/attributes` |
427
429
  | Add or improve example data on element fields | `/examples` |