@eventmodelers/cli 0.0.1 → 0.0.3

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.
Files changed (3) hide show
  1. package/README.md +41 -9
  2. package/cli.js +96 -42
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -41,6 +41,16 @@ your-project/
41
41
 
42
42
  The four backend stacks (`node`, `supabase`, `axon`, `cratis-csharp`) also scaffold a real project skeleton into your project root (`templates/root/`) — source layout, build files, migrations, etc. `modeling-kit` only installs skills + the agent loop, with no backend opinion.
43
43
 
44
+ ### Installing skills globally
45
+
46
+ By default, skills are copied into the project's own `.claude/skills/`. Pass `--global` to `init` or `init-modeling` to install them into `~/.claude/skills/` instead — available in every project without re-running the installer each time:
47
+
48
+ ```bash
49
+ npx @eventmodelers/cli init-modeling --global
50
+ ```
51
+
52
+ Everything else (the kit dir, project scaffold, credentials, MCP registration) still targets the current directory as usual — `--global` only changes where skills land.
53
+
44
54
  ## Claude execution & config resolution
45
55
 
46
56
  During install you can optionally point the agent at a local LLM server (vLLM, Ollama) instead of the default Claude Code endpoint, and/or pin a specific model:
@@ -144,19 +154,41 @@ Which skills install depends on the chosen stack — see `stacks/<name>/template
144
154
  ## Commands
145
155
 
146
156
  ```bash
147
- npx @eventmodelers/cli init --stack <name> # scaffold a stack + install + configure (alias: install)
148
- npx @eventmodelers/cli init-modeling # skills + agent loop only, no backend scaffold (alias: modeling)
149
- npx @eventmodelers/cli run # start the agent loop (ralph-claude.js) from the installed kit dir
150
- npx @eventmodelers/cli run --ollama # same, via local Ollama (ralph-ollama.js)
151
- npx @eventmodelers/cli run --bash # bash-only loop, no realtime (ralph.sh)
152
- npx @eventmodelers/cli stacks # list available stacks
153
- npx @eventmodelers/cli status # check what's installed
154
- npx @eventmodelers/cli config # print the fully resolved config (file + env), token masked
155
- npx @eventmodelers/cli uninstall # remove the installed kit dir
157
+ npx @eventmodelers/cli init --stack <name> # scaffold a stack + install + configure (alias: install)
158
+ npx @eventmodelers/cli init --stack <name> --global # same, but skills go to ~/.claude/skills/ (every project)
159
+ npx @eventmodelers/cli init-modeling # skills + agent loop only, no backend scaffold (alias: modeling)
160
+ npx @eventmodelers/cli init-modeling --global # same, but skills go to ~/.claude/skills/ (every project)
161
+ npx @eventmodelers/cli run # start the agent loop (ralph-claude.js) from the installed kit dir
162
+ npx @eventmodelers/cli run --ollama # same, via local Ollama (ralph-ollama.js)
163
+ npx @eventmodelers/cli run --bash # bash-only loop, no realtime (ralph.sh)
164
+ npx @eventmodelers/cli stacks # list available stacks
165
+ npx @eventmodelers/cli status # check what's installed
166
+ npx @eventmodelers/cli config # print the fully resolved config (file + env), token masked
167
+ npx @eventmodelers/cli uninstall # remove everything init/init-modeling installed
156
168
  ```
157
169
 
158
170
  `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.
159
171
 
172
+ ### Uninstall
173
+
174
+ 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:
175
+
176
+ - the kit dir (`.build-kit/` or `.agent-modeling-kit/`)
177
+ - the skills it copied — from `.claude/skills/` normally, or `~/.claude/skills/` if it was installed with `--global`
178
+ - the `eventmodelers` entry it added to `.claude/settings.json`'s `mcpServers` (the rest of that file, and the file itself, is left in place)
179
+
180
+ It deliberately **never** touches the root project scaffold (`package.json`, `src/`, `server.ts`, migrations, `docker-compose.yml`, etc.) — that's your actual application code, not tooling, so `uninstall` won't delete it even though `init` wrote it.
181
+
182
+ If a kit dir predates this tracking (no manifest present), `uninstall` falls back to only removing the kit dir itself and tells you so — any skills or MCP registration from that older install need to be cleaned up by hand.
183
+
184
+ Registering the MCP server with another harness (`claude mcp add`, `code --add-mcp`, or the manual Cursor/Windsurf steps) happens outside this project's files, so `uninstall` doesn't attempt to undo it — remove it yourself in that harness if you connected one.
185
+
186
+ ```bash
187
+ npx @eventmodelers/cli uninstall # remove the one installed kit dir (errors if more than one is present)
188
+ npx @eventmodelers/cli uninstall --build-kit # remove .build-kit/ specifically
189
+ npx @eventmodelers/cli uninstall --modeling-kit # remove .agent-modeling-kit/ specifically
190
+ ```
191
+
160
192
  `--config <path>` and `--print` are global flags accepted by every command. `--print` skips the "connect MCP globally?" prompt during install and just prints the `claude mcp add` command instead of running it — combined with the env vars above, `--print` makes `init` fully non-interactive:
