@eventmodelers/cli 1.0.73 → 1.0.75
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 +15 -8
- package/cli.js +47 -29
- package/package.json +2 -2
- package/shared/build-kit/README.md +26 -9
- package/shared/build-kit/lib/local-ai-agent.js +281 -0
- package/shared/build-kit/lib/ralph.js +1 -1
- package/shared/build-kit/ralph-local-ai.js +40 -0
- package/shared/build-kit/realtime-agent.js +1 -1
- package/shared/skills/learn-eventmodelers-api/SKILL.md +4 -2
- package/stacks/blank/templates/.claude/skills/build-automation/SKILL.md +1 -1
- package/stacks/blank/templates/.claude/skills/build-state-change/SKILL.md +1 -1
- package/stacks/blank/templates/.claude/skills/build-state-view/SKILL.md +1 -1
- package/stacks/blank/templates/root/README.md +1 -1
- package/stacks/bridge/templates/bridge/lib/AGENT.md +1 -1
- package/stacks/bridge/templates/bridge/ralph-local-ai.js +43 -0
- package/stacks/modeling-kit/templates/kit/README.md +1 -1
- package/stacks/react/templates/.claude/skills/build-automation/SKILL.md +1 -1
- package/stacks/react/templates/.claude/skills/build-state-change/SKILL.md +1 -1
- package/stacks/react/templates/.claude/skills/build-state-view/SKILL.md +1 -1
- package/stacks/react/templates/build-kit/README.md +26 -9
- package/stacks/react/templates/build-kit/lib/ralph.js +1 -1
- package/stacks/react/templates/build-kit/ralph-local-ai.js +40 -0
- package/shared/build-kit/lib/ollama-agent.js +0 -147
- package/shared/build-kit/ralph-ollama.js +0 -40
- package/stacks/bridge/templates/bridge/ralph-ollama.js +0 -43
- package/stacks/cratis-csharp/templates/.claude/skills/_shared/cratis-conventions.md +0 -251
- package/stacks/cratis-csharp/templates/.claude/skills/build-automation/SKILL.md +0 -122
- package/stacks/cratis-csharp/templates/.claude/skills/build-automation/references/patterns.md +0 -115
- package/stacks/cratis-csharp/templates/.claude/skills/build-state-change/SKILL.md +0 -191
- package/stacks/cratis-csharp/templates/.claude/skills/build-state-change/references/patterns.md +0 -234
- package/stacks/cratis-csharp/templates/.claude/skills/build-state-view/SKILL.md +0 -149
- package/stacks/cratis-csharp/templates/.claude/skills/build-state-view/references/patterns.md +0 -166
- package/stacks/cratis-csharp/templates/build-kit/CLAUDE.md +0 -78
- package/stacks/cratis-csharp/templates/build-kit/lib/AGENT.md +0 -59
- package/stacks/cratis-csharp/templates/build-kit/lib/backend-prompt.md +0 -140
- package/stacks/cratis-csharp/templates/build-kit/lib/prompt.md +0 -126
- package/stacks/cratis-csharp/templates/root/.frontend/index.css +0 -29
- package/stacks/cratis-csharp/templates/root/.frontend/index.html +0 -17
- package/stacks/cratis-csharp/templates/root/.frontend/main.tsx +0 -18
- package/stacks/cratis-csharp/templates/root/.frontend/tsconfig.json +0 -42
- package/stacks/cratis-csharp/templates/root/.frontend/tsconfig.node.json +0 -11
- package/stacks/cratis-csharp/templates/root/.frontend/vite.config.ts +0 -56
- package/stacks/cratis-csharp/templates/root/App.tsx +0 -23
- package/stacks/cratis-csharp/templates/root/CratisApp.csproj +0 -25
- package/stacks/cratis-csharp/templates/root/CratisApp.sln +0 -18
- package/stacks/cratis-csharp/templates/root/GlobalUsings.cs +0 -3
- package/stacks/cratis-csharp/templates/root/Home.tsx +0 -102
- package/stacks/cratis-csharp/templates/root/Program.cs +0 -26
- package/stacks/cratis-csharp/templates/root/README.md +0 -192
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/AllListings.ts +0 -47
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/Listing.cs +0 -11
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/Listing.ts +0 -12
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/ListingDataTable.tsx +0 -17
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/index.ts +0 -1
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/Register.ts +0 -51
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/RegisterDialog.tsx +0 -18
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/Registration.cs +0 -27
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/index.ts +0 -1
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/SomeFeature.tsx +0 -22
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/SomeName.cs +0 -3
- package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/index.ts +0 -1
- package/stacks/cratis-csharp/templates/root/appsettings.Development.json +0 -9
- package/stacks/cratis-csharp/templates/root/appsettings.json +0 -26
- package/stacks/cratis-csharp/templates/root/docker-compose.yml +0 -23
- package/stacks/cratis-csharp/templates/root/package.json +0 -33
- package/stacks/cratis-csharp/templates/root/tsconfig.json +0 -3
- package/stacks/react/templates/build-kit/ralph-ollama.js +0 -40
package/README.md
CHANGED
|
@@ -18,7 +18,6 @@ Running without `--stack` shows an arrow-key picker. Or go straight to a stack:
|
|
|
18
18
|
npx @eventmodelers/cli init --stack node # Node.js / TypeScript
|
|
19
19
|
npx @eventmodelers/cli init --stack supabase # Supabase
|
|
20
20
|
npx @eventmodelers/cli init --stack axon # Axon Framework (Java/Kotlin)
|
|
21
|
-
npx @eventmodelers/cli init --stack cratis-csharp # Cratis (.NET/C#)
|
|
22
21
|
npx @eventmodelers/cli init --stack opencqrs # OpenCQRS (Java, EventSourcingDB)
|
|
23
22
|
npx @eventmodelers/cli init --stack umadb # UmaDB (Java)
|
|
24
23
|
npx @eventmodelers/cli init --stack kurrent # Kurrent (Java, KurrentDB)
|
|
@@ -79,7 +78,7 @@ your-project/
|
|
|
79
78
|
│ └── config.json ← your token + org/board (gitignored) — shared by every kit in this project
|
|
80
79
|
├── .build-kit/ ← agent runner (name is .agent-modeling-kit/ for the modeling-kit stack)
|
|
81
80
|
│ ├── ralph-claude.js ← realtime agent + task loop
|
|
82
|
-
│ ├── ralph-
|
|
81
|
+
│ ├── ralph-local-ai.js ← same, via a local/self-hosted model
|
|
83
82
|
│ ├── ralph.sh ← bash-only loop (no realtime)
|
|
84
83
|
│ ├── lib/ ← stack-specific agent prompts + helpers
|
|
85
84
|
│ └── .slices/ ← board slices, written by `fetch`/`listen` (or pre-seeded by `init --demo`)
|
|
@@ -89,11 +88,11 @@ your-project/
|
|
|
89
88
|
└── CLAUDE.md ← agent instructions
|
|
90
89
|
```
|
|
91
90
|
|
|
92
|
-
The
|
|
91
|
+
The six backend stacks (`node`, `supabase`, `axon`, `opencqrs`, `umadb`, `kurrent`) also scaffold a real project skeleton into your project root (`templates/root/`) — source layout, build files, migrations, etc.
|
|
93
92
|
|
|
94
93
|
`react` and `supabase-react` are two more registered stacks (installable the same way). `supabase-react` is real, filled-in content — a Vite + React 19 + TypeScript scaffold that authenticates and issues command POSTs via a Supabase session (`src/lib/api.ts`/`src/lib/supabase.ts`), plus `init-style-guide`/`learn-styleguide` skills so generated UI stays on-brand. It's UI-only: `.build-kit/CLAUDE.md` only routes `STATE_CHANGE`/`STATE_VIEW` slices to `build-state-change`/`build-state-view` — an `AUTOMATION` slice has no UI counterpart and gets flagged via `request-feedback` instead, since it belongs to whichever backend stack is installed alongside this one. It needs no overrides at all and uses `shared/build-kit`'s realtime agent as-is.
|
|
95
94
|
|
|
96
|
-
`react` (the plain-REST/board-polling variant, no Supabase) is still in the same state as a fresh `init --build-kit` scaffold — CLAUDE.md, the `build-*` skills, and `templates/root/` are all TODO-marked placeholders, not real content, pending an equivalent reference implementation. It overrides `lib/ralph.js` (+ `ralph-claude.js`/`ralph-
|
|
95
|
+
`react` (the plain-REST/board-polling variant, no Supabase) is still in the same state as a fresh `init --build-kit` scaffold — CLAUDE.md, the `build-*` skills, and `templates/root/` are all TODO-marked placeholders, not real content, pending an equivalent reference implementation. It overrides `lib/ralph.js` (+ `ralph-claude.js`/`ralph-local-ai.js`/`package.json`/`README.md`) for board-polling sync. Fill in the TODOs (and add a real `templates/root/` scaffold) against an actual project before relying on it.
|
|
97
96
|
|
|
98
97
|
## Skills
|
|
99
98
|
|
|
@@ -125,7 +124,7 @@ npx @eventmodelers/cli init --stack <name> # scaffold a stack + install
|
|
|
125
124
|
npx @eventmodelers/cli init --stack <name> --demo # same, plus a ready-made demo model in the kit's .slices/ to build against
|
|
126
125
|
npx @eventmodelers/cli re-init # refresh an already-installed kit's scripts/skills only — never touches the root scaffold
|
|
127
126
|
npx @eventmodelers/cli run # start the agent loop (ralph-claude.js) from the installed kit dir
|
|
128
|
-
npx @eventmodelers/cli run --
|
|
127
|
+
npx @eventmodelers/cli run --local-ai [target] # same, via a local/self-hosted model (ralph-local-ai.js)
|
|
129
128
|
npx @eventmodelers/cli run --bash # bash-only loop, no realtime (ralph.sh)
|
|
130
129
|
npx @eventmodelers/cli run --local # skip platform config/credential lookup entirely — local-only, no board sync
|
|
131
130
|
npx @eventmodelers/cli run --modeling # modeling-kit: warm Claude process driven by the board's prompt queue
|
|
@@ -162,11 +161,19 @@ npx @eventmodelers/cli init --build-kit # blank build-kit scaffold
|
|
|
162
161
|
**Building a new kit for an unsupported stack:**
|
|
163
162
|
|
|
164
163
|
```bash
|
|
165
|
-
npx @eventmodelers/cli init --build-kit
|
|
164
|
+
npx @eventmodelers/cli init --build-kit --demo
|
|
166
165
|
```
|
|
167
166
|
|
|
168
167
|
This scaffolds `.build-kit/CLAUDE.md`, `lib/prompt.md`, `lib/backend-prompt.md`, and the `build-*` skills with TODO placeholders instead of real content. Fill in the TODOs against the actual stack you're integrating (build/test commands, file layout, framework idioms) while building something real with it, then follow "Adding a stack" below to promote it to a first-class stack once it works.
|
|
169
168
|
|
|
169
|
+
`--demo` is what gives you something to build *against* while you do that: it seeds the kit's `.slices/` with the ready-made 16-slice **Understanding Eventsourcing** model (see "Trying it out before you have a board of your own" above), so you don't need a board, credentials, or a connected project to exercise the kit you're writing. With it in place the whole loop is:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
npx @eventmodelers/cli run --local
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
and the agent starts building — `--local` skips platform config and credential lookup entirely, so it works straight out of `init` with nothing connected. Every TODO you fill in gets exercised on the next iteration against real slice data covering all four slice types (state change, state view, automation, translation), which is exactly the coverage a new stack's `build-*` skills need before it's worth promoting.
|
|
176
|
+
|
|
170
177
|
Installing both a build stack and `init-modeling` into the same project reuses this one `.eventmodelers/config.json` — run whichever `init` command second and it finds the existing config already satisfies the required fields and skips straight past the credential prompt.
|
|
171
178
|
|
|
172
179
|
### The modeling agent — `run --modeling` and `--standalone`
|
|
@@ -328,7 +335,7 @@ npx @eventmodelers/cli bridge
|
|
|
328
335
|
|
|
329
336
|
`init --bridge` installs a `.bridge-kit/` (mirrors `.build-kit/`'s realtime + task-queue loop) plus only the skills for the chosen `--target` (`shared/bridge/<target>/`) — a `spec-kitty` bridge never installs Kiro's skills, and vice versa. `bridge` starts the loop: on every board slice change (not just "Planned", unlike build-kit), it regenerates that framework's spec artifacts from the current board state. It doesn't build code and doesn't claim slices.
|
|
330
337
|
|
|
331
|
-
For `spec-kitty`, that sync is deterministic and stops well short of writing Spec Kitty's own artifacts — `lib/adapters/spec-kitty-adapter.js` fetches full slice detail and restates it as a plain markdown mission brief (one section per slice, its scenarios verbatim, nothing invented), then calls `spec-kitty intake --force` to install it at `.kittify/mission-brief.md`. It deliberately doesn't create the mission, write `spec.md`, or author work packages — Spec Kitty's own `/spec-kitty.specify` → `/spec-kitty.plan` → `/spec-kitty.tasks` pipeline does that, because those steps need real judgment (work package boundaries, which files a WP owns, which agent profile fits) that only makes sense with actual codebase context, which this adapter doesn't have. What it replaces is Spec Kitty's *interactive discovery interview*: `/spec-kitty.specify`'s own "Brief Context Detection" step reads `.kittify/mission-brief.md` when present and extracts requirements from it instead of asking the user, so the event model — not a live Q&A — becomes the input. No LLM call happens in this adapter's own path, and `bridge` picks it automatically whenever a target has one (`--claude` forces the Claude runner instead). Targets without a static adapter yet fall back to Claude re-running `bridge-<target>-specify`; pass `--
|
|
338
|
+
For `spec-kitty`, that sync is deterministic and stops well short of writing Spec Kitty's own artifacts — `lib/adapters/spec-kitty-adapter.js` fetches full slice detail and restates it as a plain markdown mission brief (one section per slice, its scenarios verbatim, nothing invented), then calls `spec-kitty intake --force` to install it at `.kittify/mission-brief.md`. It deliberately doesn't create the mission, write `spec.md`, or author work packages — Spec Kitty's own `/spec-kitty.specify` → `/spec-kitty.plan` → `/spec-kitty.tasks` pipeline does that, because those steps need real judgment (work package boundaries, which files a WP owns, which agent profile fits) that only makes sense with actual codebase context, which this adapter doesn't have. What it replaces is Spec Kitty's *interactive discovery interview*: `/spec-kitty.specify`'s own "Brief Context Detection" step reads `.kittify/mission-brief.md` when present and extracts requirements from it instead of asking the user, so the event model — not a live Q&A — becomes the input. No LLM call happens in this adapter's own path, and `bridge` picks it automatically whenever a target has one (`--claude` forces the Claude runner instead). Targets without a static adapter yet fall back to Claude re-running `bridge-<target>-specify`; pass `--local-ai` for the local-model runner instead (same caveat as build-kit's `--local-ai`: `lib/local-ai-agent.js` is shared as-is).
|
|
332
339
|
|
|
333
340
|
Don't want the standing loop at all? `fetch` can call the same adapter for a single one-shot sync, no `.bridge-kit/` install required:
|
|
334
341
|
|
|
@@ -347,7 +354,7 @@ npx @eventmodelers/cli init --bridge --target spec-kitty --hook "git add .slices
|
|
|
347
354
|
npx @eventmodelers/cli bridge
|
|
348
355
|
```
|
|
349
356
|
|
|
350
|
-
`init --bridge --hook` persists the command to `.bridge-kit/bridge.json` — a plain, **committed** file (unlike `.eventmodelers/config.json`, which is gitignored for credentials) since the hook is project policy meant to be shared by every teammate and CI runner, not per-machine state. `bridge --hook "<command>"` overrides it for a single run without touching that file. Only one executor runs per invocation — `--
|
|
357
|
+
`init --bridge --hook` persists the command to `.bridge-kit/bridge.json` — a plain, **committed** file (unlike `.eventmodelers/config.json`, which is gitignored for credentials) since the hook is project policy meant to be shared by every teammate and CI runner, not per-machine state. `bridge --hook "<command>"` overrides it for a single run without touching that file. Only one executor runs per invocation — `--local-ai`, `--hook`, and `--claude` are mutually exclusive.
|
|
351
358
|
|
|
352
359
|
The hook command runs with `BRIDGE_TASK_COUNT`, `BRIDGE_SLICE_ID`/`_TITLE`/`_STATUS` (the most recent change in the batch), and `BRIDGE_BATCH_FILE` (path to the full batch as JSON) in its environment. It's invoked once per batch, not once per slice — any change that arrives while the hook is still running is left queued for the next batch rather than dropped.
|
|
353
360
|
|
package/cli.js
CHANGED
|
@@ -31,8 +31,8 @@ const __dirname = dirname(__filename);
|
|
|
31
31
|
|
|
32
32
|
// Each stack is a template set under stacks/<key>/templates/{.claude,root,<kitSubdir>}.
|
|
33
33
|
// Stacks with useShared:true also get shared/build-kit/* copied into their kit dir
|
|
34
|
-
// first (ralph.js, ralph-claude.js, ralph-
|
|
35
|
-
// code-export.mjs, lib/agent.sh, lib/
|
|
34
|
+
// first (ralph.js, ralph-claude.js, ralph-local-ai.js, ralph.sh, realtime-agent.js,
|
|
35
|
+
// code-export.mjs, lib/agent.sh, lib/local-ai-agent.js, package.json, README.md) —
|
|
36
36
|
// those files have no per-stack content, so they live once instead of being
|
|
37
37
|
// copy-pasted into every stack (that copy-pasting is exactly how they drifted out
|
|
38
38
|
// of sync before: a bugfix or default landing in one stack's copy but not another's).
|
|
@@ -41,6 +41,29 @@ const __dirname = dirname(__filename);
|
|
|
41
41
|
// modeling-kit (below) is the one kit that opts out of all of this (useShared:false)
|
|
42
42
|
// — it has no cold-spawn/tasks.json runtime at all, so none of shared/build-kit/*
|
|
43
43
|
// applies to it; see its own templates/kit for its (much smaller) self-contained set.
|
|
44
|
+
// --- Local-AI runner selection ------------------------------------------------
|
|
45
|
+
// One runner drives every local/self-hosted backend. What differs between Ollama,
|
|
46
|
+
// vLLM, LM Studio and llama.cpp is the wire dialect (native /api/chat vs the
|
|
47
|
+
// OpenAI-compatible /v1/chat/completions) plus a default URL — both handled inside
|
|
48
|
+
// lib/local-ai-agent.js. Splitting this per vendor would multiply the one file that
|
|
49
|
+
// is currently shared across every stack, so these are presets, not runners.
|
|
50
|
+
// Claude (ralph-claude.js) stays the default executor; this is opt-in.
|
|
51
|
+
const LOCAL_AI_TARGETS = ['ollama', 'vllm', 'lmstudio', 'llamacpp'];
|
|
52
|
+
|
|
53
|
+
// null = not requested. true = requested with no preset (the agent then falls back to
|
|
54
|
+
// LOCAL_AI_*/config, defaulting to Ollama). A string is a validated preset name.
|
|
55
|
+
function resolveLocalAiTarget(opts) {
|
|
56
|
+
const raw = opts.localAi;
|
|
57
|
+
if (raw === undefined || raw === false) return null;
|
|
58
|
+
if (raw === true) return true;
|
|
59
|
+
if (!LOCAL_AI_TARGETS.includes(raw)) {
|
|
60
|
+
console.error(`❌ Unknown --local-ai target "${raw}" — one of: ${LOCAL_AI_TARGETS.join(', ')}.`);
|
|
61
|
+
console.error(' Any other OpenAI-compatible server works by URL instead: LOCAL_AI_URL=http://host:8000/v1');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
return raw;
|
|
65
|
+
}
|
|
66
|
+
|
|
44
67
|
const STACKS = {
|
|
45
68
|
node: {
|
|
46
69
|
label: 'Node.js / TypeScript',
|
|
@@ -63,13 +86,6 @@ const STACKS = {
|
|
|
63
86
|
useShared: true,
|
|
64
87
|
needsBoardId: true,
|
|
65
88
|
},
|
|
66
|
-
'cratis-csharp': {
|
|
67
|
-
label: 'Cratis (.NET/C#)',
|
|
68
|
-
kitSubdir: 'build-kit',
|
|
69
|
-
kitDirName: '.build-kit',
|
|
70
|
-
useShared: true,
|
|
71
|
-
needsBoardId: true,
|
|
72
|
-
},
|
|
73
89
|
opencqrs: {
|
|
74
90
|
label: 'OpenCQRS (Java, EventSourcingDB)',
|
|
75
91
|
kitSubdir: 'build-kit',
|
|
@@ -93,7 +109,7 @@ const STACKS = {
|
|
|
93
109
|
},
|
|
94
110
|
// Frontend-only kits (UI-only: build STATE_CHANGE/STATE_VIEW slices, not
|
|
95
111
|
// AUTOMATION — those belong to whichever backend stack is installed alongside).
|
|
96
|
-
// react overrides lib/ralph.js (+ralph-claude.js/ralph-
|
|
112
|
+
// react overrides lib/ralph.js (+ralph-claude.js/ralph-local-ai.js/package.json/
|
|
97
113
|
// README.md) for board-polling instead of the realtime channel every other
|
|
98
114
|
// stack uses; supabase-react needs no overrides at all — it uses
|
|
99
115
|
// shared/build-kit's realtime agent as-is. react's CLAUDE.md/build-*
|
|
@@ -995,7 +1011,7 @@ async function installStack(stackKey, stackCfg, options = {}) {
|
|
|
995
1011
|
}
|
|
996
1012
|
|
|
997
1013
|
// Make scripts executable
|
|
998
|
-
for (const script of ['ralph.sh', 'lib/agent.sh', 'ralph-claude.js', 'ralph-
|
|
1014
|
+
for (const script of ['ralph.sh', 'lib/agent.sh', 'ralph-claude.js', 'ralph-local-ai.js']) {
|
|
999
1015
|
const p = join(kitDir, script);
|
|
1000
1016
|
if (existsSync(p)) {
|
|
1001
1017
|
try { execSync(`chmod +x "${p}"`); } catch {}
|
|
@@ -1103,7 +1119,7 @@ async function installStack(stackKey, stackCfg, options = {}) {
|
|
|
1103
1119
|
} else if (isModelingKit) {
|
|
1104
1120
|
console.log(' npx @eventmodelers/cli run --modeling\n');
|
|
1105
1121
|
} else {
|
|
1106
|
-
console.log(' npx @eventmodelers/cli run (--
|
|
1122
|
+
console.log(' npx @eventmodelers/cli run (--local-ai or --bash for other runners)\n');
|
|
1107
1123
|
}
|
|
1108
1124
|
console.log('Connect this project to an MCP client (Claude Code, VS Code, ...):\n');
|
|
1109
1125
|
console.log(` npx @eventmodelers/cli init-mcp\n`);
|
|
@@ -3024,7 +3040,7 @@ credentialFlags(program
|
|
|
3024
3040
|
credentialFlags(program
|
|
3025
3041
|
.command('run')
|
|
3026
3042
|
.description('Start the agent loop from the installed kit dir — build-kit stacks: ralph-claude.js (default); modeling-kit: --modeling, or --standalone, which needs no install at all')
|
|
3027
|
-
.option('--
|
|
3043
|
+
.option('--local-ai [target]', `Drive the loop with a local (or self-hosted) model instead of the default Claude runner, via ralph-local-ai.js (build-kit stacks only). Optional target preset picks the URL and wire dialect: ${LOCAL_AI_TARGETS.join(', ')} — bare --local-ai means ollama. Anything OpenAI-compatible (vLLM, LM Studio, llama.cpp, TGI) works by pointing LOCAL_AI_URL at it; see LOCAL_AI_* in the docs. Claude remains the default when this flag is absent.`)
|
|
3028
3044
|
.option('--bash', 'Use the bash-only ralph.sh loop (build-kit stacks only, no realtime)')
|
|
3029
3045
|
.option('--modeling', 'Keep one Claude process warm across prompts instead of spawning a fresh one per task, for low-latency voice/live use. 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.')
|
|
3030
3046
|
.option('--standalone', 'Let the modeling agent work the board in the background, on its own initiative: on top of direct prompts it subscribes to the board\'s change channel (like the build agents do) and, whenever the board goes quiet after an edit — or has simply been idle for a while — it takes a turn nobody asked for. Changed nodes are a notification, not the task: it 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 comment). Filling that detail in while the human keeps modeling is the point — it does not wait for the board to be finished. Implies --modeling.')
|
|
@@ -3078,13 +3094,13 @@ credentialFlags(program
|
|
|
3078
3094
|
const bridgeKitDir = installedKitDirs.find((d) => d.endsWith(BRIDGE_KIT.kitDirName)) ?? null;
|
|
3079
3095
|
// A bridge kit is not a build-kit stand-in even though it also reuses
|
|
3080
3096
|
// lib/ralph.js — it has its own `eventmodelers bridge` entrypoint (no
|
|
3081
|
-
// onPlannedSlice/--
|
|
3097
|
+
// onPlannedSlice/--local-ai/--bash support), so it's excluded here rather
|
|
3082
3098
|
// than falling through to the generic build-kit runner below.
|
|
3083
3099
|
const buildKitDir = installedKitDirs.find((d) => d !== modelingKitDir && d !== bridgeKitDir) ?? null;
|
|
3084
3100
|
|
|
3085
3101
|
// No overlap between the two stacks' runtimes: modeling-kit only ever runs the
|
|
3086
3102
|
// warm, direct-dispatch loop (--modeling); build-kit only ever runs the
|
|
3087
|
-
// cold-spawn/tasks.json loop (default, or --
|
|
3103
|
+
// cold-spawn/tasks.json loop (default, or --local-ai/--bash). Neither falls back
|
|
3088
3104
|
// to the other's mechanism, so each side is gated explicitly below rather than
|
|
3089
3105
|
// just being left to fail on a missing file.
|
|
3090
3106
|
// --standalone implies --modeling: it already refused every other runner, so there
|
|
@@ -3093,8 +3109,8 @@ credentialFlags(program
|
|
|
3093
3109
|
// no meaning for a build kit, which is scaffolded per project by definition.
|
|
3094
3110
|
if (opts.modeling || opts.standalone || opts.global) {
|
|
3095
3111
|
const picked = opts.modeling ? '--modeling' : opts.standalone ? '--standalone' : '--global';
|
|
3096
|
-
if (opts.bash || opts.
|
|
3097
|
-
console.error(`❌ ${picked} is mutually exclusive with --bash/--
|
|
3112
|
+
if (opts.bash || opts.localAi) {
|
|
3113
|
+
console.error(`❌ ${picked} is mutually exclusive with --bash/--local-ai — those select a build-kit runner, which the modeling loop has no use for.`);
|
|
3098
3114
|
process.exit(1);
|
|
3099
3115
|
}
|
|
3100
3116
|
if (opts.local) {
|
|
@@ -3143,7 +3159,7 @@ credentialFlags(program
|
|
|
3143
3159
|
if (modelingKitDir) {
|
|
3144
3160
|
console.error(`❌ A modeling-kit install (${MODELING_KIT.kitDirName}/) only runs via \`eventmodelers run --modeling\` — there is no cold-spawn/tasks.json loop for modeling-only projects.`);
|
|
3145
3161
|
} else if (bridgeKitDir) {
|
|
3146
|
-
console.error(`❌ A bridge-kit install (${BRIDGE_KIT.kitDirName}/) only runs via \`eventmodelers bridge\` — it has no --modeling/--
|
|
3162
|
+
console.error(`❌ A bridge-kit install (${BRIDGE_KIT.kitDirName}/) only runs via \`eventmodelers bridge\` — it has no --modeling/--local-ai/--bash modes.`);
|
|
3147
3163
|
} else {
|
|
3148
3164
|
console.error(`❌ No kit installed in ${cwd} — run \`eventmodelers install\` first.`);
|
|
3149
3165
|
console.error(' (A modeling agent needs no install at all: eventmodelers run --standalone --board-id <uuid>)');
|
|
@@ -3152,9 +3168,10 @@ credentialFlags(program
|
|
|
3152
3168
|
}
|
|
3153
3169
|
const kitDir = buildKitDir;
|
|
3154
3170
|
|
|
3155
|
-
const
|
|
3171
|
+
const localAiTarget = resolveLocalAiTarget(opts);
|
|
3172
|
+
const pickedCount = [opts.bash, localAiTarget !== null].filter(Boolean).length;
|
|
3156
3173
|
if (pickedCount > 1) {
|
|
3157
|
-
console.error('❌ --bash and --
|
|
3174
|
+
console.error('❌ --bash and --local-ai are mutually exclusive — pick one.');
|
|
3158
3175
|
process.exit(1);
|
|
3159
3176
|
}
|
|
3160
3177
|
|
|
@@ -3162,7 +3179,7 @@ credentialFlags(program
|
|
|
3162
3179
|
// is just a thin dispatcher so users don't have to remember the kit-dir name or which
|
|
3163
3180
|
// runner file to invoke. Users (and the agent itself, via AGENT.md) may customize these
|
|
3164
3181
|
// files freely; `run` always executes whatever is currently on disk.
|
|
3165
|
-
const runner = opts.bash ? 'ralph.sh' :
|
|
3182
|
+
const runner = opts.bash ? 'ralph.sh' : localAiTarget !== null ? 'ralph-local-ai.js' : 'ralph-claude.js';
|
|
3166
3183
|
const runnerPath = join(kitDir, runner);
|
|
3167
3184
|
if (!existsSync(runnerPath)) {
|
|
3168
3185
|
console.error(`❌ ${relative(cwd, runnerPath)} not found.`);
|
|
@@ -3172,13 +3189,13 @@ credentialFlags(program
|
|
|
3172
3189
|
console.log(`▶ Starting ${relative(cwd, runnerPath)}...\n`);
|
|
3173
3190
|
const cmd = runner.endsWith('.sh') ? `"${runnerPath}"` : `node "${runnerPath}"`;
|
|
3174
3191
|
try {
|
|
3175
|
-
// Only ralph-claude.js reads RALPH_VERBOSE — the bash loop and the
|
|
3192
|
+
// Only ralph-claude.js reads RALPH_VERBOSE — the bash loop and the local-AI executor have
|
|
3176
3193
|
// their own separate output paths with no stream-json parsing to gate. RALPH_LOCAL is
|
|
3177
3194
|
// read by all three runners (ralph.js's startRalph, and ralph.sh directly) to force the
|
|
3178
3195
|
// local-only branch even when .eventmodelers/config.json has valid credentials.
|
|
3179
3196
|
// RALPH_AGENT_ID/RALPH_AGENT_NAME (--id/--name) are read in ralph.js's startRalph, so
|
|
3180
3197
|
// they reach both node runners but not ralph.sh, which has no heartbeat to identify.
|
|
3181
|
-
execSync(cmd, { cwd: kitDir, stdio: 'inherit', env: { ...process.env, RALPH_VERBOSE: opts.verbose ? '1' : '', RALPH_LOCAL: opts.local ? '1' : '', RALPH_AGENT_ID: identity.agentId ?? '', RALPH_AGENT_NAME: identity.agentName ?? '' } });
|
|
3198
|
+
execSync(cmd, { cwd: kitDir, stdio: 'inherit', env: { ...process.env, RALPH_VERBOSE: opts.verbose ? '1' : '', RALPH_LOCAL: opts.local ? '1' : '', RALPH_AGENT_ID: identity.agentId ?? '', RALPH_AGENT_NAME: identity.agentName ?? '', ...(typeof localAiTarget === 'string' ? { LOCAL_AI_TARGET: localAiTarget } : {}) } });
|
|
3182
3199
|
} catch (err) {
|
|
3183
3200
|
process.exit(err.status || 1);
|
|
3184
3201
|
}
|
|
@@ -3186,8 +3203,8 @@ credentialFlags(program
|
|
|
3186
3203
|
|
|
3187
3204
|
program
|
|
3188
3205
|
.command('bridge')
|
|
3189
|
-
.description('Start the bridge agent loop from the installed .bridge-kit/ — translates board slice changes into another spec framework instead of building code. A deterministic adapter runs with no LLM call if one exists for the configured target (e.g. spec-kitty); otherwise Claude is the default executor. --
|
|
3190
|
-
.option('--
|
|
3206
|
+
.description('Start the bridge agent loop from the installed .bridge-kit/ — translates board slice changes into another spec framework instead of building code. A deterministic adapter runs with no LLM call if one exists for the configured target (e.g. spec-kitty); otherwise Claude is the default executor. --local-ai, --hook, or --claude override the pick.')
|
|
3207
|
+
.option('--local-ai [target]', `Use ralph-local-ai.js instead of the default runner — a local or self-hosted model. Optional target preset: ${LOCAL_AI_TARGETS.join(', ')} (bare flag means ollama).`)
|
|
3191
3208
|
.option('--hook <command>', 'Run this shell command instead of an AI agent for each batch of slice changes (e.g. commit + push .slices/ for a CI pipeline to pick up) — overrides any hook persisted via `init --bridge --hook` for this run only')
|
|
3192
3209
|
.option('--claude', 'Force the Claude runner even if a static adapter exists for this target')
|
|
3193
3210
|
.action((opts) => {
|
|
@@ -3198,8 +3215,9 @@ program
|
|
|
3198
3215
|
process.exit(1);
|
|
3199
3216
|
}
|
|
3200
3217
|
|
|
3201
|
-
|
|
3202
|
-
|
|
3218
|
+
const localAiTarget = resolveLocalAiTarget(opts);
|
|
3219
|
+
if ([localAiTarget !== null, opts.hook, opts.claude].filter(Boolean).length > 1) {
|
|
3220
|
+
console.error('❌ --local-ai, --hook, and --claude are mutually exclusive — pick one executor.');
|
|
3203
3221
|
process.exit(1);
|
|
3204
3222
|
}
|
|
3205
3223
|
|
|
@@ -3219,8 +3237,8 @@ program
|
|
|
3219
3237
|
|
|
3220
3238
|
const runner = hookCmd
|
|
3221
3239
|
? 'ralph-hook.js'
|
|
3222
|
-
:
|
|
3223
|
-
? 'ralph-
|
|
3240
|
+
: localAiTarget !== null
|
|
3241
|
+
? 'ralph-local-ai.js'
|
|
3224
3242
|
: !opts.claude && hasStaticAdapter
|
|
3225
3243
|
? 'ralph-static.js'
|
|
3226
3244
|
: 'ralph-claude.js';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventmodelers/cli",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon,
|
|
3
|
+
"version": "1.0.75",
|
|
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": {
|
|
7
7
|
"eventmodelers": "cli.js"
|
|
@@ -8,8 +8,11 @@ Ralph's runtime directory. Contains the agent loop, realtime subscription, promp
|
|
|
8
8
|
# Claude (default)
|
|
9
9
|
node .build-kit/ralph-claude.js
|
|
10
10
|
|
|
11
|
-
# Local
|
|
12
|
-
|
|
11
|
+
# Local or self-hosted model — Ollama (run `ollama serve` first)
|
|
12
|
+
LOCAL_AI_TARGET=ollama node .build-kit/ralph-local-ai.js
|
|
13
|
+
|
|
14
|
+
# …or any OpenAI-compatible server (vLLM, LM Studio, llama.cpp, TGI)
|
|
15
|
+
LOCAL_AI_TARGET=vllm LOCAL_AI_MODEL=Qwen/Qwen3-8B node .build-kit/ralph-local-ai.js
|
|
13
16
|
|
|
14
17
|
# Custom project directory (defaults to the parent of .build-kit)
|
|
15
18
|
node .build-kit/ralph-claude.js /path/to/project
|
|
@@ -22,7 +25,7 @@ node .build-kit/ralph-claude.js /path/to/project
|
|
|
22
25
|
| File | Purpose |
|
|
23
26
|
|------|---------|
|
|
24
27
|
| `ralph-claude.js` | Runs the full loop using Claude Code as the executor |
|
|
25
|
-
| `ralph-
|
|
28
|
+
| `ralph-local-ai.js` | Runs the full loop using a local/self-hosted model (Ollama, vLLM, LM Studio, llama.cpp) |
|
|
26
29
|
| `ralph.sh` | Shell-based loop — alternative to the JS entry points |
|
|
27
30
|
| `realtime-agent.js` | Standalone realtime agent — only needed to run it in a separate terminal |
|
|
28
31
|
|
|
@@ -31,7 +34,7 @@ node .build-kit/ralph-claude.js /path/to/project
|
|
|
31
34
|
| File | Purpose |
|
|
32
35
|
|------|---------|
|
|
33
36
|
| `lib/ralph.js` | Shared library — realtime agent + loop logic; imported by the entry points |
|
|
34
|
-
| `lib/
|
|
37
|
+
| `lib/local-ai-agent.js` | Local-AI executor — called by `ralph-local-ai.js`, can also run manually |
|
|
35
38
|
| `lib/agent.sh` | Thin shell wrapper around `claude` — called by `ralph.sh` |
|
|
36
39
|
| `lib/prompt.md` | Phase 1 prompt: tells Claude how to load a slice from the board |
|
|
37
40
|
| `lib/backend-prompt.md` | Phase 2 prompt: tells Claude how to build a planned slice |
|
|
@@ -41,13 +44,13 @@ node .build-kit/ralph-claude.js /path/to/project
|
|
|
41
44
|
|
|
42
45
|
**Phase 1** — triggered when `tasks.json` has entries:
|
|
43
46
|
- The realtime agent writes a task to `tasks.json` each time a `slice:changed` event arrives from the board
|
|
44
|
-
- The loop picks it up and runs Claude (or
|
|
47
|
+
- The loop picks it up and runs Claude (or a local model) with `prompt.md`
|
|
45
48
|
- Claude loads the slice data and updates `.slices/`
|
|
46
49
|
|
|
47
50
|
**Phase 2** — triggered when any file in `.slices/` contains `"status": "Planned"`:
|
|
48
51
|
- The loop runs Claude with `backend-prompt.md`
|
|
49
52
|
- Claude implements the slice in the project
|
|
50
|
-
- Phase 2 is Claude-only;
|
|
53
|
+
- Phase 2 is Claude-only; local-AI mode skips it (local-ai-agent handles its own queue)
|
|
51
54
|
|
|
52
55
|
Both phases run in a continuous loop with a 3-second idle sleep. The realtime agent runs concurrently in the same process.
|
|
53
56
|
|
|
@@ -63,13 +66,27 @@ node .build-kit/realtime-agent.js
|
|
|
63
66
|
.build-kit/ralph.sh
|
|
64
67
|
```
|
|
65
68
|
|
|
66
|
-
##
|
|
69
|
+
## Local-AI configuration
|
|
70
|
+
|
|
71
|
+
`ralph-local-ai.js` drives any local or self-hosted model that can do tool calling.
|
|
72
|
+
Claude (`ralph-claude.js`) stays the default runner — this is opt-in.
|
|
67
73
|
|
|
68
74
|
```bash
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
LOCAL_AI_TARGET=ollama # preset: ollama | vllm | lmstudio | llamacpp
|
|
76
|
+
LOCAL_AI_URL=http://host:8000/v1 # any OpenAI-compatible server (overrides the preset URL)
|
|
77
|
+
LOCAL_AI_MODEL=qwen3.5:9b # model name as the server knows it
|
|
78
|
+
LOCAL_AI_API=openai # force the wire dialect: ollama | openai (normally inferred)
|
|
79
|
+
LOCAL_AI_API_KEY=local # sent as `Authorization: Bearer` on the openai dialect
|
|
80
|
+
LOCAL_AI_NUM_CTX=32768 # ollama only — context window (default 32768)
|
|
71
81
|
```
|
|
72
82
|
|
|
83
|
+
**Do not lower `LOCAL_AI_NUM_CTX`.** The MCP tool schemas are ~16k tokens on their own.
|
|
84
|
+
Ollama's own default is 4096, which silently truncates them — the model then sees a
|
|
85
|
+
fragment of the tool list and invents tool names instead of failing, which is why the
|
|
86
|
+
default here is raised rather than left to the server. On the `openai` dialect the
|
|
87
|
+
equivalent is set when you launch the server (vLLM `--max-model-len 32768`,
|
|
88
|
+
llama.cpp `-c 32768`); an overflow there surfaces as an HTTP 400.
|
|
89
|
+
|
|
73
90
|
## Config
|
|
74
91
|
|
|
75
92
|
Credentials are stored in `.build-kit/.eventmodelers/config.json` (written by `eventmodelers init`):
|