@eventmodelers/cli 1.0.71 → 1.0.73
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 +25 -8
- package/RELEASE_NOTES.md +5 -0
- package/cli.js +211 -49
- package/package.json +1 -1
- package/shared/demo-slices/Understanding Eventsourcing/additem/slice.json +176 -0
- package/shared/demo-slices/Understanding Eventsourcing/archiveitem/slice.json +209 -0
- package/shared/demo-slices/Understanding Eventsourcing/cartitems/slice.json +236 -0
- package/shared/demo-slices/Understanding Eventsourcing/cartpublicationfailed/slice.json +65 -0
- package/shared/demo-slices/Understanding Eventsourcing/cartpublished/slice.json +65 -0
- package/shared/demo-slices/Understanding Eventsourcing/cartswithproducts/slice.json +105 -0
- package/shared/demo-slices/Understanding Eventsourcing/changedprices/slice.json +91 -0
- package/shared/demo-slices/Understanding Eventsourcing/changeinventory/slice.json +191 -0
- package/shared/demo-slices/Understanding Eventsourcing/changeprice/slice.json +220 -0
- package/shared/demo-slices/Understanding Eventsourcing/clearcart/slice.json +219 -0
- package/shared/demo-slices/Understanding Eventsourcing/config.json +2631 -0
- package/shared/demo-slices/Understanding Eventsourcing/context.json +3 -0
- package/shared/demo-slices/Understanding Eventsourcing/index.json +132 -0
- package/shared/demo-slices/Understanding Eventsourcing/inventories/slice.json +132 -0
- package/shared/demo-slices/Understanding Eventsourcing/itemadded/slice.json +137 -0
- package/shared/demo-slices/Understanding Eventsourcing/publishcart/slice.json +230 -0
- package/shared/demo-slices/Understanding Eventsourcing/removeitem/slice.json +239 -0
- package/shared/demo-slices/Understanding Eventsourcing/submitcart/slice.json +199 -0
- package/shared/demo-slices/Understanding Eventsourcing/submittedcartdata/slice.json +90 -0
- package/shared/demo-slices/current_context.json +3 -0
- package/shared/skills/learn-eventmodelers-api/SKILL.md +1 -1
- package/stacks/modeling-kit/templates/.claude/skills/update-prompt-status/SKILL.md +1 -0
- package/stacks/modeling-kit/templates/kit/CLAUDE-STANDALONE.md +14 -9
- package/stacks/modeling-kit/templates/kit/CLAUDE.md +12 -7
package/README.md
CHANGED
|
@@ -38,6 +38,16 @@ npx @eventmodelers/cli init --stack node
|
|
|
38
38
|
|
|
39
39
|
Answer the credential prompts once — the stack's scaffold, skills, and agent loop are all installed in this one step.
|
|
40
40
|
|
|
41
|
+
**Trying it out before you have a board of your own:**
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx @eventmodelers/cli init --stack node --demo
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`--demo` additionally writes a ready-made model into the kit's `.slices/` — the **Understanding Eventsourcing** context, a 16-slice shopping cart covering every slice type (state change, state view, automation, translation). It's ordinary fetched slice data in exactly the shape `fetch` writes, so the build skills, `activate-context`, `set-slice-status`, and the agent loop all work against it immediately, with no board connected. Build one with `/build-state-change` in Claude Code, or start `run` and let the agent work the queue.
|
|
48
|
+
|
|
49
|
+
Nothing downstream treats it specially: `fetch --context <name>` replaces it with your own board whenever you're ready. `--demo` is skipped (with a message) if `.slices/` already holds slices, so it can never overwrite fetched work.
|
|
50
|
+
|
|
41
51
|
**Already initialized — you just want the agent to start working the board:**
|
|
42
52
|
|
|
43
53
|
```bash
|
|
@@ -71,7 +81,8 @@ your-project/
|
|
|
71
81
|
│ ├── ralph-claude.js ← realtime agent + task loop
|
|
72
82
|
│ ├── ralph-ollama.js ← same, via local Ollama
|
|
73
83
|
│ ├── ralph.sh ← bash-only loop (no realtime)
|
|
74
|
-
│
|
|
84
|
+
│ ├── lib/ ← stack-specific agent prompts + helpers
|
|
85
|
+
│ └── .slices/ ← board slices, written by `fetch`/`listen` (or pre-seeded by `init --demo`)
|
|
75
86
|
├── .claude/
|
|
76
87
|
│ └── skills/ ← eventmodelers skills for Claude Code
|
|
77
88
|
├── src/ … (or the stack's own layout)
|
|
@@ -111,6 +122,7 @@ Which skills install depends on the chosen stack — see `stacks/<name>/template
|
|
|
111
122
|
|
|
112
123
|
```bash
|
|
113
124
|
npx @eventmodelers/cli init --stack <name> # scaffold a stack + install + configure (alias: install)
|
|
125
|
+
npx @eventmodelers/cli init --stack <name> --demo # same, plus a ready-made demo model in the kit's .slices/ to build against
|
|
114
126
|
npx @eventmodelers/cli re-init # refresh an already-installed kit's scripts/skills only — never touches the root scaffold
|
|
115
127
|
npx @eventmodelers/cli run # start the agent loop (ralph-claude.js) from the installed kit dir
|
|
116
128
|
npx @eventmodelers/cli run --ollama # same, via local Ollama (ralph-ollama.js)
|
|
@@ -130,7 +142,7 @@ npx @eventmodelers/cli config # print the fully resolved c
|
|
|
130
142
|
|
|
131
143
|
`run` is a thin dispatcher — it just finds the installed kit dir (whatever it's named for the stack) and execs the runner file already sitting in it. The agent loop's actual logic stays in the scaffolded `<kit-dir>/`, not in this package, since you (and the agent itself, via `AGENT.md`) may customize those files per project.
|
|
132
144
|
|
|
133
|
-
`fetch` calls `slicedata?contextName=<name>` for the required `--context` (full slice detail — commands/events/readmodels/screens/processors/specifications/comments), and writes `.slices/<context>/<slice>/slice.json`, `index.json`, and `context.json`. It does not fetch screen images (those only arrive via `listen`'s push, see Power users). Unlike every other command, `fetch` also works with no kit installed at all — it only needs credentials, not kit-specific files. If credentials are missing, it prompts the same way `init-config` does. `--slice-id`/`--slice-title` still fetch and persist the whole context, then just print the one slice you asked about.
|
|
145
|
+
`fetch` calls `slicedata?contextName=<name>` for the required `--context` (full slice detail — commands/events/readmodels/screens/processors/specifications/comments), and writes `.slices/<context>/<slice>/slice.json`, `index.json`, and `context.json`. It does not fetch screen images (those only arrive via `listen`'s push, see Power users). Unlike every other command, `fetch` also works with no kit installed at all — it only needs credentials, not kit-specific files. If credentials are missing, it prompts the same way `init-config` does. `--slice-id`/`--slice-title` still fetch and persist the whole context, then just print the one slice you asked about. `init --demo` seeds that same `.slices/` layout from a bundled example context instead of from a board, for trying the kit out before connecting one.
|
|
134
146
|
|
|
135
147
|
---
|
|
136
148
|
|
|
@@ -272,19 +284,22 @@ the turn's own instructions rather than being enforced from outside — the `cla
|
|
|
272
284
|
what spawns the agents — so it's a budget the agent is told to keep, not a hard ceiling.
|
|
273
285
|
|
|
274
286
|
Every event that arrives is remembered until a turn carries it — including events that land
|
|
275
|
-
while a turn is running. Its own writes come back on that same channel and
|
|
276
|
-
|
|
277
|
-
|
|
287
|
+
while a turn is running. Its own writes come back on that same channel, and each event says who
|
|
288
|
+
made it (`agent_id` from the writer's `x-agent-id` header, `user_id` for a browser), so the
|
|
289
|
+
agent's own echo is recognized exactly: a burst that is nothing but its own writes is dropped
|
|
290
|
+
without spending a turn, and one that is mixed lists its own lines marked as such. Only a write
|
|
291
|
+
that reached the platform with neither id falls back to the echo window's guess and is labelled
|
|
292
|
+
"possibly your own". The lane is damped on timing on top of that: it waits for a quiet period (but not forever), waits
|
|
278
293
|
out the echo window of its last turn, never fires twice in quick succession, and widens that
|
|
279
294
|
floor each time it answers `NOOP`, so a finished board goes quiet by itself. Override the
|
|
280
295
|
windows if the defaults don't suit your board:
|
|
281
296
|
|
|
282
297
|
| Env var | Default | What it controls |
|
|
283
298
|
|---|---|---|
|
|
284
|
-
| `EVENTMODELERS_STANDALONE_DEBOUNCE_MS` | `
|
|
299
|
+
| `EVENTMODELERS_STANDALONE_DEBOUNCE_MS` | `2500` | quiet period before buffered board changes turn into a turn — long enough to coalesce one gesture (a placement, a drag) into a single turn |
|
|
285
300
|
| `EVENTMODELERS_STANDALONE_MAX_WAIT_MS` | `90000` | cap on that quiet period, so a board being edited continuously still gets a turn |
|
|
286
|
-
| `EVENTMODELERS_STANDALONE_ECHO_WINDOW_MS` | `
|
|
287
|
-
| `EVENTMODELERS_STANDALONE_MIN_INTERVAL_MS` | `60000` | floor between two self-directed turns |
|
|
301
|
+
| `EVENTMODELERS_STANDALONE_ECHO_WINDOW_MS` | `0` (off) | after a turn, how long an *unattributed* incoming change is labelled as probably the agent's own echo. Off because every event carries `agent_id`/`user_id`, which answers the same question exactly and costs no delay; set it only on a backend whose writes arrive unattributed (the run log names the origin of every change) |
|
|
302
|
+
| `EVENTMODELERS_STANDALONE_MIN_INTERVAL_MS` | `60000` | floor between two self-directed turns — a runaway-loop guard, so a change written from a browser session skips it (a person is not a loop; another agent's write still waits) |
|
|
288
303
|
| `EVENTMODELERS_STANDALONE_BACKOFF_CAP_MS` | `900000` | ceiling that floor doubles up to while turns keep answering `NOOP` |
|
|
289
304
|
| `EVENTMODELERS_STANDALONE_IDLE_MS` | `900000` | with nothing happening at all, how long before the agent reviews the model anyway (`0` disables it) |
|
|
290
305
|
|
|
@@ -538,6 +553,8 @@ npx @eventmodelers/cli uninstall --modeling-kit # remove .agent-modeling-ki
|
|
|
538
553
|
|
|
539
554
|
Each stack lives under `stacks/<name>/templates/` with `.claude/` (skills), `root/` (spread into the project root), and either `build-kit/` (backend stacks) or `kit/` (modeling-only) for the agent runner. Files identical across all backend stacks live once in `shared/build-kit/` and get layered in automatically — only put stack-specific overrides under `stacks/<name>/templates/build-kit/`. Skills with no stack-specific content (`connect`, `learn-eventmodelers-api`, `update-slice-status`, `request-feedback`) work the same way via `shared/skills/` — a new stack gets them for free without copying anything; add a skill there only once it needs a stack-specific fork.
|
|
540
555
|
|
|
556
|
+
The demo model `init --demo` installs is shared the same way, in `shared/demo-slices/` — a verbatim `fetch --format json` tree (`current_context.json` plus `<context>/{context,index,config}.json` and `<slice>/slice.json`), copied as-is into the kit's `.slices/`. It's stack-agnostic board data, so a new stack gets `--demo` for free. To replace or extend it, `fetch` a context into an empty directory and copy the result in, then update `DEMO_CONTEXT_NAME`/`DEMO_SLICE_COUNT` in `cli.js` (used only for the line `init` prints).
|
|
557
|
+
|
|
541
558
|
Once your `init --build-kit` scaffold (see above) works against a real backend, promote it to a first-class stack:
|
|
542
559
|
|
|
543
560
|
1. Copy `.build-kit/` → `stacks/<name>/templates/build-kit/`, `.claude/skills/build-*` → `stacks/<name>/templates/.claude/skills/`, and whatever `root/` scaffold you built → `stacks/<name>/templates/root/`.
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
## v1.0.72
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
- `init --demo` seeds the kit's `.slices/` with a ready-made model — the "Understanding Eventsourcing" context, a 16-slice shopping cart covering every slice type — so the `build-*` skills, `activate-context`, `set-slice-status`, and the agent loop all have something real to work on before the project is connected to a board. It is a verbatim `fetch --format json` tree, so nothing downstream has a demo-only path and a later `fetch --context <name>` simply replaces it. Skipped with a message when `.slices/` already holds fetched slices, so it can never overwrite real board state. Works with every install mode (`--stack`, `--modeling`, `--build-kit`, `--bridge`, `--git`); modeling-kit gets it at the project root, matching where `fetch` writes for that kit.
|
|
5
|
+
|
|
1
6
|
## v1.0.56
|
|
2
7
|
|
|
3
8
|
### Features
|
package/cli.js
CHANGED
|
@@ -176,6 +176,11 @@ const BLANK_BUILD_KIT = {
|
|
|
176
176
|
|
|
177
177
|
const KIT_DIR_NAMES = [...new Set([...Object.values(STACKS), MODELING_KIT, BRIDGE_KIT, BLANK_BUILD_KIT].map((s) => s.kitDirName))];
|
|
178
178
|
|
|
179
|
+
// What `init --demo` installs, for the messages it prints. Kept in step with
|
|
180
|
+
// shared/demo-slices/ (context.json's name, and the slice folders beside it).
|
|
181
|
+
const DEMO_CONTEXT_NAME = 'Understanding Eventsourcing';
|
|
182
|
+
const DEMO_SLICE_COUNT = 16;
|
|
183
|
+
|
|
179
184
|
// Same principle Playwright MCP uses per harness: one shared server, but each coding
|
|
180
185
|
// agent has its own registration mechanism. Automate the ones with a real, verified
|
|
181
186
|
// CLI install command; for the rest, print manual steps instead of guessing at an
|
|
@@ -1009,6 +1014,14 @@ async function installStack(stackKey, stackCfg, options = {}) {
|
|
|
1009
1014
|
}
|
|
1010
1015
|
}
|
|
1011
1016
|
|
|
1017
|
+
// --- 3c. Opt-in demo model (`init --demo`) ---
|
|
1018
|
+
// After the kit dir exists (the .slices/ tree nests inside it for every kit but
|
|
1019
|
+
// modeling-kit) and before npm install/credentials, so the "demo installed" line
|
|
1020
|
+
// lands with the rest of the file copying rather than after a credential prompt.
|
|
1021
|
+
if (options.demo) {
|
|
1022
|
+
installDemoSlices({ kitDir, targetDir, stackCfg });
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1012
1025
|
// --- 4. Install kit dependencies ---
|
|
1013
1026
|
// modeling-kit's package.json has no dependencies at all — it exists purely for its
|
|
1014
1027
|
// `"type": "module"`, so lib/config.js can be ESM-imported. Running npm for that buys
|
|
@@ -1281,6 +1294,46 @@ async function configureMcp(options = {}) {
|
|
|
1281
1294
|
// standalone `init-hooks` command so all three copy/chmod/git-config identically
|
|
1282
1295
|
// instead of drifting apart — callers are responsible for checking `hooksSrc`
|
|
1283
1296
|
// exists first, since what "no template for this stack" means differs per caller.
|
|
1297
|
+
// `init --demo` — drop a ready-made .slices/ tree into the install so the build
|
|
1298
|
+
// skills (and `activate-context`/`slice-status`/the agent loop) have something real
|
|
1299
|
+
// to work on before the project is ever connected to a board. The tree under
|
|
1300
|
+
// shared/demo-slices/ is a verbatim `fetch --format json` output for the
|
|
1301
|
+
// "Understanding Eventsourcing" context (a 16-slice shopping-cart model, every slice
|
|
1302
|
+
// type represented), so it is byte-identical in shape to what a real fetch writes —
|
|
1303
|
+
// a later `fetch` just overwrites it, and nothing downstream needs a demo-only path.
|
|
1304
|
+
//
|
|
1305
|
+
// The destination mirrors `fetch`'s own resolution exactly (see the SLICES_DIR comment
|
|
1306
|
+
// there): nested under the kit dir for every kit whose code-export.mjs hardcodes
|
|
1307
|
+
// `.slices/` next to itself, and at the project root for modeling-kit, which has no
|
|
1308
|
+
// code-export.mjs and no skill reading a nested copy.
|
|
1309
|
+
function installDemoSlices({ kitDir, targetDir, stackCfg }) {
|
|
1310
|
+
const slicesDir = stackCfg.kitDirName === MODELING_KIT.kitDirName
|
|
1311
|
+
? join(targetDir, '.slices')
|
|
1312
|
+
: join(kitDir, '.slices');
|
|
1313
|
+
const rel = relative(targetDir, slicesDir) || '.slices';
|
|
1314
|
+
|
|
1315
|
+
// An existing .slices/ is fetched board state — the user's real model. The copy
|
|
1316
|
+
// below merges rather than replaces, so installing over it would leave a half-demo,
|
|
1317
|
+
// half-real tree with a current_context.json pointing at the wrong one. Refuse
|
|
1318
|
+
// instead. Deliberately not overridable by --force: that flag means "don't re-ask
|
|
1319
|
+
// about credentials", never "discard fetched work".
|
|
1320
|
+
if (existsSync(slicesDir) && readdirSync(slicesDir).length > 0) {
|
|
1321
|
+
console.log(` ℹ️ --demo skipped — ${rel}/ already has slices in it (delete it first if you really want the demo model)`);
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
const demoSrc = join(__dirname, 'shared', 'demo-slices');
|
|
1326
|
+
if (!existsSync(demoSrc)) {
|
|
1327
|
+
console.log(' ℹ️ --demo was given but this CLI build ships no shared/demo-slices/ — nothing to install');
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
console.log('📦 Installing the demo model...');
|
|
1332
|
+
copyDirContents(demoSrc, slicesDir);
|
|
1333
|
+
console.log(` ✓ Demo context "${DEMO_CONTEXT_NAME}" (${DEMO_SLICE_COUNT} slices) is active in ${rel}/`);
|
|
1334
|
+
console.log(' ℹ️ It is ordinary fetched slice data — `fetch --context <name>` replaces it with your own board whenever you are ready');
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1284
1337
|
function configureHooks({ hooksSrc, targetDir }) {
|
|
1285
1338
|
copyDirContents(hooksSrc, join(targetDir, '.githooks'));
|
|
1286
1339
|
const preCommitHook = join(targetDir, '.githooks', 'pre-commit');
|
|
@@ -1310,7 +1363,15 @@ function configureHooks({ hooksSrc, targetDir }) {
|
|
|
1310
1363
|
// per task, or a long-lived warm process) must call this first — a `.mcp.json`
|
|
1311
1364
|
// written mid-session by the process itself is too late for that same process.
|
|
1312
1365
|
// The token itself is never written to disk here — `${EVENTMODELERS_TOKEN}` is
|
|
1313
|
-
// resolved by `claude` from its own process env, which the caller must set.
|
|
1366
|
+
// resolved by `claude` from its own process env, which the caller must set. Same for
|
|
1367
|
+
// `${EVENTMODELERS_AGENT_ID}`: without it on the transport, every MCP write this agent makes
|
|
1368
|
+
// reaches the platform unattributed (board_events.agent_id null), and the board shows one
|
|
1369
|
+
// anonymous robot for it instead of this agent's name. It belongs here rather than only in the
|
|
1370
|
+
// skill's Step 3.5 because this entry is rewritten on every run — it would otherwise overwrite
|
|
1371
|
+
// the header the skill just added — and because a `.mcp.json` fixed mid-session comes too late
|
|
1372
|
+
// for the `claude` process already running. An unset var arrives at the server as the literal
|
|
1373
|
+
// `${EVENTMODELERS_AGENT_ID}` text, which it drops (it only accepts a uuid), so the entry is the
|
|
1374
|
+
// same one for a human's session and an agent's.
|
|
1314
1375
|
function ensureMcpRegistered(projectDir, baseUrl) {
|
|
1315
1376
|
const mcpConfigPath = join(projectDir, '.mcp.json');
|
|
1316
1377
|
const mcpConfig = readJsonSafe(mcpConfigPath);
|
|
@@ -1318,7 +1379,7 @@ function ensureMcpRegistered(projectDir, baseUrl) {
|
|
|
1318
1379
|
mcpConfig.mcpServers.eventmodelers = {
|
|
1319
1380
|
type: 'http',
|
|
1320
1381
|
url: `${baseUrl}/mcp`,
|
|
1321
|
-
headers: { 'x-token': '${EVENTMODELERS_TOKEN}' },
|
|
1382
|
+
headers: { 'x-token': '${EVENTMODELERS_TOKEN}', 'x-agent-id': '${EVENTMODELERS_AGENT_ID}' },
|
|
1322
1383
|
};
|
|
1323
1384
|
writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
|
|
1324
1385
|
}
|
|
@@ -1882,11 +1943,17 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
1882
1943
|
}
|
|
1883
1944
|
|
|
1884
1945
|
// A standalone session is long-lived and spends most of its life waiting, so the setup
|
|
1885
|
-
// every turn needs — read CLAUDE.md, run /connect,
|
|
1886
|
-
// startup instead of being paid by whoever happens to send the first prompt. By the
|
|
1887
|
-
// a real turn arrives the credentials are resolved and the
|
|
1888
|
-
//
|
|
1889
|
-
//
|
|
1946
|
+
// every turn needs — read CLAUDE.md, run /connect, find out which chapters exist — is done
|
|
1947
|
+
// once at startup instead of being paid by whoever happens to send the first prompt. By the
|
|
1948
|
+
// time a real turn arrives the credentials are resolved and the turn is straight into the
|
|
1949
|
+
// work. It reads only: nothing is placed, no prompt status is touched (there is no prompt_id
|
|
1950
|
+
// here), no subagent is dispatched.
|
|
1951
|
+
//
|
|
1952
|
+
// What it deliberately does *not* do is read the board. Reading every chapter up front cost a
|
|
1953
|
+
// minute and a dollar before anyone had asked for anything, on a board where a turn typically
|
|
1954
|
+
// touches one chapter — and it front-loaded the context every later turn then carries. Chapters
|
|
1955
|
+
// are read lazily instead: the first turn with business in one fetches its outline and keeps it
|
|
1956
|
+
// for the rest of the session, so the cost is paid once and only for chapters that see work.
|
|
1890
1957
|
const WARM_UP_TASK =
|
|
1891
1958
|
'This is the session warm-up, before any prompt or board change — nobody has asked for anything yet, and ' +
|
|
1892
1959
|
'there is nothing to sanitize, no prompt_id and no progress entry. Do exactly this and then stop: ' +
|
|
@@ -1894,11 +1961,13 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
1894
1961
|
'one-time reads for this session — do NOT read .agent-modeling-kit/CLAUDE-STANDALONE.md, that one still ' +
|
|
1895
1962
|
'waits for the first self-directed turn; (2) invoke /connect with the credentials above and ' +
|
|
1896
1963
|
`board=${cfg.boardId} — this is the session's one-time connect, so no later turn runs it again; ` +
|
|
1897
|
-
'(3)
|
|
1898
|
-
'
|
|
1899
|
-
'
|
|
1900
|
-
'
|
|
1901
|
-
'
|
|
1964
|
+
'(3) learn which chapters exist, and nothing beyond that: one get_chapter_bounds call returns every ' +
|
|
1965
|
+
'chapter\'s id and title. Do NOT read any chapter\'s contents here — no get_board_outline, no ' +
|
|
1966
|
+
'per-chapter get_nodes, and skip the board read /connect Step 5 would otherwise have you do. A chapter ' +
|
|
1967
|
+
'is read on the first turn that actually has business in it, and kept for the rest of the session ' +
|
|
1968
|
+
'from then on. Change nothing: no nodes, no comments, no slice statuses, no subagents. Reply ' +
|
|
1969
|
+
'<promise>READY</promise> with the chapter list — titles and how many; say nothing about what is in ' +
|
|
1970
|
+
'them, you have not looked.';
|
|
1902
1971
|
|
|
1903
1972
|
function buildWarmUpTurn() {
|
|
1904
1973
|
const header = ['SESSION_START', `board_id=${cfg.boardId}`, `organization_id=${cfg.organizationId}`].join(' ');
|
|
@@ -1910,7 +1979,7 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
1910
1979
|
function warmUpSession() {
|
|
1911
1980
|
if (warmUp) return warmUp;
|
|
1912
1981
|
if (!standalone) return (warmUp = Promise.resolve());
|
|
1913
|
-
log('warm-up: connecting and
|
|
1982
|
+
log('warm-up: connecting and listing chapters before the first turn (chapters are read on first use)');
|
|
1914
1983
|
warmingUp = true;
|
|
1915
1984
|
warmUp = sendTurn(buildWarmUpTurn())
|
|
1916
1985
|
.then((result) => log(`warm-up done — ${oneLine(result, 200) || 'session ready'}`))
|
|
@@ -2042,26 +2111,43 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
2042
2111
|
// org prompt queue, so the non-standalone case joins it too and simply throws every
|
|
2043
2112
|
// event away (see onBoardEvent). One code path either way — and whatever the backend
|
|
2044
2113
|
// later adds to these payloads lands here without a client change.
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
//
|
|
2048
|
-
//
|
|
2049
|
-
//
|
|
2050
|
-
//
|
|
2051
|
-
//
|
|
2052
|
-
//
|
|
2114
|
+
//
|
|
2115
|
+
// The edge events are deliberately *not* in this list. An edge is almost never a change
|
|
2116
|
+
// on its own: placing an element auto-connects it to its neighbours, so `edge:added`
|
|
2117
|
+
// arrives as the tail of a `node:created` this loop already woke up for — the same
|
|
2118
|
+
// gesture counted twice, and counted onto `(board)` rather than onto a node, since an
|
|
2119
|
+
// edge payload names no single node to go look at. Wiring an existing chain by hand says
|
|
2120
|
+
// nothing about the model's content either. Subscribing to them bought a second turn per
|
|
2121
|
+
// placement and nothing else, so board changes here mean node changes.
|
|
2122
|
+
const BOARD_CHANGE_EVENTS = ['node:created', 'node:changed', 'node:deleted', 'board:cleared'];
|
|
2123
|
+
|
|
2124
|
+
// Four knobs. They govern *when* a self-directed turn fires — never whether an event is
|
|
2125
|
+
// remembered: everything that arrives is buffered (see onBoardEvent), so a burst that
|
|
2053
2126
|
// straddles a turn boundary still reaches the next turn instead of being thrown away and
|
|
2054
2127
|
// leaving the agent looking at whichever single event happened to land last.
|
|
2128
|
+
// Who wrote an event is *read off the event*, not inferred from these windows: the payload
|
|
2129
|
+
// carries `agent_id` (stamped from the writer's `x-agent-id` header) and `user_id` (a
|
|
2130
|
+
// browser session), so this agent's own echo is identified exactly and dropped without
|
|
2131
|
+
// costing a turn. The windows below only cover the one case attribution can't: a write that
|
|
2132
|
+
// carries neither id.
|
|
2055
2133
|
// DEBOUNCE — one gesture (place a node, drag a column) fans out into several
|
|
2056
2134
|
// events; wait for the board to fall quiet, then send a single turn.
|
|
2057
2135
|
// MAX_WAIT — cap on that quiet period: a board someone keeps editing never falls
|
|
2058
2136
|
// quiet, and the debounce alone would slide forever.
|
|
2059
|
-
// ECHO_WINDOW — how long after a turn its own writes are expected back;
|
|
2060
|
-
// that window
|
|
2137
|
+
// ECHO_WINDOW — how long after a turn its own writes are expected back; an
|
|
2138
|
+
// *unattributed* change in that window is labelled a possible echo, and
|
|
2139
|
+
// the next turn waits it out so a write and its echo don't each get one.
|
|
2140
|
+
// Off by default: it predates the `agent_id` above, which answers the same
|
|
2141
|
+
// question exactly and for free, and every turn paid its delay to cover a
|
|
2142
|
+
// residue of unattributed writes that a stamping backend never produces.
|
|
2143
|
+
// Set it (ms) on a backend where the logs below do show unattributed
|
|
2144
|
+
// changes.
|
|
2061
2145
|
// MIN_INTERVAL — a floor between self-directed turns, so a mistake upstream can't
|
|
2062
2146
|
// become a self-feeding loop burning tokens unattended. Doubles per
|
|
2063
2147
|
// consecutive NOOP up to BACKOFF_CAP, and resets as soon as a turn
|
|
2064
|
-
// actually does something.
|
|
2148
|
+
// actually does something. A person's edit is exempt (see
|
|
2149
|
+
// dispatchStandaloneTurn): a human cannot be the loop, and waiting a
|
|
2150
|
+
// minute before reacting to them is the whole latency complaint.
|
|
2065
2151
|
// IDLE — with nothing at all happening on the board, how long before the agent
|
|
2066
2152
|
// looks the model over anyway (a BOARD_REVIEW turn). 0 disables it, and
|
|
2067
2153
|
// it answers to the same MIN_INTERVAL backoff, so a board with nothing
|
|
@@ -2070,17 +2156,22 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
2070
2156
|
const raw = Number(process.env[name]);
|
|
2071
2157
|
return Number.isFinite(raw) && raw >= 0 ? raw : fallback;
|
|
2072
2158
|
};
|
|
2073
|
-
const STANDALONE_DEBOUNCE_MS = envMs('EVENTMODELERS_STANDALONE_DEBOUNCE_MS',
|
|
2159
|
+
const STANDALONE_DEBOUNCE_MS = envMs('EVENTMODELERS_STANDALONE_DEBOUNCE_MS', 2_500);
|
|
2074
2160
|
const STANDALONE_MAX_WAIT_MS = envMs('EVENTMODELERS_STANDALONE_MAX_WAIT_MS', 90_000);
|
|
2075
|
-
const STANDALONE_ECHO_WINDOW_MS = envMs('EVENTMODELERS_STANDALONE_ECHO_WINDOW_MS',
|
|
2161
|
+
const STANDALONE_ECHO_WINDOW_MS = envMs('EVENTMODELERS_STANDALONE_ECHO_WINDOW_MS', 0);
|
|
2076
2162
|
const STANDALONE_MIN_INTERVAL_MS = envMs('EVENTMODELERS_STANDALONE_MIN_INTERVAL_MS', 60_000);
|
|
2077
2163
|
const STANDALONE_BACKOFF_CAP_MS = envMs('EVENTMODELERS_STANDALONE_BACKOFF_CAP_MS', 15 * 60_000);
|
|
2078
2164
|
const STANDALONE_IDLE_MS = envMs('EVENTMODELERS_STANDALONE_IDLE_MS', 15 * 60_000);
|
|
2079
2165
|
|
|
2080
|
-
// node_id (or '(board)') -> { types: Set<string>, count
|
|
2081
|
-
//
|
|
2082
|
-
//
|
|
2083
|
-
//
|
|
2166
|
+
// node_id (or '(board)') -> { types: Set<string>, count, own, other, maybe, person } for
|
|
2167
|
+
// everything seen since the last self-directed turn, each event counted into exactly one
|
|
2168
|
+
// origin bucket: `own` = attributed to this agent's own id, `other` = attributed to a human
|
|
2169
|
+
// or another agent, `maybe` = carries no attribution at all and landed inside the echo
|
|
2170
|
+
// window, so it *might* be this agent's. Only `maybe` is a guess.
|
|
2171
|
+
// `person` counts, alongside the bucket, the subset of `other` written from a browser
|
|
2172
|
+
// session rather than by another agent. It is what lets a human's edit skip the
|
|
2173
|
+
// MIN_INTERVAL floor: a person cannot be this agent's feedback loop, whereas two agents on
|
|
2174
|
+
// one board can ping-pong, so another agent's write keeps waiting its turn.
|
|
2084
2175
|
const observed = new Map();
|
|
2085
2176
|
let observedCount = 0;
|
|
2086
2177
|
let seqLo = null;
|
|
@@ -2097,6 +2188,16 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
2097
2188
|
return Math.min(STANDALONE_MIN_INTERVAL_MS * 2 ** noopStreak, STANDALONE_BACKOFF_CAP_MS);
|
|
2098
2189
|
}
|
|
2099
2190
|
|
|
2191
|
+
// Empties the buffer — every field of it, which is why it is one function and not five
|
|
2192
|
+
// lines repeated at each place a burst stops being pending.
|
|
2193
|
+
function resetObserved() {
|
|
2194
|
+
observed.clear();
|
|
2195
|
+
observedCount = 0;
|
|
2196
|
+
seqLo = null;
|
|
2197
|
+
seqHi = null;
|
|
2198
|
+
firstObservedAt = 0;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2100
2201
|
function onBoardEvent(type, payload) {
|
|
2101
2202
|
if (!standalone) {
|
|
2102
2203
|
if (verbose) log(`board event ${type} dropped — not running with --standalone`);
|
|
@@ -2107,12 +2208,28 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
2107
2208
|
// The warm-up turn is read-only, so a change that lands while it runs is somebody
|
|
2108
2209
|
// else's — labelling it "possibly your own write" would only teach the agent to
|
|
2109
2210
|
// discount the very edits it just came up to work on.
|
|
2110
|
-
const
|
|
2211
|
+
const guessOwn = (!!pending && !warmingUp) || draining || inEchoWindow;
|
|
2212
|
+
// Attribution beats the clock in both directions. `agent_id === ours` is this agent's own
|
|
2213
|
+
// write, certainly, whenever it comes back. Any *other* id — a person's user_id, another
|
|
2214
|
+
// agent's — is certainly not ours, which is the half the timer used to get wrong: a human
|
|
2215
|
+
// editing while this agent worked had their change written off as an echo of it.
|
|
2216
|
+
const writerAgent = payload?.agent_id || null;
|
|
2217
|
+
const writerUser = payload?.user_id || null;
|
|
2218
|
+
const origin =
|
|
2219
|
+
writerAgent && cfg.agentId && writerAgent === cfg.agentId
|
|
2220
|
+
? 'own'
|
|
2221
|
+
: writerAgent || writerUser
|
|
2222
|
+
? 'other'
|
|
2223
|
+
: guessOwn
|
|
2224
|
+
? 'maybe'
|
|
2225
|
+
: 'other';
|
|
2111
2226
|
const nodeId = payload?.node_id ?? '(board)';
|
|
2112
|
-
const entry = observed.get(nodeId) ?? { types: new Set(), count: 0,
|
|
2227
|
+
const entry = observed.get(nodeId) ?? { types: new Set(), count: 0, own: 0, other: 0, maybe: 0, person: 0 };
|
|
2113
2228
|
entry.types.add(type);
|
|
2114
2229
|
entry.count += 1;
|
|
2115
|
-
|
|
2230
|
+
entry[origin] += 1;
|
|
2231
|
+
// A browser session id and no agent id: a human at the canvas.
|
|
2232
|
+
if (writerUser && !writerAgent) entry.person += 1;
|
|
2116
2233
|
observed.set(nodeId, entry);
|
|
2117
2234
|
observedCount += 1;
|
|
2118
2235
|
if (!firstObservedAt) firstObservedAt = Date.now();
|
|
@@ -2121,7 +2238,17 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
2121
2238
|
if (seqLo === null || seq < seqLo) seqLo = seq;
|
|
2122
2239
|
if (seqHi === null || seq > seqHi) seqHi = seq;
|
|
2123
2240
|
}
|
|
2124
|
-
|
|
2241
|
+
const writtenBy =
|
|
2242
|
+
origin === 'own'
|
|
2243
|
+
? ' — own write'
|
|
2244
|
+
: origin === 'maybe'
|
|
2245
|
+
? ' — unattributed, maybe own write'
|
|
2246
|
+
: writerUser
|
|
2247
|
+
? ' — by a person'
|
|
2248
|
+
: writerAgent
|
|
2249
|
+
? ` — by agent ${writerAgent.slice(0, 8)}`
|
|
2250
|
+
: '';
|
|
2251
|
+
log(`board change: ${type} node=${nodeId}${Number.isFinite(seq) ? ` seq=${seq}` : ''}${writtenBy}`);
|
|
2125
2252
|
armStandaloneTurn(nextDelayMs());
|
|
2126
2253
|
}
|
|
2127
2254
|
|
|
@@ -2175,9 +2302,11 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
2175
2302
|
'best target for them, not a reason to wait, and the board was already quiet before this turn was ' +
|
|
2176
2303
|
'handed to you. Only board-wide sweeps and structural moves (renames, deletions, re-shaping, slice ' +
|
|
2177
2304
|
'statuses) get a comment first instead of being done. An unanswered question you posted earlier parks ' +
|
|
2178
|
-
'that one sweep, never the fill-in work. Read
|
|
2179
|
-
'get_nodes,
|
|
2180
|
-
'
|
|
2305
|
+
'that one sweep, never the fill-in work. Read what this turn needs and no more: every nodeId above in one ' +
|
|
2306
|
+
'get_nodes, plus one get_board_outline for each chapter they land in that you have not already read this ' +
|
|
2307
|
+
'session — a chapter you already hold is not fetched again, you carry it forward and apply this turn\'s ' +
|
|
2308
|
+
'changes to your copy. A full-meta read only on the nodes you conclude you will actually touch. ' +
|
|
2309
|
+
'You do the analysis: look at every entry above, decide what ' +
|
|
2181
2310
|
'actually needs doing, and then work in parallel rather than serially — dispatch one Agent per piece of ' +
|
|
2182
2311
|
'work that needs doing, all in a single message, merging pieces that share a slice or chain so no two ' +
|
|
2183
2312
|
`agents write to the same area. ${AGENT_BUDGET} Read .agent-modeling-kit/CLAUDE-STANDALONE.md now (once ` +
|
|
@@ -2186,10 +2315,17 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
2186
2315
|
'nothing, change nothing and reply <promise>NOOP</promise>.';
|
|
2187
2316
|
|
|
2188
2317
|
function buildStandaloneTurn() {
|
|
2189
|
-
const lines = [...observed.entries()].map(
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2318
|
+
const lines = [...observed.entries()].map(([nodeId, entry]) => {
|
|
2319
|
+
const origin =
|
|
2320
|
+
entry.own === entry.count
|
|
2321
|
+
? ' — YOUR OWN earlier write, echoed back'
|
|
2322
|
+
: entry.own
|
|
2323
|
+
? ` — ${entry.own} of ${entry.count} are YOUR OWN earlier writes, the rest are not`
|
|
2324
|
+
: entry.maybe === entry.count
|
|
2325
|
+
? ' — unattributed, possibly your own earlier write'
|
|
2326
|
+
: '';
|
|
2327
|
+
return `- ${nodeId}: ${[...entry.types].join(', ')} (${entry.count}×)${origin}`;
|
|
2328
|
+
});
|
|
2193
2329
|
const header = [
|
|
2194
2330
|
'BOARD_CHANGE',
|
|
2195
2331
|
`board_id=${cfg.boardId}`,
|
|
@@ -2238,6 +2374,15 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
2238
2374
|
|
|
2239
2375
|
async function dispatchStandaloneTurn({ idle = false } = {}) {
|
|
2240
2376
|
if (!idle && !observed.size) return;
|
|
2377
|
+
// Every buffered event is provably this agent's own write coming back — attribution says
|
|
2378
|
+
// so, not a timer. There is nothing new on the board, so it costs no turn; if the board
|
|
2379
|
+
// then stays quiet the idle review still comes around.
|
|
2380
|
+
if (!idle && [...observed.values()].every((entry) => entry.own === entry.count)) {
|
|
2381
|
+
log(`standalone turn skipped: ${observedCount} board event(s) on ${observed.size} node(s), all own writes`);
|
|
2382
|
+
resetObserved();
|
|
2383
|
+
armIdleReview();
|
|
2384
|
+
return;
|
|
2385
|
+
}
|
|
2241
2386
|
// A direct message always outranks the agent's own initiative — re-arm instead of
|
|
2242
2387
|
// queueing behind the prompt lane, so the buffer just keeps collecting meanwhile.
|
|
2243
2388
|
if (pending || draining) {
|
|
@@ -2245,27 +2390,38 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
2245
2390
|
else armStandaloneTurn(nextDelayMs());
|
|
2246
2391
|
return;
|
|
2247
2392
|
}
|
|
2393
|
+
// The floor is a runaway-loop guard, and a person is not a loop. Making a human wait out
|
|
2394
|
+
// MIN_INTERVAL before their edit is even looked at is most of the delay they feel, and it
|
|
2395
|
+
// guards nothing: the loop it exists to stop is this agent (or another one) reacting to a
|
|
2396
|
+
// write and writing again, which `person` excludes by construction.
|
|
2397
|
+
const byPerson = !idle && [...observed.values()].some((entry) => entry.person > 0);
|
|
2248
2398
|
const waitLeft = minIntervalMs() - (Date.now() - lastStandaloneAt);
|
|
2249
|
-
if (lastStandaloneAt && waitLeft > 0) {
|
|
2399
|
+
if (lastStandaloneAt && waitLeft > 0 && !byPerson) {
|
|
2250
2400
|
if (idle) armIdleReview();
|
|
2251
2401
|
else armStandaloneTurn(waitLeft);
|
|
2252
2402
|
return;
|
|
2253
2403
|
}
|
|
2404
|
+
if (byPerson && lastStandaloneAt && waitLeft > 0) {
|
|
2405
|
+
log(`standalone floor skipped: a person edited the board (${Math.round(waitLeft / 1000)}s of min-interval left)`);
|
|
2406
|
+
}
|
|
2254
2407
|
const text = idle ? buildIdleReviewTurn() : buildStandaloneTurn();
|
|
2255
2408
|
if (idle) {
|
|
2256
2409
|
log(`standalone review turn: board quiet for ${Math.round(STANDALONE_IDLE_MS / 1000)}s`);
|
|
2257
2410
|
} else {
|
|
2258
|
-
const
|
|
2411
|
+
const totals = [...observed.values()].reduce(
|
|
2412
|
+
(acc, entry) => ({ own: acc.own + entry.own, maybe: acc.maybe + entry.maybe }),
|
|
2413
|
+
{ own: 0, maybe: 0 },
|
|
2414
|
+
);
|
|
2415
|
+
const breakdown = [
|
|
2416
|
+
totals.own ? `${totals.own} own` : null,
|
|
2417
|
+
totals.maybe ? `${totals.maybe} unattributed` : null,
|
|
2418
|
+
].filter(Boolean);
|
|
2259
2419
|
log(
|
|
2260
2420
|
`standalone turn: ${observedCount} board event(s) on ${observed.size} node(s)` +
|
|
2261
|
-
`${
|
|
2421
|
+
`${breakdown.length ? ` (${breakdown.join(', ')})` : ''}`,
|
|
2262
2422
|
);
|
|
2263
2423
|
}
|
|
2264
|
-
|
|
2265
|
-
observedCount = 0;
|
|
2266
|
-
seqLo = null;
|
|
2267
|
-
seqHi = null;
|
|
2268
|
-
firstObservedAt = 0;
|
|
2424
|
+
resetObserved();
|
|
2269
2425
|
lastStandaloneAt = Date.now();
|
|
2270
2426
|
try {
|
|
2271
2427
|
const result = await runClaudeWarm(text);
|
|
@@ -2469,6 +2625,7 @@ credentialFlags(program
|
|
|
2469
2625
|
.option('--hook <command>', 'Persist a default shell command hook for `bridge` to run per batch of slice changes instead of Claude/Ollama (e.g. commit + push .slices/ for a CI pipeline to pick up) — only meaningful with --bridge. Can also be set per-run with `bridge --hook`.')
|
|
2470
2626
|
.option('--build-kit', 'Install a blank build-kit scaffold (.build-kit/ + .claude/skills/build-*/SKILL.md placeholders, all TODO-marked) for a stack not built into this CLI yet — no fixed backend. Mutually exclusive with --stack/--modeling/--bridge.')
|
|
2471
2627
|
.option('--hooks', 'Install the slice commit-scope guard (.githooks/pre-commit, running .build-kit/lib/check-commit-scope.cjs) and wire it up via `git config core.hooksPath .githooks` — only meaningful with --stack (build-kit stacks). Off by default.')
|
|
2628
|
+
.option('--demo', 'Install a ready-made demo model into the kit\'s .slices/ — the "Understanding Eventsourcing" context (16 shopping-cart slices covering every slice type), in exactly the shape `fetch` writes, so the build skills and the agent loop have something real to work on before this project is connected to a board. Skipped if .slices/ already holds fetched slices. Off by default.')
|
|
2472
2629
|
.option('--global', 'Install skills into ~/.claude/skills/ instead of the project — available in every project')
|
|
2473
2630
|
.option('-f, --force', 'Re-prompt for credentials even if a config already has everything required — overwrites the existing config.json')
|
|
2474
2631
|
.option(...AGENT_NAME_OPTION))
|
|
@@ -2490,6 +2647,7 @@ credentialFlags(program
|
|
|
2490
2647
|
global: opts.global,
|
|
2491
2648
|
force: opts.force,
|
|
2492
2649
|
credentialOverrides: { ...credentialOverridesFromOpts(opts), ...identityOverridesFromOpts(opts) },
|
|
2650
|
+
demo: opts.demo,
|
|
2493
2651
|
});
|
|
2494
2652
|
return;
|
|
2495
2653
|
}
|
|
@@ -2501,6 +2659,7 @@ credentialFlags(program
|
|
|
2501
2659
|
global: opts.global,
|
|
2502
2660
|
force: opts.force,
|
|
2503
2661
|
credentialOverrides: { ...credentialOverridesFromOpts(opts), ...identityOverridesFromOpts(opts) },
|
|
2662
|
+
demo: opts.demo,
|
|
2504
2663
|
});
|
|
2505
2664
|
return;
|
|
2506
2665
|
}
|
|
@@ -2520,6 +2679,7 @@ credentialFlags(program
|
|
|
2520
2679
|
global: opts.global,
|
|
2521
2680
|
force: opts.force,
|
|
2522
2681
|
credentialOverrides: { ...credentialOverridesFromOpts(opts), ...identityOverridesFromOpts(opts) },
|
|
2682
|
+
demo: opts.demo,
|
|
2523
2683
|
target: opts.target,
|
|
2524
2684
|
});
|
|
2525
2685
|
// Deliberately NOT under .bridge-kit/.eventmodelers/ — that whole name is
|
|
@@ -2558,6 +2718,7 @@ credentialFlags(program
|
|
|
2558
2718
|
global: opts.global,
|
|
2559
2719
|
force: opts.force,
|
|
2560
2720
|
credentialOverrides: { ...credentialOverridesFromOpts(opts), ...identityOverridesFromOpts(opts) },
|
|
2721
|
+
demo: opts.demo,
|
|
2561
2722
|
templatesSource: join(clonedDir, 'templates'),
|
|
2562
2723
|
hooks: opts.hooks,
|
|
2563
2724
|
});
|
|
@@ -2571,6 +2732,7 @@ credentialFlags(program
|
|
|
2571
2732
|
global: opts.global,
|
|
2572
2733
|
force: opts.force,
|
|
2573
2734
|
credentialOverrides: { ...credentialOverridesFromOpts(opts), ...identityOverridesFromOpts(opts) },
|
|
2735
|
+
demo: opts.demo,
|
|
2574
2736
|
hooks: opts.hooks,
|
|
2575
2737
|
});
|
|
2576
2738
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventmodelers/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.73",
|
|
4
4
|
"description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, Cratis, OpenCQRS, UmaDB, Kurrent, or modeling-only)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|