@eventmodelers/cli 1.0.99 → 1.0.101

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
@@ -217,15 +217,25 @@ server to register by hand (`<baseUrl>/mcp`, header `x-token` from `$EVENTMODELE
217
217
  Run `init-agents --hosts <harness>` to install the skills for a harness that does not read
218
218
  `.claude/skills`. There are no subagents: self-directed work is done inline.
219
219
 
220
- **In a container.** `docker/<agent>/Dockerfile` runs the modeling agent for one coding agent
221
- (so far `docker/opencode`). Build from this directory and pass the board's credentials as env vars:
220
+ **In a container.** `docker/agent/Dockerfile` runs the modeling agent with Claude Code, OpenCode, Codex, Gemini CLI and Hermes all installed;
221
+ `AGENT` (`claude` by default, or `opencode`, `codex`, `gemini`, `hermes`) picks which one runs. Build from this directory and pass the board's
222
+ credentials as env vars:
222
223
 
223
224
  ```bash
224
- docker build -f docker/opencode/Dockerfile -t eventmodelers-agent-opencode .
225
- docker run --rm -e EVENTMODELERS_TOKEN=... -e EVENTMODELERS_ORGANIZATION_ID=... -e EVENTMODELERS_BOARD_ID=... \
226
- -e OPENCODE_API_KEY=... eventmodelers-agent-opencode --model opencode/big-pickle
225
+ docker build -f docker/agent/Dockerfile -t eventmodelers-agent .
226
+ docker run --rm -e AGENT=codex -e OPENAI_API_KEY \
227
+ -e EVENTMODELERS_TOKEN=... -e EVENTMODELERS_ORGANIZATION_ID=... -e EVENTMODELERS_BOARD_ID=... \
228
+ eventmodelers-agent --model gpt-5-codex
227
229
  ```
228
230
 
231
+ Claude authenticates with `ANTHROPIC_API_KEY`, or with your subscription via `CLAUDE_CODE_OAUTH_TOKEN` (run `claude setup-token`
232
+ once). The container is the sandbox, so the image sets `EVENTMODELERS_CODEX_NO_SANDBOX=1`, which makes the `codex` preset run
233
+ `--dangerously-bypass-approvals-and-sandbox` instead of `--full-auto`.
234
+
235
+ Keys: `OPENAI_API_KEY` (codex), `GEMINI_API_KEY` (gemini), and for Hermes a provider key such as `OPENROUTER_API_KEY`. Hermes reads MCP
236
+ servers only from `~/.hermes/config.yaml`, so the images ship the eventmodelers server there (env references, no secret); outside
237
+ Docker the CLI logs the snippet to add. Run `hermes` in a container as `-e AGENT=hermes --model <provider/model>`.
238
+
229
239
  `--agent`, `--exec`, `--local-ai` and `--bash` each pick the runner, so pass at most one of them —
230
240
  any two together are rejected. For a local or self-hosted model use `--local-ai`, not `--agent`.
231
241
 
@@ -734,7 +744,7 @@ The workflow then:
734
744
  | `images` | Builds the six agent images for `linux/amd64` and `linux/arm64`, **scans each for credentials** (`.github/scripts/scan-image.sh`), and only then pushes it |
735
745
  | `merge` | Combines the two architectures into one multi-arch image per name, tagged `:<tag>` and `:latest` |
736
746
 
737
- Images land in `docker.io/nebulit/`: `eventmodelers-agent-claude`, `eventmodelers-agent-opencode`, and `em-studio-buildkit-{claude,opencode}-{node,java}`. They run the CLI from the tagged checkout, not from npm, so they don't wait on the npm publish.
747
+ Images land in `docker.io/nebulit/`: `eventmodelers-agent` (modeling) and `em-studio-buildkit-{node,java,dotnet}` (building); each carries all three agent CLIs and `AGENT` selects one. They run the CLI from the tagged checkout, not from npm, so they don't wait on the npm publish.
738
748
 
739
749
  **One-time setup:** an `NPM_TOKEN` repo secret (an npm automation token allowed to publish `@eventmodelers/cli`). The images push with the built-in `GITHUB_TOKEN`; new GHCR packages start private, so make them public in the org's package settings if they should be pullable without a login.
740
750
 
package/cli.js CHANGED
@@ -82,6 +82,9 @@ const AGENT_PRESETS = {
82
82
  opencode: { modeling: 'opencode run --standalone --auto', build: 'opencode run --standalone --auto --format json' },
83
83
  codex: { modeling: 'codex exec --full-auto', build: 'codex exec --json --full-auto' },
84
84
  gemini: { modeling: 'gemini --yolo -p', build: 'gemini --yolo --output-format stream-json -p' },
85
+ // -q takes the prompt, so the preset ends with it (the --model flag is inserted before it, below).
86
+ // -Q is the plain-text mode a modeling turn reads its answer from; --format stream-json is the build kit's.
87
+ hermes: { modeling: 'hermes chat --yolo -Q -q', build: 'hermes chat --yolo --format stream-json -q' },
85
88
  };
86
89
 
87
90
  function printAgentPresets() {
@@ -116,7 +119,13 @@ function resolveAgentPreset(opts, loop) {
116
119
  console.error('❌ --agent is mutually exclusive with --local-ai/--bash — pick one runner.');
117
120
  process.exit(1);
118
121
  }
119
- return AGENT_PRESETS[name]?.[loop] ?? null;
122
+ const cmd = AGENT_PRESETS[name]?.[loop] ?? null;
123
+ // The docker images set this: Codex's own sandbox (landlock) can't start inside a container, and the
124
+ // container already is the sandbox — the flag Codex documents for externally sandboxed environments.
125
+ if (name === 'codex' && cmd && process.env.EVENTMODELERS_CODEX_NO_SANDBOX === '1') {
126
+ return cmd.replace('--full-auto', '--dangerously-bypass-approvals-and-sandbox');
127
+ }
128
+ return cmd;
120
129
  }
121
130
 
122
131
  const STACKS = {
@@ -3624,7 +3633,11 @@ credentialFlags(program
3624
3633
  console.error('❌ --model does not apply to --exec — put the harness\'s own model flag in the command (e.g. --exec "opencode run --standalone -m <id>"), or use --agent.');
3625
3634
  process.exit(1);
3626
3635
  }
3627
- if (model && agentExec) opts.exec = `${agentExec} -m '${model.replace(/'/g, `'\\''`)}'`;
3636
+ if (model && agentExec) {
3637
+ const modelFlag = `-m '${model.replace(/'/g, `'\\''`)}'`;
3638
+ // A trailing -q takes the prompt as its value, so the model flag has to come before it.
3639
+ opts.exec = agentExec.endsWith(' -q') ? `${agentExec.slice(0, -3)}${modelFlag} -q` : `${agentExec} ${modelFlag}`;
3640
+ }
3628
3641
  else if (model && resolveLocalAiTarget(opts) !== null) process.env.LOCAL_AI_MODEL = model;
3629
3642
  // The default Claude runner: the build kit's ralph-claude.js reads RALPH_MODEL from the env it
3630
3643
  // inherits; the modeling loop takes it as runModeling's `model`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "1.0.99",
3
+ "version": "1.0.101",
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": {
@@ -1,8 +1,9 @@
1
- // Registers the eventmodelers MCP server with an external agent harness (OpenCode, Gemini, Codex),
1
+ // Registers the eventmodelers MCP server with an external agent harness (OpenCode, Gemini, Codex, Hermes),
2
2
  // for both loops that hand turns to one: the modeling loop (lib/modeling-exec.js) and the build
3
3
  // loop (ralph-exec.js).
4
4
 
5
5
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
6
+ import { homedir } from 'os';
6
7
  import { dirname, join } from 'path';
7
8
 
8
9
  // POSIX single-quote escaping: close, insert an escaped quote, reopen. A turn is multi-line
@@ -60,6 +61,18 @@ const MCP_REGISTRATIONS = {
60
61
  });
61
62
  return { command, file: ok ? '.gemini/settings.json' : null, manual: ok ? null : `add mcpServers.eventmodelers = ${JSON.stringify(entry)} to .gemini/settings.json` };
62
63
  },
64
+ // Hermes reads MCP servers from ~/.hermes/config.yaml only (there is no project-level file), so
65
+ // this never writes it — a user-wide file is not ours to rewrite. It reports the server as
66
+ // registered when the file already names it (the docker images ship it), and says what to add otherwise.
67
+ hermes: (command, _projectDir, url) => {
68
+ const file = join(process.env.HERMES_HOME || join(homedir(), '.hermes'), 'config.yaml');
69
+ const has = existsSync(file) && /^\s+eventmodelers:/m.test(readFileSync(file, 'utf8'));
70
+ return {
71
+ command,
72
+ file: null,
73
+ manual: has ? null : `add to ${file}:\nmcp_servers:\n eventmodelers:\n url: "${url}"\n headers:\n x-token: "\${EVENTMODELERS_TOKEN}"`,
74
+ };
75
+ },
63
76
  codex: (command, _projectDir, url) => ({
64
77
  // -c values are TOML; env_http_headers names the env var each header is read from.
65
78
  command: `${command} -c ${shellQuote(`mcp_servers.eventmodelers.url="${url}"`)} -c ${shellQuote('mcp_servers.eventmodelers.env_http_headers={x-token="EVENTMODELERS_TOKEN",x-agent-id="EVENTMODELERS_AGENT_ID"}')}`,
@@ -89,7 +102,7 @@ export function registerMcp(command, projectDir, baseUrl, log) {
89
102
  return { command, registered: false };
90
103
  }
91
104
  const reg = MCP_REGISTRATIONS[harness](command, projectDir, url);
92
- if (reg.manual) log(`MCP: could not update the ${harness} config (not plain JSON) — ${reg.manual}`);
105
+ if (reg.manual) log(`MCP: could not update the ${harness} config${harness === 'hermes' ? '' : ' (not plain JSON)'} — ${reg.manual}`);
93
106
  else log(`MCP: eventmodelers registered for ${harness}${reg.file ? ` in ${reg.file}` : ' via -c flags'}`);
94
107
  return { command: reg.command, registered: !reg.manual };
95
108
  }