161
193
 
162
194
  ```bash
package/cli.js CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  } from 'fs';
16
16
  import { execSync } from 'child_process';
17
17
  import { createInterface, emitKeypressEvents, moveCursor, clearScreenDown } from 'readline';
18
+ import { homedir } from 'os';
18
19
 
19
20
  const __filename = fileURLToPath(import.meta.url);
20
21
  const __dirname = dirname(__filename);
@@ -285,10 +286,26 @@ async function installStack(stackKey, stackCfg, options = {}) {
285
286
  process.exit(1);
286
287
  }
287
288
 
288
- // --- 1. Install skills and Claude settings into project root ---
289
- console.log('📦 Installing Claude skills...');
290
- console.log(' Copies skills and settings into .claude/ so Claude Code picks them up automatically.\n');
291
- copyDirContents(join(templatesSource, '.claude'), join(targetDir, '.claude'));
289
+ // --- 1. Install skills (project-local by default, or ~/.claude/skills/ with --global) ---
290
+ // Recorded into the install manifest (step 8) so `uninstall` can remove exactly
291
+ // these files later and nothing the user added independently.
292
+ const claudeSkillsSrc = join(templatesSource, '.claude', 'skills');
293
+ const installedSkills = existsSync(claudeSkillsSrc) ? readdirSync(claudeSkillsSrc) : [];
294
+ let claudeExtras = [];
295
+
296
+ if (options.global) {
297
+ const globalSkillsDir = join(homedir(), '.claude', 'skills');
298
+ console.log('📦 Installing Claude skills globally...');
299
+ console.log(` Copies skills into ${globalSkillsDir} so they're available in every project, not just this one.\n`);
300
+ copyDirContents(claudeSkillsSrc, globalSkillsDir);
301
+ } else {
302
+ console.log('📦 Installing Claude skills...');
303
+ console.log(' Copies skills and settings into .claude/ so Claude Code picks them up automatically.\n');
304
+ copyDirContents(join(templatesSource, '.claude'), join(targetDir, '.claude'));
305
+ claudeExtras = existsSync(join(templatesSource, '.claude'))
306
+ ? readdirSync(join(templatesSource, '.claude')).filter((f) => f !== 'skills')
307
+ : [];
308
+ }
292
309
 
293
310
  // --- 2. Spread stack scaffold files into the project root ---
294
311
  const rootSrc = join(templatesSource, 'root');
@@ -400,41 +417,15 @@ async function installStack(stackKey, stackCfg, options = {}) {
400
417
  console.log('\n ✓ Config already present — skipping credential prompt');
401
418
  }
402
419
 
