@eventmodelers/cli 1.0.117 → 1.0.119

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
@@ -436,6 +436,21 @@ different thing, and `anthropicBaseUrl` below is that path — but note that it
436
436
  speaking Anthropic's own `/v1/messages`, which Ollama does not serve. Pointing it straight at
437
437
  `localhost:11434` gets you a 404; a translating proxy has to sit in between.
438
438
 
439
+ ### Asking for credentials again
440
+
441
+ A modeling kit installed in the project never asks on its own: `run --modeling` takes whatever
442
+ the `.eventmodelers/config.json` walk turns up. To replace those credentials (another account,
443
+ a rotated token, a different board) pass `--force`:
444
+
445
+ ```bash
446
+ npx @eventmodelers/cli run --modeling --force
447
+ ```
448
+
449
+ It re-runs the same credentials prompt as `init --modeling`, overwrites the project's
450
+ `.eventmodelers/config.json`, and then starts the loop with the new values. With the global
451
+ install, `--force` drops the "keep the stored credentials" choice so the board's credentials have
452
+ to be answered afresh. `init-config` (no flags) does the same re-ask without starting a loop.
453
+
439
454
  ### Running headless, in a container
440
455
 
441
456
  `run` normally confirms which board it is about to drive and where that board's credentials
package/cli.js CHANGED
@@ -1716,7 +1716,7 @@ async function fetchDefaultBoardId(baseUrl, token) {
1716
1716
  // .eventmodelers/config.json up the tree beat ~/.eventmodelers/config.json. So
1717
1717
  // `run --standalone --board-id <uuid>` is enough for a board used before, and any run can
1718
1718
  // be pointed somewhere else entirely with --token/--organization-id.
1719
- async function resolveModelingCredentials(cwd, flags, explicitConfigPath, print, nonInteractive = false) {
1719
+ async function resolveModelingCredentials(cwd, flags, explicitConfigPath, print, nonInteractive = false, force = false) {
1720
1720
  const walked = loadEffectiveConfig(cwd, null, explicitConfigPath).config;
1721
1721
  const explicit = Object.fromEntries(Object.entries(flags ?? {}).filter(([, v]) => v));
1722
1722
 
@@ -1795,7 +1795,8 @@ async function resolveModelingCredentials(cwd, flags, explicitConfigPath, print,
1795
1795
  { label: 'The account-wide credentials (~/.eventmodelers/config.json)', value: 'global' },
1796
1796
  { label: 'Credentials of its own — paste them now', value: 'board' },
1797
1797
  ];
1798
- if (existing) choices.unshift({ label: `Keep the credentials already stored for this board (${existing.from})`, value: 'keep' });
1798
+ // --force: the stored answer is exactly what the user wants to replace, so don't offer it.
1799
+ if (existing && !force) choices.unshift({ label: `Keep the credentials already stored for this board (${existing.from})`, value: 'keep' });
1799
1800
 
1800
1801
  const configured = existing ? 'is already configured on this machine' : "hasn't been configured on this machine yet";
1801
1802
  const choice = await selectPrompt(
@@ -1805,7 +1806,7 @@ async function resolveModelingCredentials(cwd, flags, explicitConfigPath, print,
1805
1806
  choices,
1806
1807
  // 'keep' when there is something to keep, else the pre-existing default: account-wide
1807
1808
  // when it actually holds credentials, otherwise the paste.
1808
- existing || hasAccountWide ? 0 : 1,
1809
+ (existing && !force) || hasAccountWide ? 0 : 1,
1809
1810
  );
1810
1811
 
1811
1812
  if (choice === 'keep') {
@@ -3639,6 +3640,7 @@ credentialFlags(program
3639
3640
  .option('--exec [command]', 'Fallback for any harness --agent does not cover, or other flags than its preset. Hand each prompt to an external agent command instead of the default Claude runner — for agentic harnesses that bring their own tool loop, e.g. "codex exec --full-auto", "gemini --yolo -p" or "opencode run". One process per prompt; the prompt is appended as a quoted argument and also written to the file named by RALPH_PROMPT_FILE. Bare --exec uses localAi.exec from .eventmodelers/config.json. Claude remains the default when this flag is absent. A build kit runs it via ralph-exec.js; there, with agent tracing on, include the harness\'s JSON output flag (codex exec --json, opencode run --format json, gemini --output-format stream-json) — without it no slice cost is recorded. With --modeling/--standalone/--worker it is how any agent takes board prompts and chats: the loop claims each prompt or chat message and runs the command once for it. Keep the harness in its plain-text output mode there — stdout is the turn\'s answer — the eventmodelers MCP server is registered for it on every run (opencode.json, .gemini/settings.json, or -c flags for codex; any other command logs what to register by hand), authenticating with $EVENTMODELERS_TOKEN from the env the command inherits, and `init-agents --hosts <harness>` installs the skills for a harness that does not read .claude/skills. No subagents: self-directed work is done inline.')
3640
3641
  .option('--bash', 'Use the bash-only ralph.sh loop (build-kit stacks only, no realtime)')
3641
3642
  .option('--modeling', 'Connect a modeling agent to the board: it takes prompts and chat messages and hands each to an AI — by default one Claude process kept warm across prompts, for low-latency voice/live use; --agent (or --local-ai, or --exec as the manual fallback) picks another. Runs from a modeling-kit install in this directory, or from the global install (~/.eventmodelers/kit) when there is none. Built into the CLI, not a per-project file.')
3643
+ .option('-f, --force', 'Ask for credentials again before the modeling loop starts, even when a config already has everything required. With a modeling kit in this directory it re-runs the same prompt as `init --modeling`, overwriting the project\'s .eventmodelers/config.json; with the global install it drops the "keep the stored credentials" choice, so a board\'s credentials have to be answered afresh. Only affects the modeling loop (--modeling/--standalone/--worker/--global); cannot be combined with --non-interactive or --print.')
3642
3644
  .option('--non-interactive', 'Never ask anything: take the board and credentials that resolve from flags, EVENTMODELERS_* env vars and the config files, and fail with the reason if they are incomplete instead of interviewing for them. A TTY was previously the only signal — right for CI or a supervisor, wrong for a loop started from a terminal, where stdin is a TTY nobody is watching and the run stops on a question. Only affects the modeling loop (--modeling/--standalone/--worker/--global).')
3643
3645
  .option('--standalone', 'Let the modeling agent act on board changes on its own initiative, without asking: it subscribes to the board\'s change channel and, whenever the board goes quiet after an edit — or has simply been idle for a while — it takes a turn nobody asked for, judges the model as a whole and fans the work out over parallel subagents, one per changed area (examples on a new node, specs for a new command or read model, a missing attribute along a chain, a screen, a question). Filling that detail in while the human keeps modeling is the point. A plain --modeling agent only proposes, and only to the people chatting with it: after their edits it says in the chat what it would do and waits for a yes; everyone else\'s edits are ignored. Implies --modeling.')
3644
3646
  .option('--max-agents <n>', 'Cap how many subagents a self-directed --standalone turn may dispatch at once, to bound what an unattended agent can spend per turn. The agent merges work that shares a slice or chain first, then takes the most valuable pieces up to this many and leaves the rest for a later turn. 1 makes it do the single most valuable piece itself, without spawning anything. Default 5. Ignored without --standalone — prompt turns are one piece of work by definition.', '5')
@@ -3685,6 +3687,9 @@ credentialFlags(program
3685
3687
  }
3686
3688
  // The build-kit runners claim their work from the same queue but have no addressee
3687
3689
  // filter, so the flag would silently do nothing there rather than half of what it says.
3690
+ if (opts.force && !(opts.modeling || opts.standalone || opts.worker || opts.global)) {
3691
+ console.log('ℹ️ --force only applies to the modeling loop (--modeling/--standalone/--worker/--global); for a build kit use `re-init --force` or `init-config`. Ignoring it here.');
3692
+ }
3688
3693
  if (opts.exclusive && !(opts.modeling || opts.standalone || opts.worker || opts.global)) {
3689
3694
  console.log('ℹ️ --exclusive only applies to the modeling loop (--modeling/--standalone/--worker/--global); ignoring it here.');
3690
3695
  }
@@ -3749,6 +3754,12 @@ credentialFlags(program
3749
3754
  console.error('❌ --local-ai and --exec are mutually exclusive — pick one runner.');
3750
3755
  process.exit(1);
3751
3756
  }
3757
+ // --force means "ask me", --non-interactive/--print mean "never ask" — one has to lose,
3758
+ // and silently dropping either would surprise whoever passed it.
3759
+ if (opts.force && (opts.nonInteractive || globalOpts.print)) {
3760
+ console.error(`❌ --force asks for credentials again, which --non-interactive/--print rule out — pick one.`);
3761
+ process.exit(1);
3762
+ }
3752
3763
  if (opts.local) {
3753
3764
  console.error(`❌ ${picked} has no local-only mode — it is always driven by the org-wide realtime prompt queue, so --local has no use for it.`);
3754
3765
  process.exit(1);
@@ -3777,10 +3788,31 @@ credentialFlags(program
3777
3788
  ...(opts.credentials ? parseCredentialsArg(opts.credentials) : {}),
3778
3789
  ...Object.fromEntries(Object.entries(credentialOverridesFromOpts(opts)).filter(([, v]) => v)),
3779
3790
  };
3780
- const config = await resolveModelingCredentials(cwd, flags, globalOpts.config, globalOpts.print, !!opts.nonInteractive);
3791
+ const config = await resolveModelingCredentials(cwd, flags, globalOpts.config, globalOpts.print, !!opts.nonInteractive, !!opts.force);
3781
3792
  projectDir = await ensureGlobalKit(config.baseUrl);
3782
3793
  kitDir = join(projectDir, MODELING_KIT.kitDirName);
3783
3794
  overrides = config;
3795
+ } else if (opts.force) {
3796
+ // A project install never asks on its own — runModeling just walks the config files.
3797
+ // --force re-runs init's own credentials step against the same project-root config,
3798
+ // so the loop below picks up the fresh answer through that very walk.
3799
+ const configPath = globalOpts.config
3800
+ ? resolve(projectDir, globalOpts.config)
3801
+ : join(projectDir, '.eventmodelers', 'config.json');
3802
+ const effective = loadEffectiveConfig(projectDir, kitDir, globalOpts.config);
3803
+ const cfg = await configureCredentials({
3804
+ config: effective.config,
3805
+ configPath,
3806
+ targetDir: projectDir,
3807
+ requiredFields: ['organizationId', 'token'],
3808
+ boardIdOptional: true,
3809
+ overrides: credentialOverridesFromOpts(opts),
3810
+ print: false,
3811
+ force: true,
3812
+ });
3813
+ ensureMcpRegistered(projectDir, cfg.baseUrl || DEFAULT_BASE_URL);
3814
+ ensureEnvToken(projectDir, cfg.token);
3815
+ ensureMcpEnabled(projectDir);
3784
3816
  }
3785
3817
 
3786
3818
  // Writes to a stdout pipe are asynchronous on POSIX — without waiting for this
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "1.0.117",
3
+ "version": "1.0.119",
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": {
@@ -377,6 +377,26 @@ Place a free-text markdown note in that lane the same way any other node is plac
377
377
 
378
378
  The node's content lives in `meta.description` (a plain string of markdown source) — **not** `meta.content`; that field is silently accepted and stored but never rendered, producing a visibly empty note. There is no `fields[]` array on this element type, and no separate render/sketch call is needed (unlike SCREEN/HTML_SCREEN). `node.type` comes back as `"markdown"` (lowercase) on read.
379
379
 
380
+ ### Linking to elements in markdown: `ref:<nodeId>`
381
+
382
+ Any markdown the app renders can point at an element on the board. Clicking the link zooms the canvas to that element. This works in MARKDOWN notes, element `meta.description`s (doc view and slice view) and timeline info notes.
383
+
384
+ | Write | Renders as |
385
+ |-------|------------|
386
+ | `ref:<nodeId>` | the element's **current** title, so it stays right after a rename. Use this one by default. |
387
+ | `[Visit Booked](ref:<nodeId>)` | your own label, for a different wording or a phrase in a sentence |
388
+
389
+ ```markdown
390
+ | # | Decision | Element(s) |
391
+ |---|----------|------------|
392
+ | 1 | Insert into `visits` → event | ref:3f2b8c1e-1a2b-4c3d-8e9f-0123456789ab |
393
+ ```
394
+
395
+ - `<nodeId>` is the full node uuid, taken from a read or from your own `node:created`. Never make one up: an id that isn't on the open board renders as a greyed-out `ref:3f2b8c1e…` link.
396
+ - A slice can be linked through its slice border's node id.
397
+ - Write it as a separate word (`see ref:<id>`, not `xref:<id>`). Inside backticks or code blocks it stays plain text.
398
+ - Use it only where the app renders markdown. Chat message `text` is plain text, so use a snippet's `nodeId` there (`link`, `tasks`, `changes`). Comments are plain text too.
399
+
380
400
  ---
381
401
 
382
402
  ## 3. Nodes
@@ -69,6 +69,8 @@ Every pass reads the code through the same four lenses; each produces *hypothese
69
69
 
70
70
  **With a URL, walk it via `discover-storyboard`** (URL already known — don't ask again; pass the flow as guidance and ask for screenshots). Its click path is the *proposed* order and its screens go into the chapter as evidence; confirm both against the API and persistence lenses. Submit forms only against a local or test system, or after the person agrees.
71
71
 
72
+ If the code is a generic engine (templates, schemas, configurable entities), model one real case it handles, not the engine (see *Concrete Over Generic* in `eventmodeling-core-rules`).
73
+
72
74
  Where the lenses disagree (a route nobody calls, a table never written, a test for removed behaviour, a screen with no backend), that is a question for the person — not something to model.
73
75
 
74
76
  ---
@@ -158,6 +160,8 @@ Create notes with `submit_node_events` (`node:created`, `meta.type: "MARKDOWN"`,
158
160
 
159
161
  **Use tables, not prose.** Each note is mostly markdown tables. Prose is allowed only for a one-line intro, never for something that fits in a row.
160
162
 
163
+ **Link elements, don't just name them.** Wherever a note mentions an element on the board, write `ref:<nodeId>`. It renders as the element's current title, and a click zooms the canvas to it (see `/learn-eventmodelers-api` *Linking to elements in markdown*). Use `[label](ref:<nodeId>)` only when the wording must differ. Take the id from your `node:created` or from a read. An element that isn't placed yet keeps its plain *italic* name until it is, and then the row is updated.
164
+
161
165
  **References must be precise and resolvable.** Paths are relative to the analysed code root, with a line number where one exists: `src/main/java/org/acme/visit/VisitController.java:57`, plus the class and method (`VisitController#processNewVisitForm`), table, endpoint (`POST /owners/{id}/pets/{petId}/visits/new`), test (`VisitControllerTests#testProcessNewVisitFormSuccess`) or template (`templates/pets/createOrUpdateVisitForm.html`). Use folders and packages for anything bigger than one class. Never "the visit code". A line number is valid for the commit of the *Analysis history* row that wrote it; when a later pass re-reads a changed file, it updates the references into that file.
162
166
 
163
167
  ### The analysis log — column 0 of every chapter
@@ -192,7 +196,7 @@ Code root `petclinic/` · running UI: http://localhost:8080 (test account `georg
192
196
  ## Chapter decisions
193
197
  | # | Decision | Why | Source | Confirmed by |
194
198
  |---|----------|-----|--------|--------------|
195
- | 1 | Flow starts at *Pet Added* | A visit needs a pet (`Visit.petId` not null) | `Visit.java:34` | person |
199
+ | 1 | Flow starts at ref:<petAddedId> | A visit needs a pet (`Visit.petId` not null) | `Visit.java:34` | person |
196
200
 
197
201
  ## Not modeled
198
202
  | Code | Reason |
@@ -205,8 +209,8 @@ Code root `petclinic/` · running UI: http://localhost:8080 (test account `georg
205
209
  ```markdown
206
210
  | # | Decision | Element(s) | Why | Source | Confirmed by |
207
211
  |---|----------|------------|-----|--------|--------------|
208
- | 1 | Insert into `visits` → event | *Visit Booked* | the only write of the booking | `VisitController.java:57` `#processNewVisitForm` | code |
209
- | 2 | Title *Book Visit*, not *New Visit Form* | *Book Visit* | the business word | person, 2nd answer | person |
212
+ | 1 | Insert into `visits` → event | ref:<visitBookedId> | the only write of the booking | `VisitController.java:57` `#processNewVisitForm` | code |
213
+ | 2 | Title *Book Visit*, not *New Visit Form* | ref:<bookVisitId> | the business word | person, 2nd answer | person |
210
214
 
211
215
  ## Code vs. business terms
212
216
  | Code | Board |
@@ -147,16 +147,18 @@ The answer comes as a `CHAT` turn (`Please do these:` + ticked titles or ids). R
147
147
 
148
148
  Print the report below, with every finding as a `- [ ]` checkbox carrying its proposed fix, and ask which to apply. Wait for the answer.
149
149
 
150
+ Name each slice and element as `[<title>](ref:<nodeId>)` (the slice border's id for a slice). The terminal shows the title, and once the report is in a board note or an element description, a click zooms the canvas to it (see `/learn-eventmodelers-api` *Linking to elements in markdown*). A slice that exists only in code has no node, so it stays plain text.
151
+
150
152
  ```
151
153
  ## Architecture Drift — <scope: board | timeline "<title>" | context "<name>">
152
154
  Compared: <n> slices on the board, <n> slice folders in code Analysed: <ISO timestamp>
153
155
 
154
156
  ### Slices
155
157
  - [ ] <slice> — in code, not on the board → add it to the model
156
- - [ ] <slice> — board says Done, no code → set back to Planned
158
+ - [ ] [<slice>](ref:<sliceBorderId>) — board says Done, no code → set back to Planned
157
159
 
158
160
  ### Fields
159
- - [ ] <slice> / <element>: model `customerId: String`, code `custoemrId` → fix model typo
161
+ - [ ] [<slice>](ref:<sliceBorderId>) / [<element>](ref:<nodeId>): model `customerId: String`, code `custoemrId` → fix model typo
160
162
 
161
163
  ### Specifications / Screens / Edges
162
164
  - [ ] ...
@@ -165,6 +165,14 @@ Model the business process as it would work without any software first, then tra
165
165
 
166
166
  Question any step that exists only because of the system — a loading spinner, a cache refresh, a session check is not a business step and does not belong on the timeline. This is the discipline behind the Anti-Patterns below; the anti-patterns are what it looks like when this principle is skipped.
167
167
 
168
+ ## Concrete Over Generic
169
+
170
+ Looking at the model, a reader must understand how the business works. Model one real flow with real examples and real data, not a generic model that covers every case. This applies to generic platforms too: model one real case the platform handles, not the platform's own structure.
171
+
172
+ Example (a template-driven health record system):
173
+ - ❌ `Commit Composition` with fields `content`, or `name`/`category`/`entries` — still says nothing about the business.
174
+ - ✅ `Record Blood Pressure` → `Blood Pressure Recorded` with `systolic: 132`, `diastolic: 92`, `pulse: 113`, `composer: Dr. Marcus Johnson`.
175
+
168
176
  ## Anti-Patterns
169
177
 
170
178
  - **Data-loading commands**: `LoadOrders`/`FetchData` are queries, not business intent — model as a READMODEL, not a COMMAND.
@@ -209,5 +217,6 @@ Before treating a model as done, verify:
209
217
  - [ ] The timeline starts with a state-view or an automation reacting to an event — not a bare COMMAND
210
218
  - [ ] No two `state-change`/`state-change` slices are chained without a new trigger between them
211
219
  - [ ] No SCREEN is wired to more than one COMMAND (the bed — see Structural Shapes)
220
+ - [ ] The model follows one real flow with real data, not a generic one (see Concrete Over Generic)
212
221
 
213
222
  This is the fast pass — the full, deeper check is `eventmodeling-validating-event-models-checklist`.
@@ -448,7 +448,7 @@ Not delegated to a separate skill — performed directly by this orchestrating s
448
448
 
449
449
  The note's body lives in **`meta.description`** as plain markdown source — headings, lists, bold, code fences, tables all render. **Not `meta.content`** — that field is accepted and stored without error but never rendered by the board UI, producing a visibly empty note; this was caught by comparing against a note authored directly in the UI, so treat it as confirmed, not a guess. There is no separate render/sketch call (unlike SCREEN/HTML_SCREEN) and no `fields[]` array on this element type.
450
450
 
451
- **What the note should actually contain** — write for the next person (or next session) who opens this board cold, not for whoever just built it:
451
+ **What the note should actually contain** — write for the next person (or next session) who opens this board cold, not for whoever just built it. Wherever it names an element on the board, write `ref:<nodeId>`: it renders as the element's title and a click zooms the canvas to it (see `/learn-eventmodelers-api` *Linking to elements in markdown*):
452
452
  - **Scope**: what business process this chapter covers, and its entities (identity keys).
453
453
  - **Assumptions added beyond the literal brief** — anything invented to fill a gap the requirements left open, and why (e.g. adding a resolution event so a state isn't a one-way trap door).
454
454
  - **Business rules deliberately encoded as scenarios, not new events** — so a reader doesn't mistake a missing event for an oversight.
@@ -171,7 +171,7 @@ The summary lives **on the board, not in a file**: one MARKDOWN note per chapter
171
171
  2. Take the leftmost entry of `meta.timelineData.columns` as the first column (re-read the chapter after modeling — columns may have been added or reordered).
172
172
  3. Create the note: `node:created`, `meta.type: "MARKDOWN"`, `cellId = "<feedbackLaneId>-<firstColumnId>"`, title `Summary — <Chapter Name>`, body as plain markdown in **`meta.description`** (not `meta.content` — it is stored but never rendered).
173
173
 
174
- Exactly one summary note per chapter; on a re-run, update the existing one. Write it **after** the chapter's model, scenarios and review are complete, so it describes the finished shape. Use the source document's language (or `language`). Each note must read standalone for someone who has not seen the source, and carry enough detail to start specifying and building that chapter. Tables over prose, no filler.
174
+ Name every board element by link: `ref:<nodeId>` renders as the element's title and zooms the canvas to it on click (see `/learn-eventmodelers-api` *Linking to elements in markdown*). Exactly one summary note per chapter; on a re-run, update the existing one. Write it **after** the chapter's model, scenarios and review are complete, so it describes the finished shape. Use the source document's language (or `language`). Each note must read standalone for someone who has not seen the source, and carry enough detail to start specifying and building that chapter. Tables over prose, no filler.
175
175
 
176
176
  **Every chapter's note:**
177
177
 
@@ -183,7 +183,7 @@ Exactly one summary note per chapter; on a re-run, update the existing one. Writ
183
183
  | What | When | Source | Consequence of missing it |
184
184
 
185
185
  ## Business rules
186
- | Rule (verbatim thresholds) | Modeled in (command / read model) | Scenario | Source |
186
+ | Rule (verbatim thresholds) | Modeled in (`ref:<nodeId>` of the command / read model) | Scenario | Source |
187
187
 
188
188
  ## Requirements & deliverables
189
189
  Mandatory vs. optional (Muss/Kann), each with the board element that tracks it.
@@ -192,7 +192,7 @@ Mandatory vs. optional (Muss/Kann), each with the board element that tracks it.
192
192
  Forms/documents exchanged and their fields, where known.
193
193
 
194
194
  ## Elements
195
- Counts and names of the key events, commands, read models, automations, screens, and the slice list (state-change / state-view / automation). Note external parties and their translated events.
195
+ Counts and names of the key events, commands, read models, automations, screens, and the slice list (state-change / state-view / automation), each as `ref:<nodeId>`. Note external parties and their translated events.
196
196
 
197
197
  ## Open questions & risks
198
198
  | Question / risk | Source(s) | Why it matters |