@eventmodelers/cli 1.0.3 → 1.0.5

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
@@ -101,6 +101,7 @@ Which skills install depends on the chosen stack — see `stacks/<name>/template
101
101
 
102
102
  ```bash
103
103
  npx @eventmodelers/cli init --stack <name> # scaffold a stack + install + configure (alias: install)
104
+ npx @eventmodelers/cli re-init # refresh an already-installed kit's scripts/skills only — never touches the root scaffold
104
105
  npx @eventmodelers/cli run # start the agent loop (ralph-claude.js) from the installed kit dir
105
106
  npx @eventmodelers/cli run --ollama # same, via local Ollama (ralph-ollama.js)
106
107
  npx @eventmodelers/cli run --bash # bash-only loop, no realtime (ralph.sh)
@@ -306,6 +307,19 @@ npx @eventmodelers/cli listen --port 4000 # same, on a different port
306
307
 
307
308
  `listen` is a dispatcher for `<kit-dir>/code-export.mjs` — a local HTTP server (port 3001 by default) that the eventmodelers board UI posts slice/screen data to, which then gets written under `<kit-dir>/.slices/`. Unlike `fetch`, it does receive screen images, since the board UI pushes them directly.
308
309
 
310
+ ### Re-init — refresh scripts/skills without touching your app
311
+
312
+ ```bash
313
+ npx @eventmodelers/cli re-init # refresh the installed build kit (.build-kit/) + its skills
314
+ npx @eventmodelers/cli re-init --modeling # refresh the modeling kit (.agent-modeling-kit/) + its skills
315
+ ```
316
+
317
+ `re-init` re-runs `init` against whichever stack `install-manifest.json` says was installed (no need to pass `--stack` again), but skips step 2 of `init` entirely — the root project scaffold (`package.json`, `src/`, `server.ts`, `docker-compose.yml`, etc.) and the root `CLAUDE.md` router are never touched. Use it after upgrading the CLI to pick up fixes to `ralph.js`/`ralph.sh`/skills without re-scaffolding a project you've since built on top of.
318
+
319
+ Credentials are left alone unless you pass `--force` — same rule `init` already follows when everything required is already configured. `--global` defaults to however skills were originally installed; pass it explicitly to move them.
320
+
321
+ If the kit dir predates install-manifest.json tracking, or was installed via `init --git <url>` (a community/custom stack, not one of the built-in `STACKS` keys), `re-init` can't tell what to re-copy and tells you to re-run the original `init` command by hand instead.
322
+
309
323
  ### Uninstall
310
324
 
311
325
  Every `init`/`init-modeling` run writes an install manifest into `<kit-dir>/.eventmodelers/install-manifest.json` recording exactly what it put down. `uninstall` reads that manifest back and removes only:
package/cli.js CHANGED
@@ -754,6 +754,10 @@ async function installStack(stackKey, stackCfg, options = {}) {
754
754
  }
755
755
 
756
756
  // --- 2. Spread stack scaffold files into the project root ---
757
+ // Skipped entirely by `re-init` (options.skipRootScaffold) — that command only
758
+ // refreshes an already-scaffolded project's kit dir + skills, and must never
759
+ // re-touch root/ files the user has since built on top of, nor the root
760
+ // CLAUDE.md router below.
757
761
  const rootSrc = join(templatesSource, 'root');
758
762
  // root/CLAUDE.md is never copied to the project root directly (see step 3 below) —
759
763
  // built-in stacks no longer ship one at all, and an outdated community/--git stack
