@eventmodelers/cli 1.0.160 → 1.0.162

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 CHANGED
@@ -680,6 +680,7 @@ Flow: `Ready → Planned → InProgress → Handover → InProgress → Done`. P
680
680
  - **Both sides need the new kit.** An already-installed backend kit keeps its old `ralph.js` and prompts and silently ignores `--handover` (slices still end at `Done`); `re-init` it to pick the feature up.
681
681
  - **The platform must know the `Ready` / `Handover` statuses.**
682
682
  - A kit declares what it claims in `lib/kit.json` — `{ "pickupStatus": "Handover" }`; absent means `Planned`.
683
+ - `run --status-trigger <status>` overrides that pickup status for a single run (e.g. `--status-trigger Ready`); nothing is saved.
683
684
 
684
685
  The contract itself is the shared `ui-contract` skill's job (see above); it also owns the rule for which status a slice ends at.
685
686
 
package/cli.js CHANGED
@@ -3718,6 +3718,7 @@ credentialFlags(program
3718
3718
  .option('--dangerously-skip-permissions', 'Run the modeling agent\'s Claude process with --dangerously-skip-permissions, as before the lockdown: every tool call is approved — any shell command, curl to anywhere, any file the user can read or write, every MCP server the machine configures. Off by default because the agent takes chat messages and snippet clicks from any collaborator on the board; pass it only for a board whose collaborators you trust with your shell. Default Claude runner of --modeling/--standalone/--worker/--global only — not with --agent, --exec or --local-ai, which bring their own permission flags.')
3719
3719
  .option('--browser', 'Give the locked-down modeling agent a browser: the Playwright MCP server (npx @playwright/mcp) next to the eventmodelers one, for discover-storyboard. Off by default because a browser can reach any site, which the lockdown otherwise rules out. Default Claude runner only; --dangerously-skip-permissions already loads every MCP server the machine configures.')
3720
3720
  .option('--handover', 'Backend build kit: for this run only, finish slices that have a UI at `Handover` instead of `Done` (same as the project setting `handover: true`, which `init --handover` / `config --handover on` save — this flag writes nothing). Without it the project setting applies; without both, slices end at `Done`.')
3721
+ .option('--status-trigger <status>', 'Build kit: the slice status the agent picks work up from, for this run only (e.g. Ready, Planned, Handover). Defaults to the kit\'s own pickup status — `Planned` for backend kits, `Handover` for the UI kits. Nothing is written to disk.')
3721
3722
  .option('--verbose', 'Log every tool call\'s full input (commands, skill args, file paths) and assistant reasoning text. Default is condensed, high-level per-step logging only.')
3722
3723
  .option('--id <id>', 'Pin the agent id this run identifies itself with on the platform. A project install otherwise mints one id per project and reuses it on every restart; --global/--standalone mints a fresh one per run, since two ad-hoc agents for one board must not share a row (the heartbeat is keyed on token + agent_id + agent_type, so the second would replace the first). Pass this when an agent has to keep ONE identity across restarts — a supervisor that already knows the id, or a board where it is the starred "preferred agent". Per-run only: nothing is written to disk.')
3723
3724
  .option('--name <name>', 'A human-readable name for this agent, sent with every heartbeat so the board shows which agent is live rather than a bare uuid (e.g. "ci-builder", "martins-laptop"). Per-run only, like --id: the persistent name is `agentName` in config.json (set via `init --name` / `init-config --name`), and this overrides it for one run without writing anything.')
@@ -3988,7 +3989,7 @@ credentialFlags(program
3988
3989
  // local-only branch even when .eventmodelers/config.json has valid credentials.
3989
3990
  // RALPH_AGENT_ID/RALPH_AGENT_NAME (--id/--name) are read in ralph.js's startRalph, so
3990
3991
  // they reach both node runners but not ralph.sh, which has no heartbeat to identify.
3991
- execSync(cmd, { cwd: kitDir, stdio: 'inherit', env: { ...process.env, RALPH_VERBOSE: opts.verbose ? '1' : '', RALPH_LOCAL: opts.local ? '1' : '', RALPH_HANDOVER: opts.handover ? '1' : '', RALPH_AGENT_ID: identity.agentId ?? '', RALPH_AGENT_NAME: identity.agentName ?? '', ...(typeof localAiTarget === 'string' ? { LOCAL_AI_TARGET: localAiTarget } : {}), ...(typeof opts.exec === 'string' ? { RALPH_EXEC_CMD: opts.exec } : {}) } });
3992
+ execSync(cmd, { cwd: kitDir, stdio: 'inherit', env: { ...process.env, RALPH_VERBOSE: opts.verbose ? '1' : '', RALPH_LOCAL: opts.local ? '1' : '', RALPH_HANDOVER: opts.handover ? '1' : '', RALPH_STATUS_TRIGGER: opts.statusTrigger ? String(opts.statusTrigger).trim() : '', RALPH_AGENT_ID: identity.agentId ?? '', RALPH_AGENT_NAME: identity.agentName ?? '', ...(typeof localAiTarget === 'string' ? { LOCAL_AI_TARGET: localAiTarget } : {}), ...(typeof opts.exec === 'string' ? { RALPH_EXEC_CMD: opts.exec } : {}) } });
3992
3993
  } catch (err) {
3993
3994
  process.exit(err.status || 1);
3994
3995
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "1.0.160",
3
+ "version": "1.0.162",
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": {
@@ -521,8 +521,11 @@ function hasPendingTasks(kitDir) {
521
521
 
522
522
  // The slice status this kit claims work from. A stack declares it in `lib/kit.json`
523
523
  // ({ "pickupStatus": "Handover" } for the UI kits, which build what a backend kit handed over);
524
- // absent means "Planned", the backend default.
524
+ // absent means "Planned", the backend default. `run --status-trigger <status>` (RALPH_STATUS_TRIGGER)
525
+ // overrides either for that run.
525
526
  export function readPickupStatus(kitDir) {
527
+ const override = (process.env.RALPH_STATUS_TRIGGER || '').trim();
528
+ if (override) return override;
526
529
  try {
527
530
  const s = JSON.parse(readFileSync(join(kitDir, 'lib', 'kit.json'), 'utf-8')).pickupStatus;
528
531
  if (typeof s === 'string' && s.trim()) return s.trim();
@@ -703,7 +706,7 @@ function taskRequest(planned = null, cfg = null, pickupStatus = 'Planned') {
703
706
  : 'process the pending tasks in tasks.json';
704
707
  // Stated explicitly so the agent never has to work it out from config.json: this run's effective
705
708
  // value, which is what the `ui-contract` skill (Step 4) follows. UI kits (pickup Handover) ignore it.
706
- const handover = planned && pickupStatus === 'Planned' ? `\nHandover: ${resolveHandover(cfg) ? 'on' : 'off'}\n` : '';
709
+ const handover = planned && pickupStatus.toLowerCase() !== 'handover' ? `\nHandover: ${resolveHandover(cfg) ? 'on' : 'off'}\n` : '';
707
710
  return `\n\n---\n\nYour task now: ${task}, following the instructions above. Start immediately.\n${handover}`;
708
711
  }
709
712
 
@@ -41,9 +41,12 @@ has_pending_tasks() {
41
41
  [[ "$content" != "[]" && -n "$content" ]]
42
42
  }
43
43
 
44
- # Returns 0 if any JSON under .build-kit/.slices/ contains a "Planned" status
44
+ # Status this loop picks work up from — `run --status-trigger <status>` (RALPH_STATUS_TRIGGER), default Planned
45
+ PICKUP_STATUS="${RALPH_STATUS_TRIGGER:-Planned}"
46
+
47
+ # Returns 0 if any JSON under .build-kit/.slices/ contains a slice in the pickup status
45
48
  has_planned_slices() {
46
- grep -rqi '"status"[[:space:]]*:[[:space:]]*"planned"' "$KIT_DIR/.slices/" --include='index.json' 2>/dev/null
49
+ grep -rqi "\"status\"[[:space:]]*:[[:space:]]*\"$PICKUP_STATUS\"" "$KIT_DIR/.slices/" --include='index.json' 2>/dev/null
47
50
  }
48
51
 
49
52
  # Returns the title of the first "Planned" slice, or empty string
@@ -54,10 +57,10 @@ get_planned_slice_title() {
54
57
  title=$(node -e "
55
58
  try {
56
59
  const d = JSON.parse(require('fs').readFileSync(process.argv[1], 'utf-8'));
57
- const s = (d.slices||[]).find(s => (s.status||'').toLowerCase() === 'planned');
60
+ const s = (d.slices||[]).find(s => (s.status||'').toLowerCase() === process.argv[2].toLowerCase());
58
61
  if (s) process.stdout.write(s.slice || s.id || '');
59
62
  } catch(e) {}
60
- " "$index_file" 2>/dev/null)
63
+ " "$index_file" "$PICKUP_STATUS" 2>/dev/null)
61
64
  if [[ -n "$title" ]]; then
62
65
  echo "$title"
63
66
  return
@@ -95,7 +98,7 @@ while [[ "$ITERATIONS" -eq 0 || "$cycle" -lt "$ITERATIONS" ]]; do
95
98
 
96
99
  if has_planned_slices; then
97
100
  slice_title=$(get_planned_slice_title)
98
- run_agent "onPlannedSlice: building \"$slice_title\"..." "$(cat "$BACKEND_PROMPT_FILE"; task_request "build the Planned slice \"$slice_title\"")"
101
+ run_agent "onPlannedSlice: building \"$slice_title\"..." "$(cat "$BACKEND_PROMPT_FILE"; task_request "build the $PICKUP_STATUS slice \"$slice_title\"")"
99
102
  echo "[$(date -u +%H:%M:%S)] Slice \"$slice_title\" build complete — waiting for next slice"
100
103
  ran_something=true
101
104
  fi
@@ -631,6 +631,8 @@ Create a SCREEN node from a sketch description.
631
631
 
632
632
  **File**: `src/slices/change/api-.slices/routes.ts`
633
633
 
634
+ **One slice border per column.** Every node write (`/nodes/events`, MCP tools, the web UI) rejects a `SLICE_BORDER` whose `colId` — read from `meta.colId` or `node.data.colId` — already belongs to another `SLICE_BORDER` on the board, with `SLICE_BORDER_EXISTS` (`context.existingSliceBorderId` names the border to update or delete instead). Re-sending the same border, or creating a new one after the old one was deleted, is fine. Slice reads (`slicedata`) only ever return one slice per column, so a second border would otherwise exist on the board yet read back as an empty slice.
635
+
634
636
  ### POST `/api/org/:orgId/boards/:boardId/timelines/:timelineId/slices`
635
637
  Create a complete slice (1 column + its nodes automatically placed).
636
638
 
@@ -1175,11 +1177,12 @@ Rule of thumb: if you are about to write "Shall I…?", "Want me to…?", "A or
1175
1177
  | 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 |
1176
1178
  | to show a diagram you drew yourself — a context map, a domain story, a sketch, any picture that isn't a chart | `svg` | nothing |
1177
1179
  | to show a UML or flow diagram you write as Mermaid — a sequence, flowchart, state, ER or class diagram | `mermaid` | nothing |
1180
+ | to ask a complex question with several fields at once — a short form with text, choices, a slider, a date, checkboxes ("which environment, when, and who to notify?") | `a2ui` | `Answer to "<title>" (<action>):` + one `- label: value` line per input |
1178
1181
  | to lay out rows and columns — a comparison, a matrix (stressors × elements), a short list with a few attributes | `table` | nothing — linked cells pan the canvas |
1179
1182
  | to show a prototype you just built and uploaded (the `prototype` skill), running in the chat | `prototype` | nothing — **Full screen** opens the board's prototype player |
1180
1183
  | something the customer defined their own snippet type for (a risk card, a sign-off — see `list_snippet_types`) | `custom` | the clicked action's `answer`; `context.snippetReply.then` says what to do |
1181
1184
 
1182
- `confirm`, `poll`, `tasks` and `changes` are questions: a person answers once, then they show as answered. `link`, `image`, `code`, `report`, `walkthrough`, `screen`, `dataJourney`, `svg`, `mermaid`, `table` and `prototype` are display only and never wait for anything.
1185
+ `confirm`, `poll`, `tasks`, `changes` and `a2ui` are questions: a person answers once, then they show as answered. `link`, `image`, `code`, `report`, `walkthrough`, `screen`, `dataJourney`, `svg`, `mermaid`, `table` and `prototype` are display only and never wait for anything.
1183
1186
 
1184
1187
  ### Shapes
1185
1188
  ```typescript
@@ -1230,6 +1233,12 @@ Rule of thumb: if you are about to write "Shall I…?", "Want me to…?", "A or
1230
1233
  width: number, height: number, title?: string }> } // a click zooms the canvas to nodeId; title = tooltip (else the element's title)
1231
1234
  { kind: 'mermaid', source: string, // Mermaid source, no ``` fence (≤ 60000 chars); the app draws it
1232
1235
  title?: string, caption?: string }
1236
+ { kind: 'a2ui', title?: string, // an A2UI v0.9 form (https://a2ui.org), basic catalog subset
1237
+ components: Array<{ id: string, component: string, ... }>, // flat list, ≤ 150; one has id 'root'; children refer to ids, no cycles
1238
+ data?: object } // initial data model; inputs bind to it with value: { path: '/form/x' }
1239
+ // Text{text, variant?} Row/Column/List{children:[id], align?} Card{child} Divider TextField{label, value?, variant?: 'shortText'|'longText'|'number'}
1240
+ // CheckBox{label, value?} ChoicePicker{label, options:[{label,value}], value?:[…], variant?:'mutuallyExclusive'} Slider{label, min, max, value?}
1241
+ // DateTimeInput{label, enableDate?, enableTime?} Button{child:<Text id>, variant?:'primary', action:{event:{name, context?}}}
1233
1242
  { kind: 'table', title?: string, caption?: string,
1234
1243
  columns: Array<string | { label: string, nodeId?: string, // 1–12; nodeId makes the header a link (a matrix with one column per element)
1235
1244
  align?: 'left' | 'center' | 'right' }>,
@@ -1252,6 +1261,7 @@ Rule of thumb: if you are about to write "Shall I…?", "Want me to…?", "A or
1252
1261
  - **`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.
1253
1262
  - **`svg`** is for a picture you draw yourself: give the root a `viewBox` (and `width`/`height` for its natural size), inline colours and fonts — it is shown as an image, so scripts, `<a>` links and external resources do nothing. To make parts of it clickable, list them in `links`: a rectangle in the viewBox coordinates you drew in plus the element it stands for (only real ids from this board); the app lays a clickable area over each that zooms the canvas there. Prefer `report` for numbers. A domain story (who does what with which work object) is an `svg` you draw — the `domain-story` skill (modeling kit) has the procedure and the drawing style. Every SVG snippet has a download-as-PNG button.
1254
1263
  - **"Place it on the board" after you drew an SVG always means: put that picture on the board as an image** — never rebuild it from elements (stickies, lanes, a timeline), never a SCREEN, never a new snippet. `place_image` with `imageBase64` = the SVG document itself, base64-encoded, `mimeType: "image/svg+xml"`, `title` = the snippet's title: it becomes an OUTLINE (the drawing tools' image element), free-floating at `x`/`y`, in no chapter and connected to nothing. Place it where the person says, else in free space next to the chapter it is about (`get_chapter_bounds`: right of or below it). Add a white background `<rect>` first if the SVG has none, or it is unreadable on a dark board. One SVG = one image. The image on the board has no clickable areas — `links` stay in the chat.
1264
+ - **`a2ui`** is for a question that needs several answers at once — instead of five chat turns, send one form. Build it as a flat component list: a `Column` with id `root`, then the fields, then a primary `Button` whose `action.event.name` says what the click means (`submit`, `approve` …). Several buttons are fine (`approve` / `reject`); the clicked one's `name` is in the answer. The person's answer is one chat message — `Answer to "<title>" (<action>):` and a `- label: value` line per filled input, chosen options by their labels, then the action's `context` — so give every input a clear `label`. Keep it small (≤ ~8 fields) and send a meaningful `text`. Not supported (renders nothing): Image, Icon, Video, AudioPlayer, Tabs, Modal, template children (`{path, componentId}`) and function calls; a missing `root`, an unknown child id or a cycle makes the whole snippet invisible. Like every question it answers once.
1255
1265
  - **`mermaid`** is for a diagram you can write as text: a sequence diagram (`sequenceDiagram`), a flow (`flowchart LR`), states (`stateDiagram-v2`), an ER or class diagram. Send just the source; the app draws it, with a *Show source* toggle and a copy button, and shows the source when it has a syntax error — so keep the syntax plain (quote labels with special characters). Prefer it over a `code` block for any diagram: a `code` block with `language: "mermaid"` is drawn too, but `mermaid` says what you mean. It has no element links; for clickable parts draw an `svg`.
1256
1266
  - **`table`** is for anything that is rows and columns and not a chart: a comparison of options, an incidence matrix (rows = stressors, columns = elements, `true` where it hits), a short list with a few attributes per item. It computes nothing — totals are a row you add. Put element ids in `nodeId` (header or cell) so names are links; a row longer than the columns is cut, a shorter one padded, an empty one dropped. Prefer `report` for numbers you want *seen* as a trend or share, `dataJourney` for one piece of data's path.
1257
1267
  - **`prototype`** runs a prototype in the chat: the page the platform stored for `timelineId` (the `contextId` the `prototype` skill uploaded under — any timeline of that context works too), in a sandboxed iframe with **Restart** (empty event log) and **Full screen** (the same player as the timeline's ▶). Send it only after `upload` succeeded — before that it shows "No prototype has been built for this yet". The `text` is the short report of what was built; the prototype itself is the snippet.
@@ -51,7 +51,7 @@ Ask only what you cannot infer, one question per turn, with a snippet whenever t
51
51
  - `instructions`: at most 4000 characters.
52
52
 
53
53
  ### Based on a built-in kind — when the built-in snippet already looks right
54
- A type doesn't have to be custom-drawn. With `base` set to a built-in kind (`table`, `report`, `poll`, `tasks`, `confirm`, `dataJourney`, `link`, `code`, …) the type has **no template and no actions**, and the `schema` is optional. Its data is exactly that kind's JSON without `kind` (for `table`: `{ title?, columns, rows }`), and the chat draws and answers it like that kind — element links (`nodeId`), paging and its own answer buttons included.
54
+ A type doesn't have to be custom-drawn. With `base` set to a built-in kind (`table`, `report`, `poll`, `tasks`, `confirm`, `dataJourney`, `link`, `code`, `a2ui`, …) the type has **no template and no actions**, and the `schema` is optional. Its data is exactly that kind's JSON without `kind` (for `table`: `{ title?, columns, rows }`) — an `a2ui` base is the way to keep a reusable form (`{ title?, components, data? }`, see `learn-eventmodelers-api` §16), and the chat draws and answers it like that kind — element links (`nodeId`), paging and its own answer buttons included.
55
55
 
56
56
  - Prefer this whenever the person wants to keep a built-in snippet you showed, and whenever the card must **link to elements** on the board — a template's sandboxed frame can't.
57
57
  - The `instructions` carry what makes it reusable: what to collect each time and how to fill the kind's fields (*"One row per chapter (CHAPTER elements), sorted by title; column 'Chapter'; each cell `{text: <title>, nodeId: <id>}`."*).
@@ -315,6 +315,7 @@ for it. Instead:
315
315
  - **"draw" / "sketch" / "diagram" something** (a UML diagram, a flow, a class model) → a snippet in the chat (`mermaid` for a sequence/flow/state/ER/class diagram written as
316
316
  Mermaid, `svg` for a picture you draw yourself, or a `code` block with 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
+ - **a question with several fields** (environment + date + who to notify, a small form) → `a2ui`, one form instead of several chat turns;
318
319
  - **rows and columns** — a comparison, a matrix, a short list with a few attributes per item → `table`.
319
320
  - **a prototype you just built** (the `/prototype` skill) → `prototype`, the uploaded page running in the chat with a Full screen button;
320
321
  - anything the customer defined **their own snippet type** for → `custom` with its `ref`: `list_snippet_types`