403
- // --- 6. Claude execution (optional) ---
404
- console.log('\n🧠 Configuring Claude execution (optional)...');
405
- console.log(' Point the agent at a local vLLM/Ollama endpoint and/or pin a specific model, instead of the default Claude Code setup.');
406
-
407
- if (process.env.EVENTMODELERS_ANTHROPIC_BASE_URL || process.env.EVENTMODELERS_MODEL) {
408
- console.log(' ✓ Using EVENTMODELERS_ANTHROPIC_BASE_URL / EVENTMODELERS_MODEL from environment — skipping prompt');
409
- } else if (options.print) {
410
- console.log(' ✓ --print — skipping prompt, keeping existing Claude execution settings');
411
- } else {
412
- const presetUrls = ['', 'http://localhost:8000', 'http://localhost:11434'];
413
- let defaultUrlIndex = presetUrls.indexOf(config.anthropicBaseUrl || '');
414
- if (defaultUrlIndex === -1) defaultUrlIndex = 0;
415
-
416
- let anthropicBaseUrl = await selectPrompt('Anthropic Base URL:', [
417
- { label: 'None — use the default Claude Code endpoint', value: '' },
418
- { label: 'Local vLLM (http://localhost:8000)', value: 'http://localhost:8000' },
419
- { label: 'Local Ollama (http://localhost:11434)', value: 'http://localhost:11434' },
420
- { label: 'Custom…', value: '__custom__' },
421
- ], defaultUrlIndex);
422
-
423
- if (anthropicBaseUrl === '__custom__') {
424
- anthropicBaseUrl = await prompt(' Custom Anthropic Base URL: ');
425
- }
426
-
427
- const claudeModel = await prompt(` Model ${config.model ? `[${config.model}]` : '(optional, press Enter to skip)'}: `);
428
-
429
- if (anthropicBaseUrl) config.anthropicBaseUrl = anthropicBaseUrl;
430
- else delete config.anthropicBaseUrl;
431
- if (claudeModel) config.model = claudeModel;
432
- }
433
-
420
+ // Claude vs. Ollama, and any custom Anthropic-compatible base URL, is a `run`-time
421
+ // choice (`eventmodelers run` vs `--ollama`) — not an install-time one. Power users
422
+ // can still pin config.anthropicBaseUrl/model via EVENTMODELERS_ANTHROPIC_BASE_URL /
423
+ // EVENTMODELERS_MODEL env vars or by editing config.json directly; loadEffectiveConfig
424
+ // already picks those up above, so nothing further to do here.
434
425
  writeFileSync(configPath, JSON.stringify(config, null, 2));
435
426
  console.log(`\n ✓ Saved to ${relative(targetDir, configPath)}`);
436
427
 
437
- // --- 7. MCP server in .claude/settings.json ---
428
+ // --- 6. MCP server in .claude/settings.json ---
438
429
  console.log('\n🔌 Configuring MCP server...');
439
430
  console.log(' Registers the Eventmodelers MCP server in .claude/settings.json so Claude Code can call modeling tools directly.\n');
440
431
  const claudeSettingsDir = join(targetDir, '.claude');
@@ -493,6 +484,17 @@ async function installStack(stackKey, stackCfg, options = {}) {
493
484
  }
494
485
  }
495
486
 
487
+ // --- 7. Install manifest (drives precise `uninstall` later) ---
488
+ // Only the footprint listed here is ever removed by `uninstall` — the root
489
+ // scaffold (step 2) is real project source the user builds on, so it's
490
+ // deliberately left out and never touched by uninstall.
491
+ const manifestDir = join(kitDir, '.eventmodelers');
492
+ mkdirSync(manifestDir, { recursive: true });
493
+ writeFileSync(
494
+ join(manifestDir, 'install-manifest.json'),
495
+ JSON.stringify({ stack: stackKey, global: !!options.global, skills: installedSkills, claudeExtras, mcpRegistered: true }, null, 2),
496
+ );
497
+
496
498
  console.log('\n✅ Done!\n');
497
499
  console.log('Start the agent (realtime + task loop in one process):');
498
500
  console.log(' npx @eventmodelers/cli run\n');
@@ -500,7 +502,7 @@ async function installStack(stackKey, stackCfg, options = {}) {
500
502
  console.log(' npx @eventmodelers/cli run --ollama\n');
501
503
  console.log('Or using the bash loop only (no realtime):');
502
504
  console.log(' npx @eventmodelers/cli run --bash\n');
503
- console.log(`Skills are ready in .claude/skills/ — use /connect to set a board ID.\n`);
505
+ console.log(`Skills are ready in ${options.global ? join(homedir(), '.claude', 'skills') : '.claude/skills/'} — use /connect to set a board ID.\n`);
504
506
  console.log('💡 Recommended: add Chrome DevTools MCP for browser inspection:');
505
507
  console.log(' claude mcp add chrome-devtools --scope user -- npx chrome-devtools-mcp@latest\n');
506
508
  }
@@ -519,19 +521,21 @@ program
519
521
  .alias('install')
520
522
  .description('Scaffold a stack + install the agent modeling kit into the current directory')
521
523
  .option('--stack <name>', `Stack to install (${Object.keys(STACKS).join(', ')})`)
524
+ .option('--global', 'Install skills into ~/.claude/skills/ instead of the project — available in every project')
522
525
  .action(async (opts, command) => {
523
526
  const stackKey = await resolveStack(opts.stack);
524
527
  const globalOpts = command.optsWithGlobals();
525
- await installStack(stackKey, STACKS[stackKey], { configPath: globalOpts.config, print: globalOpts.print });
528
+ await installStack(stackKey, STACKS[stackKey], { configPath: globalOpts.config, print: globalOpts.print, global: opts.global });
526
529
  });