@@ -761,7 +765,7 @@ async function installStack(stackKey, stackCfg, options = {}) {
761
765
  // copy must never let it slip through to root and clobber the shared router there.
762
766
  const stackRootClaudeSrc = join(rootSrc, 'CLAUDE.md');
763
767
  const stackShipsOwnRootClaude = existsSync(stackRootClaudeSrc);
764
- if (existsSync(rootSrc)) {
768
+ if (!options.skipRootScaffold && existsSync(rootSrc)) {
765
769
  console.log('📦 Installing project files...');
766
770
  // .gitignore is the one file every stack's root/ ships that can collide with
767
771
  // another already-installed kit's own .gitignore (e.g. modeling-kit + a build-kit
@@ -796,7 +800,7 @@ async function installStack(stackKey, stackCfg, options = {}) {
796
800
  // instead of silently guessing either way.
797
801
  const rootClaudeDest = join(targetDir, 'CLAUDE.md');
798
802
  const sharedRootClaude = join(__dirname, 'shared', 'root-claude', 'CLAUDE.md');
799
- const routerContent = existsSync(sharedRootClaude) ? readFileSync(sharedRootClaude, 'utf-8') : null;
803
+ const routerContent = options.skipRootScaffold ? null : (existsSync(sharedRootClaude) ? readFileSync(sharedRootClaude, 'utf-8') : null);
800
804
  if (routerContent !== null) {
801
805
  if (!existsSync(rootClaudeDest)) {
802
806
  writeFileSync(rootClaudeDest, routerContent);
@@ -1161,39 +1165,50 @@ function ensureMcpRegistered(projectDir, baseUrl) {
1161
1165
  // process spawns `claude` (true for `run --modeling`'s own spawn). It is NOT true
1162
1166
  // for an interactive session opened directly in the project right after
1163
1167
  // `init`/`init-config` — that `claude` process inherits the user's shell env,
1164
- // which never had a reason to already have this var set. Without a `.env` to
1165
- // resolve it from, `.mcp.json`'s `${EVENTMODELERS_TOKEN}` placeholder resolves to
1166
- // empty, the MCP server gets an empty x-token header, and the connection fails
1167
- // silently, until something happens to trigger the `connect` skill's own Step 3.5,
1168
- // which writes this same file. Do it here too so a fresh interactive session works
1169
- // without depending on that skill having run first.
1168
+ // which never had a reason to already have this var set.
1169
+ //
1170
+ // A plain `.env` file does NOT fix this Claude Code never sources one; per its
1171
+ // own docs, an unresolved `.mcp.json` placeholder is left as the literal
1172
+ // `${EVENTMODELERS_TOKEN}` text, which fails auth and falls back to an OAuth
1173
+ // flow the eventmodelers server can't actually satisfy for this client. The
1174
+ // only things Claude Code itself resolves `.mcp.json` placeholders against are
1175
+ // the inherited shell env and its own settings files' `env` block. `.claude/
1176
+ // settings.local.json` is the documented per-user, gitignored-by-convention
1177
+ // scope for exactly this — same idea as `.eventmodelers/config.json` already
1178
+ // holding the raw token, just in the one file Claude Code's own process env
1179
+ // actually consults before expanding `.mcp.json`.
1170
1180
  function ensureEnvToken(targetDir, token) {
1171
1181
  if (!token) return;
1172
- const envPath = join(targetDir, '.env');
1173
- const line = `EVENTMODELERS_TOKEN=${token}`;
1174
-
1175
- if (existsSync(envPath)) {
1176
- const content = readFileSync(envPath, 'utf-8');
1177
- if (/^EVENTMODELERS_TOKEN=/m.test(content)) {
1178
- const updated = content.replace(/^EVENTMODELERS_TOKEN=.*$/m, line);
1179
- if (updated !== content) writeFileSync(envPath, updated);
1180
- } else {
1181
- appendFileSync(envPath, `${content === '' || content.endsWith('\n') ? '' : '\n'}${line}\n`);
1182
+ const claudeDir = join(targetDir, '.claude');
1183
+ mkdirSync(claudeDir, { recursive: true });
1184
+ const settingsPath = join(claudeDir, 'settings.local.json');
1185
+
1186
+ let settings = {};
1187
+ if (existsSync(settingsPath)) {
1188
+ try {
1189
+ settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
1190
+ } catch {
1191
+ settings = {};
1182
1192
  }
1183
- } else {
1184
- writeFileSync(envPath, `${line}\n`);
1185
1193
  }
1186
1194
 
1195
+ settings.env = settings.env || {};
1196
+ settings.env.EVENTMODELERS_TOKEN = token;
1197
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
1198
+
1199
+ // settings.local.json is gitignored by Claude Code's own convention, but make
1200
+ // sure nothing here relies on that silently — it now holds a live secret.
1187
1201
  const gitignorePath = join(targetDir, '.gitignore');
1202
+ const entry = '.claude/settings.local.json';
1188
1203
  if (existsSync(gitignorePath)) {
1189
1204
  const content = readFileSync(gitignorePath, 'utf-8');
1190
- if (!content.split('\n').map((l) => l.trim()).includes('.env')) {
1191
- appendFileSync(gitignorePath, `${content === '' || content.endsWith('\n') ? '' : '\n'}.env\n`);
1205
+ if (!content.split('\n').map((l) => l.trim()).includes(entry)) {
1206
+ appendFileSync(gitignorePath, `${content === '' || content.endsWith('\n') ? '' : '\n'}${entry}\n`);
1192
1207
  }
1193
1208
  } else {
1194
- writeFileSync(gitignorePath, '.env\n');
1209
+ writeFileSync(gitignorePath, `${entry}\n`);
1195
1210
  }
1196
- console.log(' ✓ Wrote EVENTMODELERS_TOKEN to .env (gitignored)');
1211
+ console.log(' ✓ Wrote EVENTMODELERS_TOKEN to .claude/settings.local.json (gitignored)');
1197
1212
  }
1198
1213
 
1199
1214
  // `run --modeling`: modeling-kit's one and only runtime mode — there is no
@@ -1609,6 +1624,49 @@ credentialFlags(program
1609
1624
  });
1610
1625
  });
1611
1626
 
1627
+ // Every kit config `re-init` can refresh, keyed the same way install-manifest.json's
1628
+ // `stack` field is — looked up after reading that manifest so re-init knows exactly
1629
+ // which templates to re-copy without the user having to pass --stack again.
1630
+ const REINITIABLE_STACKS = { ...STACKS, [MODELING_KIT.key]: MODELING_KIT, [BLANK_BUILD_KIT.key]: BLANK_BUILD_KIT };
1631
+
1632
+ credentialFlags(program
1633
+ .command('re-init')
1634
+ .description('Refresh an already-installed kit from the current CLI version — re-copies skills and the kit dir (.build-kit or .agent-modeling-kit) so you pick up script/skill updates after upgrading. Unlike `init`, never touches the project root scaffold or the root CLAUDE.md router, and leaves existing credentials alone unless --force is passed.')
1635
+ .option('--modeling', 'Refresh the modeling kit (.agent-modeling-kit) instead of a build kit')
1636
+ .option('--global', 'Re-install skills into ~/.claude/skills/ instead of the project — defaults to however they were originally installed')
1637
+ .option('-f, --force', 'Re-prompt for credentials even if a config already has everything required — overwrites the existing config.json'))
1638
+ .action(async (opts, command) => {
1639
+ const globalOpts = command.optsWithGlobals();
1640
+ const targetDir = process.cwd();
1641
+
1642
+ const kitDirName = opts.modeling ? MODELING_KIT.kitDirName : STACKS.node.kitDirName;
1643
+ const kitDir = join(targetDir, kitDirName);
1644
+
1645
+ if (!existsSync(kitDir)) {
1646
+ console.error(`❌ No ${kitDirName}/ found in ${targetDir} — run \`init${opts.modeling ? ' --modeling' : ''}\` first.`);
1647
+ process.exit(1);
1648
+ }
1649
+
1650
+ const manifest = readJsonSafe(join(kitDir, '.eventmodelers', 'install-manifest.json'));
1651
+ const stackKey = opts.modeling ? MODELING_KIT.key : manifest.stack;
1652
+ const stackCfg = stackKey ? REINITIABLE_STACKS[stackKey] : null;
1653
+
1654
+ if (!stackCfg) {
1655
+ console.error(`❌ Can't tell which stack ${relative(targetDir, kitDir)} was installed from (${manifest.stack ? `"${manifest.stack}" isn't one re-init recognizes — likely a --git community stack` : 'its install manifest predates this tracking, or is missing'}).`);
1656
+ console.error(' Re-run the original `init --git <url> --stack <name>` command by hand instead.');
1657
+ process.exit(1);
1658
+ }
1659
+
1660
+ await installStack(stackKey, stackCfg, {
1661
+ configPath: globalOpts.config,
1662
+ print: globalOpts.print,
1663
+ global: opts.global !== undefined ? opts.global : !!manifest.global,
1664
+ force: opts.force,
1665
+ credentialOverrides: credentialOverridesFromOpts(opts),
1666
+ skipRootScaffold: true,
1667
+ });
1668
+ });
1669
+
1612
1670
  program
1613
1671
  .command('init-mcp')
1614
1672
  .description('Register the eventmodelers MCP server in .claude/settings.json (and optionally another harness)')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, Cratis, or modeling-only)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -150,7 +150,7 @@ Always run this step (not only when Step 3 ran) — it's idempotent and safe to
150
150
 
151
151
  The eventmodelers backend exposes the same board capabilities as an MCP server at `<BASE_URL>/mcp`, authenticated with the same `TOKEN` via an `x-token` header. Register it in the project's `.mcp.json` so Claude Code (or any other MCP-aware host) can connect and expose tools as `mcp__eventmodelers__<tool_name>`.
152
152
 
153
- **Do not put the raw token in `.mcp.json`** — that file is typically committed to share server config with the team. Instead reference an environment variable and keep the actual secret in a gitignored `.env` file:
153
+ **Do not put the raw token in `.mcp.json`** — that file is typically committed to share server config with the team. Instead reference an environment variable and set the actual secret in `.claude/settings.local.json`'s `env` block:
154
154
 
155
155
  1. Read the existing `.mcp.json` at the project root if present (it may already list other MCP servers, e.g. a browser-automation server used by `discover-storyboard` — merge into `mcpServers`, never replace the whole file). If absent, start from `{"mcpServers": {}}`.
156
156
  2. Add or update the `eventmodelers` entry:
@@ -165,8 +165,14 @@ The eventmodelers backend exposes the same board capabilities as an MCP server a
165
165
  }
166
166
  }
167
167
  ```
168
- 3. Ensure a project-root `.env` file contains `EVENTMODELERS_TOKEN=<TOKEN>` (append/update the line; create the file if missing).
169
- 4. Ensure `.env` is listed in `.gitignore` (same check-then-append pattern as Step 3 uses for `.eventmodelers/config.json`) — it holds the same secret and must never be committed.
168
+ 3. Read the existing `.claude/settings.local.json` if present and merge in — never clobber `permissions`/`enabledMcpjsonServers` or anything else already there. Ensure it has `EVENTMODELERS_TOKEN` set under `env` (create the file with just this key if it doesn't exist yet):
169
+ ```json
170
+ {
171
+ "env": { "EVENTMODELERS_TOKEN": "<TOKEN>" }
172
+ }
173
+ ```
174
+ A plain `.env` file does **not** work here — Claude Code never sources one, so a `${EVENTMODELERS_TOKEN}` placeholder in `.mcp.json` would be left unexpanded (sent as the literal `${EVENTMODELERS_TOKEN}` text), which fails auth and pushes the client into an OAuth flow the eventmodelers server can't satisfy for this client. `.claude/settings.local.json`'s `env` block — alongside the inherited shell environment — is the only thing Claude Code actually resolves `.mcp.json` placeholders against.
175
+ 4. Ensure `.claude/settings.local.json` is listed in `.gitignore` (same check-then-append pattern as Step 3 uses for `.eventmodelers/config.json`) — it now holds the same secret and must never be committed. (Gitignored by Claude Code's own convention already, but don't rely on that silently.)
170
176
 
171
177
  MCP tools only become visible to the current agent session after the host (re)connects to the server — a brand-new `.mcp.json` entry written mid-session may need the user to approve the new server or reconnect (e.g. Claude Code's `/mcp` command) before `mcp__eventmodelers__*` tools appear in the tool list. That's expected and not an error: tell the user once, then let every other skill fall back to curl automatically until the tools show up.
172
178
 
@@ -226,7 +232,7 @@ The `token` field is a secret. It is never logged or shown after initial confirm
226
232
 
227
233
  ## Security notes
228
234
 
229
- - The config file (`.eventmodelers/config.json`) and the `.env` file holding `EVENTMODELERS_TOKEN` are both workspace-local and gitignored — never commit either.
235
+ - The config file (`.eventmodelers/config.json`) and `.claude/settings.local.json` (holding `EVENTMODELERS_TOKEN` under `env`) are both workspace-local and gitignored — never commit either.
230
236
  - `.mcp.json` itself is safe to commit — it only ever contains the `${EVENTMODELERS_TOKEN}` placeholder, never the literal token.
231
237
  - The token grants write access to all boards in its organization — treat it like a password.
232
238
  - If a skill receives a `401`/`403` (curl) or an access-denied tool error (MCP) mid-session, re-invoke this skill to refresh the config before retrying.