@eventmodelers/cli 1.0.157 → 1.0.159
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
|
@@ -697,6 +697,7 @@ Every config field can be set via an `EVENTMODELERS_*` env var instead of the in
|
|
|
697
697
|
| `EVENTMODELERS_MODEL` | `model` |
|
|
698
698
|
| `EVENTMODELERS_SUBAGENT_MODEL` | `subagentModel` |
|
|
699
699
|
| `EVENTMODELERS_AGENT_NAME` | `agentName` |
|
|
700
|
+
| `EVENTMODELERS_POCKETBASE_URL` | `pocketbaseUrl` — on-prem only: overrides the PocketBase address the server advertises via `/api/config` (e.g. `/pb` or `https://host/pb`; a leading `/` is resolved against the base URL, replacing any path on it). Also read from `config.json`; either one beats the server's value |
|
|
700
701
|
|
|
701
702
|
```bash
|
|
702
703
|
EVENTMODELERS_ORGANIZATION_ID=... EVENTMODELERS_BOARD_ID=... EVENTMODELERS_TOKEN=... \
|
package/cli.js
CHANGED
|
@@ -437,6 +437,7 @@ const ENV_CONFIG_MAP = {
|
|
|
437
437
|
EVENTMODELERS_MODEL: 'model',
|
|
438
438
|
EVENTMODELERS_SUBAGENT_MODEL: 'subagentModel',
|
|
439
439
|
EVENTMODELERS_AGENT_NAME: 'agentName',
|
|
440
|
+
EVENTMODELERS_POCKETBASE_URL: 'pocketbaseUrl',
|
|
440
441
|
};
|
|
441
442
|
|
|
442
443
|
function applyEnvOverrides(config) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventmodelers/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.159",
|
|
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": {
|
|
@@ -16,12 +16,20 @@ const REALTIME_EVENTS_COLLECTION = 'realtime_events';
|
|
|
16
16
|
// ourselves.
|
|
17
17
|
const RECONNECT_INTERVALS_MS = [200, 300, 500, 1000, 1200, 1500, 2000];
|
|
18
18
|
|
|
19
|
+
// A path like "/pb" (client-side override) is resolved against the platform's base URL,
|
|
20
|
+
// so it replaces any path on it: https://host/api + /pb -> https://host/pb.
|
|
21
|
+
function resolvePocketBaseUrl({ pocketbaseUrl, baseUrl }) {
|
|
22
|
+
if (!pocketbaseUrl?.startsWith('/')) return pocketbaseUrl;
|
|
23
|
+
if (!baseUrl) throw new Error(`pocketbaseUrl "${pocketbaseUrl}" is a path but no baseUrl is configured to resolve it against`);
|
|
24
|
+
return new URL(pocketbaseUrl, baseUrl).href.replace(/\/+$/, '');
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
// deps.PocketBase is injectable for tests; by default it is the pocketbase SDK's client.
|
|
20
28
|
export async function createPocketBaseRealtimeAdapter(cfg, initialToken, deps = {}) {
|
|
21
29
|
const { EventSource } = await import('eventsource');
|
|
22
30
|
if (!globalThis.EventSource) globalThis.EventSource = EventSource; // PocketBase's SDK assumes a browser-style global
|
|
23
31
|
const PocketBase = deps.PocketBase ?? (await import('pocketbase')).default;
|
|
24
|
-
const pb = new PocketBase(cfg
|
|
32
|
+
const pb = new PocketBase(resolvePocketBaseUrl(cfg));
|
|
25
33
|
pb.authStore.save(initialToken, null);
|
|
26
34
|
|
|
27
35
|
// The SDK adds one EventSource listener per subscribe() call — and keeps it when the call
|
|
@@ -99,6 +99,7 @@ const ENV_CONFIG_MAP = {
|
|
|
99
99
|
EVENTMODELERS_MODEL: 'model',
|
|
100
100
|
EVENTMODELERS_SUBAGENT_MODEL: 'subagentModel',
|
|
101
101
|
EVENTMODELERS_AGENT_NAME: 'agentName',
|
|
102
|
+
EVENTMODELERS_POCKETBASE_URL: 'pocketbaseUrl',
|
|
102
103
|
};
|
|
103
104
|
|
|
104
105
|
function loadLocalConfig(kitDir) {
|
|
@@ -199,7 +200,10 @@ async function fetchPlatformConfig(local) {
|
|
|
199
200
|
const remote = await fetchJSON(`${local.baseUrl}/api/config`, {
|
|
200
201
|
headers: { 'x-token': local.token, ...agentHeaders(local) },
|
|
201
202
|
});
|
|
202
|
-
|
|
203
|
+
// A client-side pocketbaseUrl (EVENTMODELERS_POCKETBASE_URL / config.json) beats the server's:
|
|
204
|
+
// on-prem the server may advertise its internal docker address (http://pocketbase:8090) that a
|
|
205
|
+
// remote agent cannot reach.
|
|
206
|
+
return { ...local, ...remote, ...(local.pocketbaseUrl ? { pocketbaseUrl: local.pocketbaseUrl } : {}) };
|
|
203
207
|
}
|
|
204
208
|
|
|
205
209
|
// ── Realtime agent ────────────────────────────────────────────────────────────
|
|
@@ -22,7 +22,28 @@ If neither is provided, load and persist all slices without filtering.
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
-
## Step 2 — Fetch
|
|
25
|
+
## Step 2 — Fetch the slice(s) from the slicedata API
|
|
26
|
+
|
|
27
|
+
### When `sliceId` is given: fetch exactly that one slice — never all
|
|
28
|
+
|
|
29
|
+
Do **not** fetch the board's full slice data, and do not loop over contexts. Only the one slice's full
|
|
30
|
+
definition is fetched, and only that one is persisted in Step 3:
|
|
31
|
+
|
|
32
|
+
1. **Context known?** If the caller passed a `contextId` (or the chapter id), skip to 3.
|
|
33
|
+
2. **Otherwise locate it with one summary call** — `list_slices { "boardId": "<BOARD_ID>" }` (or the
|
|
34
|
+
`slicedata/slices` curl below). This is a lookup only: take the entry whose `id === sliceId` and read
|
|
35
|
+
its `contextName`. Don't persist or fan out over the rest of the list. If the id isn't in the list, stop
|
|
36
|
+
and report it.
|
|
37
|
+
3. **Resolve the context id unambiguously** (see "A name is not a safe key" below — chapters can share a
|
|
38
|
+
title), then fetch the full slice with one call:
|
|
39
|
+
`get_slice_data { "boardId": "<BOARD_ID>", "contextId": "<contextId>", "sliceId": "<sliceId>" }`
|
|
40
|
+
(curl: `GET …/slicedata?contextId=<contextId>&sliceId=<sliceId>`).
|
|
41
|
+
4. `ALL_SLICES` is then just that one slice; Step 3 persists only it.
|
|
42
|
+
|
|
43
|
+
A `sliceTitle` without an id is resolved to an id via the same one summary call (stop and ask if the
|
|
44
|
+
title matches more than one slice), then continues from step 3.
|
|
45
|
+
|
|
46
|
+
### When neither is given: load all slices
|
|
26
47
|
|
|
27
48
|
Prefer the MCP tool when `mcp__eventmodelers__*` tools are visible in this session:
|
|
28
49
|
|
|
@@ -40,6 +61,22 @@ This call stays full (no `projection`): Step 3 persists the complete slice objec
|
|
|
40
61
|
|
|
41
62
|
(`get_slice_data` requires a `contextName` or `contextId`. If the context isn't already known, list the context names in one call — `mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "MODEL_CONTEXT", "projection": "line" }` (only titles needed; a timeline name works as `contextName` too, so fall back to `type: "CHAPTER"` with `projection: "line"` when there are no MODEL_CONTEXT nodes) — then map slices to contexts with `list_slices { "boardId": "<BOARD_ID>", "contextName": "<name>" }` per context. Don't `get_node` each `SLICE_BORDER`.)
|
|
42
63
|
|
|
64
|
+
**A name is not a safe key — chapters and contexts can share a title.** The backend resolves `contextName`
|
|
65
|
+
to the *first* chapter/context whose title matches (case-insensitive) and silently returns nothing
|
|
66
|
+
if that one holds no slices — even when a same-titled twin holds them. A `SLICE_NOT_FOUND` (404) or an
|
|
67
|
+
empty `slices: []` for a slice that `list_slices` clearly shows is therefore **not** proof the slice is
|
|
68
|
+
missing. Before trusting it:
|
|
69
|
+
|
|
70
|
+
1. List the chapters (`get_nodes { "boardId": "<BOARD_ID>", "type": "CHAPTER", "projection": "line" }`)
|
|
71
|
+
and the MODEL_CONTEXT nodes. If two or more share the title in question, **warn in the output**
|
|
72
|
+
(`Duplicate chapter title "<title>": <id1>, <id2>`) and retry the full fetch with **`contextId`**
|
|
73
|
+
set to each candidate id until the slice is found — `contextId` is matched exactly, so it is
|
|
74
|
+
unambiguous.
|
|
75
|
+
2. Only if it is not found under any candidate id is the slice really absent.
|
|
76
|
+
|
|
77
|
+
Prefer `contextId` over `contextName` whenever the id is known (a calling harness that already has the
|
|
78
|
+
chapter id, or the list entry's `contextId`).
|
|
79
|
+
|
|
43
80
|
**Fallback (no MCP):**
|
|
44
81
|
|
|
45
82
|
```bash
|
|
@@ -57,7 +94,22 @@ Save the full array as `ALL_SLICES`.
|
|
|
57
94
|
|
|
58
95
|
## Step 3 — Persist slices to .build-kit/.slices/ directory
|
|
59
96
|
|
|
60
|
-
Apply the following logic for every slice in `ALL_SLICES
|
|
97
|
+
Apply the following logic for every slice in `ALL_SLICES` — **or, when `sliceId`/`sliceTitle` was given,
|
|
98
|
+
only for that one slice** (and only its own context's `index.json`). Don't rewrite 90 other slices'
|
|
99
|
+
folders and indexes to load one; they may hold uncommitted local changes.
|
|
100
|
+
|
|
101
|
+
**`slice.json` must be the full slice, never the list entry.** The `list_slices` / `slicedata/slices`
|
|
102
|
+
payload is a summary (id, title, status, type, context name — no commands, events, fields, specs or
|
|
103
|
+
comments). If the full fetch (Step 2) for a requested slice failed or came back without elements, do
|
|
104
|
+
**not** write the summary as `slice.json` — it reads as an empty slice and has made agents conclude
|
|
105
|
+
"nothing to build". Stop and report the failed fetch instead (see the duplicate-title handling in Step 2).
|
|
106
|
+
|
|
107
|
+
**Check for collisions before writing.** Two slices in one `<contextSlug>` whose `<sliceFolder>` is
|
|
108
|
+
identical (same title, or titles differing only by spaces/case) would overwrite each other, and so
|
|
109
|
+
would two contexts that slugify identically. Detect this before writing; on a collision, don't write
|
|
110
|
+
either, and report it: `Slice folder collision in <contextSlug>: "<title>" (<idA>, <status>) vs
|
|
111
|
+
"<title>" (<idB>, <status>)`. Also report same-titled slices that don't collide (different contexts) as
|
|
112
|
+
a one-line warning, so callers address slices by id.
|
|
61
113
|
|
|
62
114
|
### Derive paths
|
|
63
115
|
|
|
@@ -132,7 +184,9 @@ If `sliceId` was given: find the entry in `ALL_SLICES` where `id === sliceId`.
|
|
|
132
184
|
If `sliceTitle` was given: find the entry where `title` matches case-insensitively.
|
|
133
185
|
If neither: return all slices.
|
|
134
186
|
|
|
135
|
-
If a specific slice was requested but not found, stop and list the available titles
|
|
187
|
+
If a specific slice was requested but not found, stop and list the available titles — after ruling out the
|
|
188
|
+
duplicate-chapter-title case from Step 2. If several slices share the requested title, don't pick one:
|
|
189
|
+
list them with their ids, contexts and statuses and ask the caller to choose by id.
|
|
136
190
|
|
|
137
191
|
---
|
|
138
192
|
|
|
@@ -157,4 +211,11 @@ All slices (<count>) — context: <contextSlug>:
|
|
|
157
211
|
- ...
|
|
158
212
|
```
|
|
159
213
|
|
|
214
|
+
Append any warnings collected above (duplicate chapter titles, same-titled slices, folder collisions):
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
Warnings:
|
|
218
|
+
- Duplicate chapter title "Teams": d151f017-… , e985e6b3-… (slice found under e985e6b3-…)
|
|
219
|
+
```
|
|
220
|
+
|
|
160
221
|
Make the matched slice's `id`, `title`, `status`, and local folder path available to subsequent steps in the same session.
|
|
@@ -71,6 +71,37 @@ Find the slice whose `title` matches `sliceName` (case-insensitive), or whose `i
|
|
|
71
71
|
If no match is found, stop and list the available slice titles so the caller can pick one. Save the
|
|
72
72
|
matched slice's `id` as `SLICE_NODE_ID` and its current `status` as `CURRENT_STATUS`.
|
|
73
73
|
|
|
74
|
+
## Step 2.5 — Verify the slice is really empty, and check for duplicate names
|
|
75
|
+
|
|
76
|
+
"The slice has no elements" is the most common reason to block — and also the easiest to get wrong.
|
|
77
|
+
The backend resolves `contextName` to the *first* chapter/context with a matching title, so when two
|
|
78
|
+
chapters share a title, `slicedata?contextName=…` (and `slicedata/slices?contextName=…`) can return
|
|
79
|
+
nothing for a slice that is fully modeled under the twin. A comment claiming "no elements" then
|
|
80
|
+
blocks a healthy slice. Before posting anything:
|
|
81
|
+
|
|
82
|
+
1. **Fetch the slice by id, not by name.** Resolve the slice's chapter/context id and call
|
|
83
|
+
`get_slice_data { "boardId": "<BOARD_ID>", "contextId": "<id>", "sliceId": "<SLICE_NODE_ID>" }`
|
|
84
|
+
(curl: `GET …/slicedata?contextId=<id>&sliceId=<SLICE_NODE_ID>`). Fetch only this one slice.
|
|
85
|
+
2. **Check for duplicate chapter/context titles.** List chapters and MODEL_CONTEXT nodes
|
|
86
|
+
(`get_nodes { "boardId": "<BOARD_ID>", "type": "CHAPTER", "projection": "line" }`, and likewise
|
|
87
|
+
`MODEL_CONTEXT`). If two or more share the slice's chapter/context title (case-insensitive), retry
|
|
88
|
+
step 1 with each candidate `contextId` before concluding the slice is empty.
|
|
89
|
+
3. **Check for duplicate slice names.** From the `list_slices` result, note any other slice with the
|
|
90
|
+
same title (case-insensitive) — in the same or another context — with its id and status.
|
|
91
|
+
4. **Decide:**
|
|
92
|
+
- The slice has elements under some candidate id → **do not block, do not comment.** The "empty"
|
|
93
|
+
reading was a lookup artefact; go back and build using the correct `contextId`. Report the
|
|
94
|
+
duplicate-title problem to the caller instead.
|
|
95
|
+
- The slice really is empty (or contradictory) under every candidate → continue to Step 3, and
|
|
96
|
+
**name every duplicate you found in the question**, e.g. `Note: 2 timelines are titled "Teams"
|
|
97
|
+
(d151f017-…, e985e6b3-…) and a slice "Add Team" also exists in <context> (<id>, <status>) — please
|
|
98
|
+
check the content isn't under the twin.` so the human can tell a genuinely empty slice from a
|
|
99
|
+
misplaced one.
|
|
100
|
+
- Duplicates exist but the cause of the block is unrelated (e.g. a contradiction between two
|
|
101
|
+
specs): still mention them in the report (Step 5), not necessarily in the comment.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
74
105
|
## Step 3 — Post the comment
|
|
75
106
|
|
|
76
107
|
There is no separate `QUESTION` type at the API level — post a normal `COMMENT` worded as a question.
|
|
@@ -138,6 +169,7 @@ Do not continue implementing the slice after this. Report back to whoever invoke
|
|
|
138
169
|
Requested feedback on slice "<sliceName>" (<SLICE_NODE_ID>)
|
|
139
170
|
Question posted: "<question>"
|
|
140
171
|
Status: <CURRENT_STATUS> → Blocked
|
|
172
|
+
Duplicates: <none | duplicate chapter titles "<title>" (<ids>) | same-titled slices "<title>" (<id> <status>, …)>
|
|
141
173
|
```
|
|
142
174
|
|
|
143
175
|
Then stop work on this slice for this run. If the caller has other, unrelated slices queued, it may
|