527
530
 
528
531
  program
529
532
  .command('init-modeling')
530
533
  .alias('modeling')
531
534
  .description('Install skills + the agent loop only — no backend scaffold')
535
+ .option('--global', 'Install skills into ~/.claude/skills/ instead of the project — available in every project')
532
536
  .action(async (opts, command) => {
533
537
  const globalOpts = command.optsWithGlobals();
534
- await installStack(MODELING_KIT.key, MODELING_KIT, { configPath: globalOpts.config, print: globalOpts.print });
538
+ await installStack(MODELING_KIT.key, MODELING_KIT, { configPath: globalOpts.config, print: globalOpts.print, global: opts.global });
535
539
  });
536
540
 
537
541
  program
@@ -579,9 +583,60 @@ program
579
583
  console.log(`\nNot a stack — skills + agent loop only, no backend: npx @eventmodelers/cli init-modeling`);
580
584
  });
581
585
 
586
+ // Removes exactly what a given `init`/`init-modeling` run put down — read back from
587
+ // the install manifest written at the end of installStack() — and nothing else: not
588
+ // unrelated skills the user added by hand, not the root project scaffold.
589
+ function uninstallKitDir(kitDir, cwd) {
590
+ const manifestPath = join(kitDir, '.eventmodelers', 'install-manifest.json');
591
+ const manifest = readJsonSafe(manifestPath);
592
+
593
+ if (manifest.skills?.length) {
594
+ const skillsDir = manifest.global ? join(homedir(), '.claude', 'skills') : join(cwd, '.claude', 'skills');
595
+ for (const name of manifest.skills) {
596
+ const p = join(skillsDir, name);
597
+ if (existsSync(p)) {
598
+ rmSync(p, { recursive: true, force: true });
599
+ console.log(` ✓ Removed ${relative(cwd, p) || p}`);
600
+ }
601
+ }
602
+ }
603
+
604
+ if (manifest.claudeExtras?.length && !manifest.global) {
605
+ for (const name of manifest.claudeExtras) {
606
+ const p = join(cwd, '.claude', name);
607
+ if (existsSync(p)) {
608
+ rmSync(p, { recursive: true, force: true });
609
+ console.log(` ✓ Removed ${relative(cwd, p)}`);
610
+ }
611
+ }
612
+ }
613
+
614
+ if (manifest.mcpRegistered) {
615
+ const settingsPath = join(cwd, '.claude', 'settings.json');
616
+ if (existsSync(settingsPath)) {
617
+ try {
618
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
619
+ if (settings.mcpServers?.[MCP_SERVER_NAME]) {
620
+ delete settings.mcpServers[MCP_SERVER_NAME];
621
+ if (Object.keys(settings.mcpServers).length === 0) delete settings.mcpServers;
622
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
623
+ console.log(` ✓ Removed ${MCP_SERVER_NAME} MCP entry from ${relative(cwd, settingsPath)}`);
624
+ }
625
+ } catch {}
626
+ }
627
+ }
628
+
629
+ if (!existsSync(manifestPath)) {
630
+ console.log(` ℹ️ ${relative(cwd, kitDir)} predates install tracking — only the kit dir itself was removed; any installed skills or MCP registration must be cleaned up by hand.`);
631
+ }
632
+
633
+ rmSync(kitDir, { recursive: true, force: true });
634
+ console.log(` ✓ Removed ${relative(cwd, kitDir) || kitDir}`);
635
+ }
636
+
582
637
  program
583
638
  .command('uninstall')
584
- .description('Remove the installed kit dir from the current directory')
639
+ .description('Remove everything init/init-modeling installed: the kit dir, the skills it copied (project-local or ~/.claude/skills with --global), and its MCP entry in .claude/settings.json. Leaves the root project scaffold untouched.')
585
640
  .option('--build-kit', `Remove ${STACKS.node.kitDirName}/ (the backend-stack kit dir)`)
586
641
  .option('--modeling-kit', `Remove ${MODELING_KIT.kitDirName}/ (the modeling-only kit dir)`)
587
642
  .action((opts) => {
@@ -611,8 +666,7 @@ program
611
666
  }
612
667
 
613
668
  for (const t of targets) {
614
- rmSync(t, { recursive: true, force: true });
615
- console.log(` ✓ Removed ${t}`);
669
+ uninstallKitDir(t, cwd);
616
670
  }
617
671
  console.log('✅ Uninstalled');
618
672
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
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": {