@eventmodelers/cli 1.0.128 → 1.0.130
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/README.md +1 -0
- package/package.json +1 -1
- package/shared/skills/learn-eventmodelers-api/SKILL.md +19 -3
- package/stacks/modeling-kit/templates/.claude/skills/data-journey/SKILL.md +88 -0
- package/stacks/modeling-kit/templates/.claude/skills/walkthrough/SKILL.md +6 -3
- package/stacks/modeling-kit/templates/kit/CLAUDE.md +6 -0
package/README.md
CHANGED
|
@@ -107,6 +107,7 @@ Use skills in Claude Code with `/skill-name`:
|
|
|
107
107
|
| `/timeline` | Live event storming facilitator |
|
|
108
108
|
| `/wdyt` | Business analyst review of your event model |
|
|
109
109
|
| `/walkthrough` | Slice-by-slice player through a feature, or through every slice a planned feature needs (existing, to adjust, new) |
|
|
110
|
+
| `/data-journey` | Where one piece of information (e.g. the customer email) originates, is stored, transformed, used and sent out — as a table in the chat |
|
|
110
111
|
| `/storyboard` | Build a full visual storyboard |
|
|
111
112
|
| `/html-screen` | Design individual real HTML/CSS screens (default) |
|
|
112
113
|
| `/storyboard-screen` | Design individual wireframe/sketch screens (explicit request only) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventmodelers/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.130",
|
|
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": {
|
|
@@ -1122,8 +1122,10 @@ Rule of thumb: if you are about to write "Shall I…?", "Want me to…?", "A or
|
|
|
1122
1122
|
| to show a command or payload | `code` | nothing |
|
|
1123
1123
|
| to show numbers as a diagram (counts per slice, a trend, shares of a whole) | `report` | nothing |
|
|
1124
1124
|
| to explain how a feature works, or walk through what a planned feature takes, slice by slice ("how does registration work?", "we need to add an email — walk me through it") | `walkthrough` | nothing — selecting a step pans the canvas |
|
|
1125
|
+
| to show what a screen could look like, without putting it on the board | `screen` | nothing |
|
|
1126
|
+
| to answer where one piece of information comes from and where it ends up ("where is the customer email used?") | `dataJourney` | nothing — element names pan the canvas |
|
|
1125
1127
|
|
|
1126
|
-
`confirm`, `poll`, `tasks` and `changes` are questions: a person answers once, then they show as answered. `link`, `image`, `code`, `report` and `
|
|
1128
|
+
`confirm`, `poll`, `tasks` and `changes` are questions: a person answers once, then they show as answered. `link`, `image`, `code`, `report`, `walkthrough`, `screen` and `dataJourney` are display only and never wait for anything.
|
|
1127
1129
|
|
|
1128
1130
|
### Shapes
|
|
1129
1131
|
```typescript
|
|
@@ -1152,10 +1154,22 @@ Rule of thumb: if you are about to write "Shall I…?", "Want me to…?", "A or
|
|
|
1152
1154
|
nodeId?: string, // the slice's SLICE_BORDER — drawn in its lanes, selecting the step zooms to it; none for a new slice
|
|
1153
1155
|
status?: 'existing' | 'changed' | 'new', // planned feature: used as is / needs adjustment / built from scratch
|
|
1154
1156
|
elements?: Array<{ type: 'SCREEN' | 'COMMAND' | 'EVENT' | 'READMODEL' | 'AUTOMATION',
|
|
1155
|
-
title: string, fields?: string[]
|
|
1157
|
+
title: string, fields?: string[],
|
|
1158
|
+
html?: string }>, // a new slice only: its sketch, drawn in lanes; html = the SCREEN as a small HTML page (≤ 16000 chars)
|
|
1156
1159
|
screenId?: string, // a SCREEN / HTML_SCREEN outside the slice the person sees in this step
|
|
1157
1160
|
rules?: string[], // business rules of this slice
|
|
1158
1161
|
changes?: string[] }> } // planned feature: what has to change or be built here
|
|
1162
|
+
{ kind: 'screen', html: string, // a screen mock-up as a small HTML page (≤ 16000 chars), shown sandboxed
|
|
1163
|
+
title?: string, caption?: string }
|
|
1164
|
+
{ kind: 'dataJourney', subject: string, // the piece of information followed, in the person's words ("Customer email")
|
|
1165
|
+
summary?: string, // 1–2 sentences: where it comes from, where it goes
|
|
1166
|
+
stops: Array<{ title: string, // 1+, in flow order: the element the data passes through
|
|
1167
|
+
nodeId?: string, // makes the title a link; the element's type is read from the board
|
|
1168
|
+
elementType?: string, // SCREEN, COMMAND, EVENT, READMODEL, AUTOMATION … for an element not on the board
|
|
1169
|
+
role: 'origin' | 'stored' | 'transformed' | 'used' | 'sent', // entered/imported here / kept in this event / renamed, derived, reformatted / read, shown, decided on / leaves the system
|
|
1170
|
+
field?: string, // the field holding it here — names change along the way (email → customerEmail)
|
|
1171
|
+
slice?: string,
|
|
1172
|
+
note?: string }> } // how it is used or changed here, a few words
|
|
1159
1173
|
```
|
|
1160
1174
|
|
|
1161
1175
|
### How to use them
|
|
@@ -1166,7 +1180,9 @@ Rule of thumb: if you are about to write "Shall I…?", "Want me to…?", "A or
|
|
|
1166
1180
|
- **Answers are text.** For a `tasks` answer, match the titles (or ids) back to your list; an element the person left unticked was declined, not forgotten. A `poll` option with a `message` of your own makes the answer unambiguous.
|
|
1167
1181
|
- `get_chat_session` shows your snippet on the message as `snippet`, and the person's answer is the user message after it, carrying `context.snippetReply.messageId` (your message's id) — useful to tell an answer to a snippet from a new message.
|
|
1168
1182
|
- **`report`** draws the numbers you send — it computes nothing, so aggregate first. A series whose length differs from `labels`, or a non-number, makes the whole snippet invisible. Keep `text` as a one-line summary of what the diagram shows. Add `confirm` when the diagram leads to a follow-up question ("Want me to look into the backward arrows?"): the answer comes back like a `confirm` answer, and without it the report is display only.
|
|
1169
|
-
- **`walkthrough`** answers "how does X work?" and "walk me through what changes for Y", **one step per slice**. It opens with a summary page — the slice count (with `status`: how many exist, need adjustment, are new) above your `summary` — then shows each slice in its lanes like Slice View (screen, command / read model, events), a `new` slice sketched from its `elements
|
|
1183
|
+
- **`walkthrough`** answers "how does X work?" and "walk me through what changes for Y", **one step per slice**. It opens with a summary page — the slice count (with `status`: how many exist, need adjustment, are new) above your `summary` — then shows each slice in its lanes like Slice View (screen, command / read model, events), a `new` slice sketched from its `elements` (its SCREEN rendered from the `html` you write: a real, simple page — a form, a list, a table — with inline styles only; scripts and external resources are blocked; without `html` a plain form/table is generated from the fields); each step zooms the canvas to its slice, and **Play** opens it full-screen with description, changes and rules underneath. Build it from the board, never from memory — the `walkthrough` skill (modeling kit) has the procedure. Every `nodeId`/`screenId` must be a real id from this board. Keep `text` to one line naming the feature and the number of slices.
|
|
1184
|
+
- **`screen`** shows a mock-up in the chat — "what could the registration screen look like?", a variant to compare — without creating anything on the board (that is the `html-screen` skill's job). `html` follows the walkthrough's screen rules: body content, inline styles or one `<style>` block, no scripts, no external images, fonts or stylesheets (they are blocked), at most 16000 characters.
|
|
1185
|
+
- **`dataJourney`** answers "where does X come from / where is X used?" for **one piece of information**, as a table: one stop per element it passes through, in flow order, each with its `role`, the field name there and the slice. A headline counts the roles ("Originates in 1 place, stored in 2 elements and used in 3 places"). Build it from the board — the `data-journey` skill (modeling kit) has the procedure; every `nodeId` must be a real id from this board, and a field the board doesn't carry is not a stop. Keep `text` to one line naming the data and where it originates.
|
|
1170
1186
|
- A snippet is shown exactly as you send it — keep secrets and tokens out of `code` and `image`.
|
|
1171
1187
|
|
|
1172
1188
|
---
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: data-journey
|
|
3
|
+
description: Follow one piece of information through the model — "where does the customer email come from, and where is it used?" — and show it in the chat as a table. One stop per element it passes through, in flow order — where it originates (a screen, an external event, an import), which events store it, where it is renamed or derived, which read models, screens and automations use it, and where it leaves the system — each with the field name it has there and its slice. Read-only — changes nothing on the board.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Data journey — where a piece of information comes from and where it goes
|
|
7
|
+
|
|
8
|
+
> **Before doing anything else**, invoke the `connect` skill — if not already connected — to resolve `TOKEN`, `BOARD_ID`, `ORG_ID`, and `BASE_URL`. Do not proceed until it has completed.
|
|
9
|
+
|
|
10
|
+
Prefer `mcp__eventmodelers__*` tools. See `learn-eventmodelers-api` for the curl fallback and §16 *Chat snippets* for the `dataJourney` shape.
|
|
11
|
+
|
|
12
|
+
The result is **one `dataJourney` snippet** in the chat: a table with one row ("stop") per element the information passes through, in the order it flows. Each stop has a **role** — `origin`, `stored`, `transformed`, `used`, `sent` — the **field** it is held in there (names change along the way: `email` on the screen, `customerEmail` in the event), the slice and a few words on what happens to it. A headline above the table counts the roles ("Originates in 1 place, stored in 2 elements and used in 3 places"). Element names are links that zoom the canvas.
|
|
13
|
+
|
|
14
|
+
Typical questions: *"Where is the customer email used?"*, *"Where does the delivery address come from?"*, *"Which events store the IBAN?"*, *"What happens to the discount code?"*, *"/data-journey customer email"*.
|
|
15
|
+
|
|
16
|
+
**This skill never writes to the board.**
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Step 1 — The subject
|
|
21
|
+
|
|
22
|
+
The **subject** is one piece of information in the person's words ("customer email", "order total"). From `$ARGUMENTS` / the message, and the turn's `context=`: a selected element with one field selected, or a message like "this field", means that field.
|
|
23
|
+
|
|
24
|
+
No subject, or several equally likely ("the customer data"): **ask first** and end the turn — a `poll` of the candidates (at most ~8 field names you found, each option's `message` a complete `/data-journey <name>` request), or text only when there is nothing to offer. One piece of information per journey: "name and email" is two journeys — do the first and offer the second in the text.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Step 2 — Find every element that carries it
|
|
29
|
+
|
|
30
|
+
Cheap reads first, never the whole board in full:
|
|
31
|
+
|
|
32
|
+
1. **Name search**: `get_nodes { boardId, name: "<term>", projection: "line" }` finds elements whose *title* mentions it; the field names are in `fields`. Search for the obvious variants (`email`, `mail`, `e-mail`; the board's language).
|
|
33
|
+
2. **Field search**: per chapter, `get_slice_data { boardId, contextName, projection: "fields", format: "toon" }` — every element's fields with their `mapping`. Collect every field that holds the subject: the same name, a prefixed/renamed one (`customerEmail`, `newEmail`, `recipient`), and — via `mapping` — every field mapped **from** one you already have. Follow `mapping` both ways until nothing new turns up; that is how a rename is traced, not by guessing from names. A field that only *sounds* related (`emailVerified`, `emailCount`) is a different piece of information — leave it out, or mention it in `note` on the stop it sits on.
|
|
34
|
+
3. Note `pii: true` on a field — say so in the `summary` if the subject is personal data.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Step 3 — Order and classify the stops
|
|
39
|
+
|
|
40
|
+
Order the stops the way the information flows — follow the edges (screen → command → event → read model → screen; event → automation → command → …), from where it enters to where it ends. Branches follow their source: after the event that stores it, list the read models fed by it, then the automations.
|
|
41
|
+
|
|
42
|
+
| `role` | The element … |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `origin` | is where the information first enters the system: a SCREEN the person types it into, an external/translated event, an import, an API. Usually one; more when it can enter in several ways (registration *and* "change email") |
|
|
45
|
+
| `stored` | is an EVENT that records it — the source of truth. Every event carrying the field is a stop |
|
|
46
|
+
| `transformed` | changes it: a renamed field via `mapping` (`email` → `recipient`), a derived value, a normalisation stated in the element's description or scenarios ("lower-cased") — say what in `note` |
|
|
47
|
+
| `used` | reads it: a COMMAND that only passes it through or checks it, a READMODEL that projects it, a SCREEN that shows it, a scenario that decides on it ("email must be unique") — `note` says how |
|
|
48
|
+
| `sent` | hands it outside the system: an automation calling an external service (mail provider, payment, CRM), an outbound integration event |
|
|
49
|
+
|
|
50
|
+
A command that takes the field from its screen is `used` (it carries it), unless the screen isn't modelled — then the command is the `origin`. Each element appears once; pick the role that says most about it.
|
|
51
|
+
|
|
52
|
+
Per stop: `title` as on the board, `nodeId` (the element's real id), `field` (the field name on that element), `slice` (the slice title from `list_slices`), `note` in a few business words — no ids, no cell addresses. `elementType` only for a stop that is not a board node.
|
|
53
|
+
|
|
54
|
+
**Faithfulness.** Only what the board says. If the information is shown on a screen but no read model carries it, or an event stores it but nothing ever reads it, that is a finding — put it in the `summary` ("Stored in Email Changed, but no read model picks the new email up"), not as an invented stop.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Step 4 — Post it
|
|
59
|
+
|
|
60
|
+
**One** `post_chat_message`: in a `CHAT` turn `replyTo` = this turn's `message_id`; in a prompt turn that came from a chat, `sessionId` = `CHAT_SESSION_ID` and no `replyTo`.
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
mcp__eventmodelers__post_chat_message {
|
|
64
|
+
"boardId": "$BOARD_ID",
|
|
65
|
+
"replyTo": "<message_id>",
|
|
66
|
+
"text": "The customer email enters at registration and ends up in 5 more places.",
|
|
67
|
+
"snippet": {
|
|
68
|
+
"kind": "dataJourney",
|
|
69
|
+
"subject": "Customer email",
|
|
70
|
+
"summary": "Entered once at registration (personal data), kept in two events and used for login and mails. Nothing shows the changed email after Email Changed.",
|
|
71
|
+
"stops": [
|
|
72
|
+
{ "title": "Registration", "nodeId": "<id>", "role": "origin", "field": "email", "slice": "Register Customer", "note": "typed in by the customer" },
|
|
73
|
+
{ "title": "Register Customer", "nodeId": "<id>", "role": "used", "field": "email", "slice": "Register Customer", "note": "must be unique" },
|
|
74
|
+
{ "title": "Customer Registered", "nodeId": "<id>", "role": "stored", "field": "customerEmail", "slice": "Register Customer" },
|
|
75
|
+
{ "title": "Email Changed", "nodeId": "<id>", "role": "stored", "field": "newEmail", "slice": "Change Email" },
|
|
76
|
+
{ "title": "Customer Profile", "nodeId": "<id>", "role": "used", "field": "email", "slice": "Customer Profile", "note": "shown on the profile page" },
|
|
77
|
+
{ "title": "Send Welcome Mail", "nodeId": "<id>", "role": "sent", "field": "recipient", "slice": "Welcome Mail", "note": "to the mail provider" }
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- `text` is one line — the subject and where it originates; it is the fallback and must make sense alone.
|
|
84
|
+
- Every `nodeId` is an id you read in Step 2 — never a guessed one.
|
|
85
|
+
- Long journeys page themselves (5 rows per page); keep every stop in the one snippet.
|
|
86
|
+
- If the person then asks to change something (add the field to a read model, mark it as `pii`), that is ordinary work — prompts per the kit's CLAUDE.md.
|
|
87
|
+
|
|
88
|
+
**No chat** (run from a terminal): print the summary and the stops as a table (role, element, field, slice, note) instead of posting.
|
|
@@ -107,11 +107,13 @@ Per step:
|
|
|
107
107
|
| `description` | 1–3 sentences: what happens in this slice and why it matters, in the board's language. No ids, no cell addresses. |
|
|
108
108
|
| `nodeId` | the slice's `SLICE_BORDER` id from `list_slices` — the player draws the slice from it and zooms the canvas to it. Left out for a `new` slice |
|
|
109
109
|
| `status` | **impact mode: required on every step** — `existing`, `changed` or `new` (table above). Omitted in explain mode |
|
|
110
|
-
| `elements` | **`new` slices only**: the sketch — `[{ "type": "SCREEN" \| "COMMAND" \| "EVENT" \| "READMODEL" \| "AUTOMATION", "title": "…", "fields": ["…"] }]`, the elements the slice will have (a state change: screen → command → event; a state view: event(s) → read model → screen; an automation: read model → automation → command → event). Titles and fields in the board's style; reuse the names of existing events the slice reads from |
|
|
110
|
+
| `elements` | **`new` slices only**: the sketch — `[{ "type": "SCREEN" \| "COMMAND" \| "EVENT" \| "READMODEL" \| "AUTOMATION", "title": "…", "fields": ["…"] }]`, the elements the slice will have (a state change: screen → command → event; a state view: event(s) → read model → screen; an automation: read model → automation → command → event). Titles and fields in the board's style; reuse the names of existing events the slice reads from. **Give the SCREEN an `html`** — see *Screen HTML* below |
|
|
111
111
|
| `screenId` | only when the screen a person sees in this step is not part of the slice itself |
|
|
112
112
|
| `rules` | the business rules of this slice, taken from its scenarios (and the context note); one line each, thresholds verbatim. Empty when there are none — never invent one |
|
|
113
113
|
| `changes` | **impact mode only**: what has to change or be built in this slice, one line each — "add field `email` (String) to Register Customer", "new scenario: duplicate email is rejected" |
|
|
114
114
|
|
|
115
|
+
**Screen HTML.** A new slice's SCREEN is shown as a real screen, so write it: `html` is a small HTML page of what the person sees there — a form for a state change (an input per command field, labelled in business words, a button named after the command), a list or table for a state view (columns from the read model's fields, 2–3 rows of plausible example data), whatever fits the screen otherwise (a checklist, a confirmation page). Keep it rough and simple: a heading and the content, no navigation, header or footer. Body content only (no `<html>`/`<head>` needed), inline `style` attributes or one `<style>` block, at most 16000 characters. It renders sandboxed: scripts, external images, fonts and stylesheets are blocked, so no `<script>`, no CDN, no `https://` images. Without `html` the player generates a plain form/table from the fields.
|
|
116
|
+
|
|
115
117
|
**Faithfulness.** Existing slices come from the board. A rule nobody modelled is not a rule — if the walkthrough reveals a gap ("nothing says what happens when confirmation never arrives"), mention it in the step's `description` as an open question, not as a rule. A sketched new slice is a proposal, and the `description` should read like one.
|
|
116
118
|
|
|
117
119
|
---
|
|
@@ -152,7 +154,8 @@ Impact mode, same shape with `status` (and `changes`, or `elements` for a new sl
|
|
|
152
154
|
{ "title": "Confirm Email", "status": "new",
|
|
153
155
|
"description": "Proposed: the customer opens the link and the email is marked as confirmed.",
|
|
154
156
|
"elements": [
|
|
155
|
-
{ "type": "SCREEN", "title": "Confirm Email Page"
|
|
157
|
+
{ "type": "SCREEN", "title": "Confirm Email Page",
|
|
158
|
+
"html": "<h3>Confirm your email</h3><p>We sent a link to <b>jane@example.com</b>.</p><label>Code<br><input value=\"8F3K-21\"></label><br><br><button>Confirm Email</button>" },
|
|
156
159
|
{ "type": "COMMAND", "title": "Confirm Email", "fields": ["customerId", "token"] },
|
|
157
160
|
{ "type": "EVENT", "title": "Email Confirmed", "fields": ["customerId", "confirmedAt"] }
|
|
158
161
|
],
|
|
@@ -163,7 +166,7 @@ Impact mode, same shape with `status` (and `changes`, or `elements` for a new sl
|
|
|
163
166
|
```
|
|
164
167
|
|
|
165
168
|
- `text` is one line: the subject and the number of slices — in impact mode with the split ("Startup license in 8 slices: 3 new, 2 to adjust"). It is the fallback, so it must make sense alone.
|
|
166
|
-
- Impact mode: before posting, check that every step has a `status`, every `changed` step has `changes` and every `new` step has `elements` and no `nodeId`.
|
|
169
|
+
- Impact mode: before posting, check that every step has a `status`, every `changed` step has `changes` and every `new` step has `elements` (its SCREEN with `html`) and no `nodeId`.
|
|
167
170
|
- `summary` is always there — the first page shows it under the slice count.
|
|
168
171
|
- Every `nodeId` / `screenId` is an id you read in Steps 2–3 — never a guessed or remembered one. A `new` slice has no `nodeId`.
|
|
169
172
|
- The snippet is at most 64 KB; keep descriptions short rather than dropping steps.
|
|
@@ -208,6 +208,10 @@ for it. Instead:
|
|
|
208
208
|
subject (a bare `/walkthrough`) it asks first — which flow, or which new feature — and that question is the reply. A planned change
|
|
209
209
|
("we need to add an email") is its impact mode: every slice marked existing / needs adjustment / new, new ones sketched — the walkthrough is the proposal, and the changes become work only
|
|
210
210
|
when the person asks for them afterwards.
|
|
211
|
+
- **data journey** — "where does X come from?", "where is X used?", "which events store X?", or `/data-journey …`, about
|
|
212
|
+
**one piece of information** (a field, "the customer email"): run the `/data-journey` skill **in this turn** — it only reads
|
|
213
|
+
the board, so no prompt is needed. Its `dataJourney` snippet (a table: origin, stored, transformed, used, sent — per element,
|
|
214
|
+
with the field name there) is your one reply. Without a clear subject it asks first which information.
|
|
211
215
|
- **a skill by name** — the message starts with `/<name>` of one of your skills (the chat panel's menu
|
|
212
216
|
writes these: `/analyze-existing-model`, `/analyze-code-base`, `/detect-model-drift`, `/add-next-slice`, `/examples`, `/timeline …`,
|
|
213
217
|
`/html-screen …`, `/storyboard …`): the person chose that skill, so it **is** the work — create one prompt,
|
|
@@ -274,6 +278,8 @@ for it. Instead:
|
|
|
274
278
|
- a **choice between alternatives** ("A or B?", "which one first?") → `poll`;
|
|
275
279
|
- a **list of things for the person to pick from** (findings, candidates, drifts, "which of these?") → `tasks`;
|
|
276
280
|
- a **pointer to an element, a picture or a command** → `link` / `image` / `code`, instead of describing it.
|
|
281
|
+
- a **screen idea** in the conversation ("what could that screen look like?") → `screen`, a sandboxed HTML mock-up in the chat; putting a screen on the board is `/html-screen` work;
|
|
282
|
+
- **where one piece of information comes from and where it is used** → `dataJourney` (the `/data-journey` skill).
|
|
277
283
|
Plain text stays for answers, explanations and open questions that need words. `text` is still
|
|
278
284
|
required and stays short — it is the lead-in, the snippet carries the detail. A snippet changes
|
|
279
285
|
nothing on the board: the click comes back as the next `CHAT` turn, and only then do you
|