@eventmodelers/cli 0.0.1

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 (220) hide show
  1. package/README.md +175 -0
  2. package/cli.js +676 -0
  3. package/package.json +35 -0
  4. package/shared/build-kit/code-export.mjs +560 -0
  5. package/shared/build-kit/lib/ollama-agent.js +147 -0
  6. package/shared/build-kit/package.json +11 -0
  7. package/shared/build-kit/ralph-ollama.js +39 -0
  8. package/shared/build-kit/realtime-agent.js +18 -0
  9. package/stacks/axon/templates/.claude/skills/build-automation/SKILL.md +407 -0
  10. package/stacks/axon/templates/.claude/skills/build-automation/references/feature-flag-patterns.md +114 -0
  11. package/stacks/axon/templates/.claude/skills/build-automation-workflow/SKILL.md +496 -0
  12. package/stacks/axon/templates/.claude/skills/build-automation-workflow/references/axon-workflow-api.md +279 -0
  13. package/stacks/axon/templates/.claude/skills/build-automation-workflow/references/feature-flag-patterns.md +114 -0
  14. package/stacks/axon/templates/.claude/skills/build-state-change/SKILL.md +396 -0
  15. package/stacks/axon/templates/.claude/skills/build-state-change/references/axon-test-fixture-patterns.md +162 -0
  16. package/stacks/axon/templates/.claude/skills/build-state-change/references/feature-flag-patterns.md +56 -0
  17. package/stacks/axon/templates/.claude/skills/build-state-change/references/rest-api-patterns.md +115 -0
  18. package/stacks/axon/templates/.claude/skills/build-state-view/SKILL.md +282 -0
  19. package/stacks/axon/templates/.claude/skills/connect/SKILL.md +178 -0
  20. package/stacks/axon/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +611 -0
  21. package/stacks/axon/templates/.claude/skills/load-slice/SKILL.md +141 -0
  22. package/stacks/axon/templates/.claude/skills/update-slice-status/SKILL.md +105 -0
  23. package/stacks/axon/templates/build-kit/lib/AGENT.md +47 -0
  24. package/stacks/axon/templates/build-kit/lib/agent.sh +20 -0
  25. package/stacks/axon/templates/build-kit/lib/backend-prompt.md +158 -0
  26. package/stacks/axon/templates/build-kit/lib/prompt.md +126 -0
  27. package/stacks/axon/templates/build-kit/lib/ralph.js +369 -0
  28. package/stacks/axon/templates/build-kit/ralph-claude.js +44 -0
  29. package/stacks/axon/templates/build-kit/ralph.sh +98 -0
  30. package/stacks/axon/templates/root/.env.example +5 -0
  31. package/stacks/axon/templates/root/CLAUDE.md +60 -0
  32. package/stacks/axon/templates/root/README.md +44 -0
  33. package/stacks/axon/templates/root/docker-compose.yml +64 -0
  34. package/stacks/axon/templates/root/mvnw +259 -0
  35. package/stacks/axon/templates/root/mvnw.cmd +149 -0
  36. package/stacks/axon/templates/root/pom.xml +125 -0
  37. package/stacks/axon/templates/root/src/main/java/io/axoniq/quickstart/QuickstartApplication.java +128 -0
  38. package/stacks/axon/templates/root/src/main/java/io/axoniq/quickstart/config/WebConfig.java +155 -0
  39. package/stacks/axon/templates/root/src/main/resources/application.properties +2 -0
  40. package/stacks/axon/templates/root/src/main/resources/static/bg-body.png +0 -0
  41. package/stacks/axon/templates/root/src/main/resources/static/bg-header.png +0 -0
  42. package/stacks/axon/templates/root/src/main/resources/static/index.html +596 -0
  43. package/stacks/cratis-csharp/templates/.claude/skills/_shared/cratis-conventions.md +251 -0
  44. package/stacks/cratis-csharp/templates/.claude/skills/build-automation/SKILL.md +99 -0
  45. package/stacks/cratis-csharp/templates/.claude/skills/build-automation/references/patterns.md +115 -0
  46. package/stacks/cratis-csharp/templates/.claude/skills/build-state-change/SKILL.md +163 -0
  47. package/stacks/cratis-csharp/templates/.claude/skills/build-state-change/references/patterns.md +234 -0
  48. package/stacks/cratis-csharp/templates/.claude/skills/build-state-view/SKILL.md +120 -0
  49. package/stacks/cratis-csharp/templates/.claude/skills/build-state-view/references/patterns.md +166 -0
  50. package/stacks/cratis-csharp/templates/.claude/skills/connect/SKILL.md +169 -0
  51. package/stacks/cratis-csharp/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +609 -0
  52. package/stacks/cratis-csharp/templates/.claude/skills/load-slice/SKILL.md +141 -0
  53. package/stacks/cratis-csharp/templates/.claude/skills/update-slice-status/SKILL.md +105 -0
  54. package/stacks/cratis-csharp/templates/build-kit/lib/AGENT.md +58 -0
  55. package/stacks/cratis-csharp/templates/build-kit/lib/agent.sh +20 -0
  56. package/stacks/cratis-csharp/templates/build-kit/lib/backend-prompt.md +129 -0
  57. package/stacks/cratis-csharp/templates/build-kit/lib/prompt.md +124 -0
  58. package/stacks/cratis-csharp/templates/build-kit/lib/ralph.js +302 -0
  59. package/stacks/cratis-csharp/templates/build-kit/ralph-claude.js +37 -0
  60. package/stacks/cratis-csharp/templates/build-kit/ralph.sh +98 -0
  61. package/stacks/cratis-csharp/templates/root/.frontend/index.css +29 -0
  62. package/stacks/cratis-csharp/templates/root/.frontend/index.html +17 -0
  63. package/stacks/cratis-csharp/templates/root/.frontend/main.tsx +18 -0
  64. package/stacks/cratis-csharp/templates/root/.frontend/tsconfig.json +42 -0
  65. package/stacks/cratis-csharp/templates/root/.frontend/tsconfig.node.json +11 -0
  66. package/stacks/cratis-csharp/templates/root/.frontend/vite.config.ts +56 -0
  67. package/stacks/cratis-csharp/templates/root/App.tsx +23 -0
  68. package/stacks/cratis-csharp/templates/root/CLAUDE.md +62 -0
  69. package/stacks/cratis-csharp/templates/root/CratisApp.csproj +25 -0
  70. package/stacks/cratis-csharp/templates/root/CratisApp.sln +18 -0
  71. package/stacks/cratis-csharp/templates/root/GlobalUsings.cs +3 -0
  72. package/stacks/cratis-csharp/templates/root/Home.tsx +102 -0
  73. package/stacks/cratis-csharp/templates/root/Program.cs +26 -0
  74. package/stacks/cratis-csharp/templates/root/README.md +192 -0
  75. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/AllListings.ts +47 -0
  76. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/Listing.cs +11 -0
  77. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/Listing.ts +12 -0
  78. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/ListingDataTable.tsx +17 -0
  79. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/index.ts +1 -0
  80. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/Register.ts +51 -0
  81. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/RegisterDialog.tsx +18 -0
  82. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/Registration.cs +27 -0
  83. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/index.ts +1 -0
  84. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/SomeFeature.tsx +22 -0
  85. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/SomeName.cs +3 -0
  86. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/index.ts +1 -0
  87. package/stacks/cratis-csharp/templates/root/appsettings.Development.json +9 -0
  88. package/stacks/cratis-csharp/templates/root/appsettings.json +26 -0
  89. package/stacks/cratis-csharp/templates/root/docker-compose.yml +23 -0
  90. package/stacks/cratis-csharp/templates/root/package.json +33 -0
  91. package/stacks/cratis-csharp/templates/root/tsconfig.json +3 -0
  92. package/stacks/modeling-kit/templates/.claude/settings.local.json +13 -0
  93. package/stacks/modeling-kit/templates/.claude/skills/analyze-existing-model/SKILL.md +215 -0
  94. package/stacks/modeling-kit/templates/.claude/skills/attributes/SKILL.md +176 -0
  95. package/stacks/modeling-kit/templates/.claude/skills/connect/SKILL.md +182 -0
  96. package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/SKILL.md +383 -0
  97. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-applying-conways-law/SKILL.md +465 -0
  98. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/SKILL.md +587 -0
  99. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/references/facilitating-event-modeling-workshops.md +626 -0
  100. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/SKILL.md +542 -0
  101. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-event-models/SKILL.md +497 -0
  102. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/SKILL.md +783 -0
  103. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/SKILL.md +604 -0
  104. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md +699 -0
  105. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-integrating-legacy-systems/SKILL.md +674 -0
  106. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/SKILL.md +231 -0
  107. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/domain-patterns.md +150 -0
  108. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/patterns.md +329 -0
  109. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/snapshotting.md +204 -0
  110. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/SKILL.md +352 -0
  111. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/references/project-planning-with-event-modeling.md +341 -0
  112. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-plotting-events/SKILL.md +153 -0
  113. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/SKILL.md +136 -0
  114. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/examples.md +78 -0
  115. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/patterns.md +85 -0
  116. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md +655 -0
  117. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-translating-external-events/SKILL.md +479 -0
  118. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models/SKILL.md +234 -0
  119. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models-checklist/SKILL.md +353 -0
  120. package/stacks/modeling-kit/templates/.claude/skills/examples/SKILL.md +200 -0
  121. package/stacks/modeling-kit/templates/.claude/skills/handle-comment/SKILL.md +94 -0
  122. package/stacks/modeling-kit/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +441 -0
  123. package/stacks/modeling-kit/templates/.claude/skills/place-element/SKILL.md +290 -0
  124. package/stacks/modeling-kit/templates/.claude/skills/storyboard/SKILL.md +212 -0
  125. package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/SKILL.md +123 -0
  126. package/stacks/modeling-kit/templates/.claude/skills/timeline/SKILL.md +353 -0
  127. package/stacks/modeling-kit/templates/.claude/skills/update-slice-status/SKILL.md +110 -0
  128. package/stacks/modeling-kit/templates/.claude/skills/wdyt/SKILL.md +134 -0
  129. package/stacks/modeling-kit/templates/kit/lib/agent.sh +20 -0
  130. package/stacks/modeling-kit/templates/kit/lib/ollama-agent.js +147 -0
  131. package/stacks/modeling-kit/templates/kit/lib/ralph.js +286 -0
  132. package/stacks/modeling-kit/templates/kit/package.json +12 -0
  133. package/stacks/modeling-kit/templates/kit/ralph-claude.js +50 -0
  134. package/stacks/modeling-kit/templates/kit/ralph-ollama.js +38 -0
  135. package/stacks/modeling-kit/templates/kit/ralph.sh +61 -0
  136. package/stacks/modeling-kit/templates/kit/realtime-agent.js +18 -0
  137. package/stacks/modeling-kit/templates/root/CLAUDE.md +56 -0
  138. package/stacks/node/templates/.claude/skills/build-automation/SKILL.md +266 -0
  139. package/stacks/node/templates/.claude/skills/build-state-change/SKILL.md +344 -0
  140. package/stacks/node/templates/.claude/skills/build-state-view/SKILL.md +412 -0
  141. package/stacks/node/templates/.claude/skills/connect/SKILL.md +178 -0
  142. package/stacks/node/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +628 -0
  143. package/stacks/node/templates/.claude/skills/load-slice/SKILL.md +143 -0
  144. package/stacks/node/templates/.claude/skills/update-slice-status/SKILL.md +110 -0
  145. package/stacks/node/templates/build-kit/README.md +86 -0
  146. package/stacks/node/templates/build-kit/lib/AGENT.md +47 -0
  147. package/stacks/node/templates/build-kit/lib/agent.sh +20 -0
  148. package/stacks/node/templates/build-kit/lib/backend-prompt.md +152 -0
  149. package/stacks/node/templates/build-kit/lib/prompt.md +122 -0
  150. package/stacks/node/templates/build-kit/lib/ralph.js +369 -0
  151. package/stacks/node/templates/build-kit/ralph-claude.js +44 -0
  152. package/stacks/node/templates/build-kit/ralph.sh +98 -0
  153. package/stacks/node/templates/root/.env.example +15 -0
  154. package/stacks/node/templates/root/CLAUDE.md +60 -0
  155. package/stacks/node/templates/root/docker-compose.yml +15 -0
  156. package/stacks/node/templates/root/flyway.conf +17 -0
  157. package/stacks/node/templates/root/migrations/V1__schema.sql.example +12 -0
  158. package/stacks/node/templates/root/package.json +50 -0
  159. package/stacks/node/templates/root/server.ts +130 -0
  160. package/stacks/node/templates/root/setup-env.sh +53 -0
  161. package/stacks/node/templates/root/src/common/assertions.ts +6 -0
  162. package/stacks/node/templates/root/src/common/db.ts +32 -0
  163. package/stacks/node/templates/root/src/common/loadPostgresEventstore.ts +23 -0
  164. package/stacks/node/templates/root/src/common/parseEndpoint.ts +51 -0
  165. package/stacks/node/templates/root/src/common/processorDlq.ts +28 -0
  166. package/stacks/node/templates/root/src/common/realtimeBroadcast.ts +12 -0
  167. package/stacks/node/templates/root/src/common/replay.ts +16 -0
  168. package/stacks/node/templates/root/src/common/routes.ts +19 -0
  169. package/stacks/node/templates/root/src/common/testHelpers.ts +44 -0
  170. package/stacks/node/templates/root/src/swagger.ts +34 -0
  171. package/stacks/node/templates/root/src/util/assertions.ts +6 -0
  172. package/stacks/node/templates/root/src/util/hash.ts +9 -0
  173. package/stacks/node/templates/root/src/util/sanitize.ts +23 -0
  174. package/stacks/node/templates/root/tsconfig.json +32 -0
  175. package/stacks/node/templates/root/vercel.json +8 -0
  176. package/stacks/supabase/templates/.claude/skills/build-automation/SKILL.md +266 -0
  177. package/stacks/supabase/templates/.claude/skills/build-state-change/SKILL.md +344 -0
  178. package/stacks/supabase/templates/.claude/skills/build-state-view/SKILL.md +390 -0
  179. package/stacks/supabase/templates/.claude/skills/build-webhook/SKILL.md +303 -0
  180. package/stacks/supabase/templates/.claude/skills/connect/SKILL.md +178 -0
  181. package/stacks/supabase/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +628 -0
  182. package/stacks/supabase/templates/.claude/skills/load-slice/SKILL.md +143 -0
  183. package/stacks/supabase/templates/.claude/skills/update-slice-status/SKILL.md +110 -0
  184. package/stacks/supabase/templates/build-kit/README.md +86 -0
  185. package/stacks/supabase/templates/build-kit/lib/AGENT.md +47 -0
  186. package/stacks/supabase/templates/build-kit/lib/agent.sh +20 -0
  187. package/stacks/supabase/templates/build-kit/lib/backend-prompt.md +152 -0
  188. package/stacks/supabase/templates/build-kit/lib/prompt.md +123 -0
  189. package/stacks/supabase/templates/build-kit/lib/ralph.js +369 -0
  190. package/stacks/supabase/templates/build-kit/ralph-claude.js +44 -0
  191. package/stacks/supabase/templates/build-kit/ralph.sh +98 -0
  192. package/stacks/supabase/templates/root/.env.example +22 -0
  193. package/stacks/supabase/templates/root/CLAUDE.md +61 -0
  194. package/stacks/supabase/templates/root/flyway.conf +17 -0
  195. package/stacks/supabase/templates/root/package.json +52 -0
  196. package/stacks/supabase/templates/root/server.ts +177 -0
  197. package/stacks/supabase/templates/root/setup-env.sh +53 -0
  198. package/stacks/supabase/templates/root/src/common/assertions.ts +6 -0
  199. package/stacks/supabase/templates/root/src/common/db.ts +32 -0
  200. package/stacks/supabase/templates/root/src/common/loadPostgresEventstore.ts +23 -0
  201. package/stacks/supabase/templates/root/src/common/parseEndpoint.ts +51 -0
  202. package/stacks/supabase/templates/root/src/common/processorDlq.ts +28 -0
  203. package/stacks/supabase/templates/root/src/common/realtimeBroadcast.ts +19 -0
  204. package/stacks/supabase/templates/root/src/common/replay.ts +16 -0
  205. package/stacks/supabase/templates/root/src/common/routes.ts +23 -0
  206. package/stacks/supabase/templates/root/src/common/testHelpers.ts +54 -0
  207. package/stacks/supabase/templates/root/src/supabase/README.md +141 -0
  208. package/stacks/supabase/templates/root/src/supabase/api.ts +56 -0
  209. package/stacks/supabase/templates/root/src/supabase/requireOrgaAdmin.ts +32 -0
  210. package/stacks/supabase/templates/root/src/supabase/requireSysUser.ts +40 -0
  211. package/stacks/supabase/templates/root/src/supabase/requireUser.ts +72 -0
  212. package/stacks/supabase/templates/root/src/swagger.ts +34 -0
  213. package/stacks/supabase/templates/root/src/util/assertions.ts +6 -0
  214. package/stacks/supabase/templates/root/src/util/hash.ts +9 -0
  215. package/stacks/supabase/templates/root/src/util/sanitize.ts +23 -0
  216. package/stacks/supabase/templates/root/supabase/config.toml +295 -0
  217. package/stacks/supabase/templates/root/supabase/migrations/V1__schema.sql.example +12 -0
  218. package/stacks/supabase/templates/root/supabase/seed.sql +1 -0
  219. package/stacks/supabase/templates/root/tsconfig.json +32 -0
  220. package/stacks/supabase/templates/root/vercel.json +8 -0
package/cli.js ADDED
@@ -0,0 +1,676 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join, relative, resolve, sep } from 'path';
6
+ import {
7
+ existsSync,
8
+ mkdirSync,
9
+ cpSync,
10
+ rmSync,
11
+ readdirSync,
12
+ writeFileSync,
13
+ readFileSync,
14
+ appendFileSync,
15
+ } from 'fs';
16
+ import { execSync } from 'child_process';
17
+ import { createInterface, emitKeypressEvents, moveCursor, clearScreenDown } from 'readline';
18
+
19
+ const __filename = fileURLToPath(import.meta.url);
20
+ const __dirname = dirname(__filename);
21
+
22
+ // Each backend stack is a template set under stacks/<key>/templates/{.claude,root,build-kit}.
23
+ // They also get shared/build-kit/* copied into their kit dir first — those files
24
+ // (ralph-ollama.js, realtime-agent.js, code-export.mjs, lib/ollama-agent.js, package.json)
25
+ // are identical across all of them.
26
+ const STACKS = {
27
+ node: {
28
+ label: 'Node.js / TypeScript',
29
+ kitSubdir: 'build-kit',
30
+ kitDirName: '.build-kit',
31
+ useShared: true,
32
+ needsBoardId: true,
33
+ },
34
+ supabase: {
35
+ label: 'Supabase',
36
+ kitSubdir: 'build-kit',
37
+ kitDirName: '.build-kit',
38
+ useShared: true,
39
+ needsBoardId: true,
40
+ },
41
+ axon: {
42
+ label: 'Axon Framework (Java/Kotlin)',
43
+ kitSubdir: 'build-kit',
44
+ kitDirName: '.build-kit',
45
+ useShared: true,
46
+ needsBoardId: true,
47
+ },
48
+ 'cratis-csharp': {
49
+ label: 'Cratis (.NET/C#)',
50
+ kitSubdir: 'build-kit',
51
+ kitDirName: '.build-kit',
52
+ useShared: true,
53
+ needsBoardId: true,
54
+ },
55
+ };
56
+
57
+ // Not a stack — no backend scaffold, just skills + the agent loop. Gets its own
58
+ // command (`init-modeling`) instead of living in the `init --stack` picker.
59
+ const MODELING_KIT = {
60
+ key: 'modeling-kit',
61
+ label: 'Modeling only — skills + agent loop, no backend scaffold',
62
+ kitSubdir: 'kit',
63
+ kitDirName: '.agent-modeling-kit',
64
+ useShared: false,
65
+ needsBoardId: false,
66
+ };
67
+
68
+ const KIT_DIR_NAMES = [...new Set([...Object.values(STACKS), MODELING_KIT].map((s) => s.kitDirName))];
69
+
70
+ // Same principle Playwright MCP uses per harness: one shared server, but each coding
71
+ // agent has its own registration mechanism. Automate the ones with a real, verified
72
+ // CLI install command; for the rest, print manual steps instead of guessing at an
73
+ // unverified config file format (https://playwright.dev/mcp/clients/*).
74
+ const MCP_SERVER_NAME = 'eventmodelers';
75
+ const MCP_CLIENTS = {
76
+ 'claude-code': {
77
+ label: 'Claude Code',
78
+ command: (url) => `claude mcp add ${MCP_SERVER_NAME} --transport http ${url}`,
79
+ },
80
+ vscode: {
81
+ label: 'VS Code',
82
+ command: (url) => `code --add-mcp '${JSON.stringify({ name: MCP_SERVER_NAME, type: 'http', url })}'`,
83
+ },
84
+ };
85
+ const MCP_MANUAL_CLIENTS = [
86
+ { label: 'Cursor', hint: (url) => `Settings → MCP → Add new MCP Server → Type: http, URL: ${url}` },
87
+ { label: 'Windsurf', hint: (url) => `Add an HTTP MCP server pointing at ${url} in Windsurf's MCP settings` },
88
+ ];
89
+
90
+ // Every config field can also be set via an EVENTMODELERS_* env var — these always
91
+ // win over whatever's in config.json, so scripted/CI installs can skip prompts entirely.
92
+ const ENV_CONFIG_MAP = {
93
+ EVENTMODELERS_ORGANIZATION_ID: 'organizationId',
94
+ EVENTMODELERS_BOARD_ID: 'boardId',
95
+ EVENTMODELERS_TOKEN: 'token',
96
+ EVENTMODELERS_BASE_URL: 'baseUrl',
97
+ EVENTMODELERS_ANTHROPIC_BASE_URL: 'anthropicBaseUrl',
98
+ EVENTMODELERS_MODEL: 'model',
99
+ };
100
+
101
+ function applyEnvOverrides(config) {
102
+ const result = { ...config };
103
+ for (const [envVar, field] of Object.entries(ENV_CONFIG_MAP)) {
104
+ if (process.env[envVar]) result[field] = process.env[envVar];
105
+ }
106
+ return result;
107
+ }
108
+
109
+ function maskSecret(value) {
110
+ if (!value) return value;
111
+ return value.length <= 8 ? '*'.repeat(value.length) : `${value.slice(0, 4)}...${value.slice(-4)}`;
112
+ }
113
+
114
+ // A single shared readline interface for the process lifetime. Opening and closing
115
+ // a new one per prompt() call drops buffered input when stdin is piped (e.g. tests,
116
+ // scripted installs) — the first interface can read ahead and consume lines meant
117
+ // for later prompts, leaving the next one waiting on a stream that already ended.
118
+ let sharedRl = null;
119
+ function getSharedRl() {
120
+ if (!sharedRl) {
121
+ sharedRl = createInterface({ input: process.stdin, output: process.stdout });
122
+ }
123
+ return sharedRl;
124
+ }
125
+
126
+ async function prompt(question) {
127
+ return new Promise((resolve) => {
128
+ getSharedRl().question(question, (answer) => {
129
+ resolve(answer.trim());
130
+ });
131
+ });
132
+ }
133
+
134
+ // Arrow-key single-select menu. Falls back to a numbered prompt on non-TTY stdin (e.g. piped input, CI).
135
+ async function selectPrompt(question, choices, defaultIndex = 0) {
136
+ if (!process.stdin.isTTY) {
137
+ console.log(`\n${question}`);
138
+ choices.forEach((c, i) => console.log(` ${i + 1}) ${c.label}`));
139
+ const answer = await prompt(` Select [1-${choices.length}] (default ${defaultIndex + 1}): `);
140
+ const idx = parseInt(answer, 10) - 1;
141
+ return choices[Number.isInteger(idx) && idx >= 0 && idx < choices.length ? idx : defaultIndex].value;
142
+ }
143
+
144
+ return new Promise((resolve) => {
145
+ let index = defaultIndex;
146
+ const stdin = process.stdin;
147
+ const render = () => choices.map((c, i) => ` ${i === index ? '●' : '○'} ${c.label}`);
148
+
149
+ console.log(`\n${question}`);
150
+ let lines = render();
151
+ lines.forEach((l) => console.log(l));
152
+
153
+ emitKeypressEvents(stdin);
154
+ stdin.setRawMode(true);
155
+
156
+ const cleanup = () => {
157
+ stdin.removeListener('keypress', onKeypress);
158
+ stdin.setRawMode(false);
159
+ stdin.pause();
160
+ };
161
+
162
+ const onKeypress = (str, key) => {
163
+ if (key.ctrl && key.name === 'c') {
164
+ cleanup();
165
+ process.exit(1);
166
+ }
167
+ if (key.name === 'up' || key.name === 'k') {
168
+ index = (index - 1 + choices.length) % choices.length;
169
+ } else if (key.name === 'down' || key.name === 'j') {
170
+ index = (index + 1) % choices.length;
171
+ } else if (key.name === 'return') {
172
+ cleanup();
173
+ resolve(choices[index].value);
174
+ return;
175
+ } else {
176
+ return;
177
+ }
178
+ moveCursor(process.stdout, 0, -lines.length);
179
+ clearScreenDown(process.stdout);
180
+ lines = render();
181
+ lines.forEach((l) => console.log(l));
182
+ };
183
+
184
+ stdin.on('keypress', onKeypress);
185
+ stdin.resume();
186
+ });
187
+ }
188
+
189
+ function findConfigInParents(startDir) {
190
+ let dir = startDir;
191
+ while (true) {
192
+ const candidate = join(dir, '.eventmodelers', 'config.json');
193
+ if (existsSync(candidate)) return candidate;
194
+ const parent = dirname(dir);
195
+ if (parent === dir) return null;
196
+ dir = parent;
197
+ }
198
+ }
199
+
200
+ function readJsonSafe(path) {
201
+ if (!path || !existsSync(path)) return {};
202
+ try {
203
+ return JSON.parse(readFileSync(path, 'utf-8'));
204
+ } catch {
205
+ return {};
206
+ }
207
+ }
208
+
209
+ // Hierarchical resolution: a shared config higher up the directory tree (e.g.
210
+ // ~/.eventmodelers/config.json with org/token/baseUrl) provides defaults, and the
211
+ // kit dir's own config.json (project-specific — typically just boardId) overrides
212
+ // any field it also sets. An explicit --config path bypasses this entirely.
213
+ function loadEffectiveConfig(cwd, kitDir, explicitPath) {
214
+ if (explicitPath) {
215
+ const configPath = resolve(cwd, explicitPath);
216
+ return { configPath, sources: [configPath], config: applyEnvOverrides(readJsonSafe(configPath)) };
217
+ }
218
+
219
+ const kitConfigPath = kitDir ? join(kitDir, '.eventmodelers', 'config.json') : null;
220
+ const kitConfigExists = kitConfigPath && existsSync(kitConfigPath);
221
+ const parentConfigPath = findConfigInParents(cwd);
222
+
223
+ const merged = { ...readJsonSafe(parentConfigPath), ...(kitConfigExists ? readJsonSafe(kitConfigPath) : {}) };
224
+ const sources = [parentConfigPath, kitConfigExists ? kitConfigPath : null].filter(Boolean);
225
+
226
+ return {
227
+ configPath: kitConfigExists ? kitConfigPath : parentConfigPath,
228
+ sources,
229
+ config: applyEnvOverrides(merged),
230
+ };
231
+ }
232
+
233
+ function findInstalledKitDir(cwd) {
234
+ for (const name of KIT_DIR_NAMES) {
235
+ const p = join(cwd, name);
236
+ if (existsSync(p)) return p;
237
+ }
238
+ return null;
239
+ }
240
+
241
+ function findAllInstalledKitDirs(cwd) {
242
+ return KIT_DIR_NAMES.map((name) => join(cwd, name)).filter((p) => existsSync(p));
243
+ }
244
+
245
+ function copyDirContents(srcDir, destDir, { skip = [] } = {}) {
246
+ if (!existsSync(srcDir)) return;
247
+ mkdirSync(destDir, { recursive: true });
248
+ for (const item of readdirSync(srcDir)) {
249
+ if (skip.includes(item)) continue;
250
+ const src = join(srcDir, item);
251
+ const dest = join(destDir, item);
252
+ cpSync(src, dest, {
253
+ recursive: true,
254
+ filter: (s) => !relative(src, s).split(sep).includes('node_modules'),
255
+ });
256
+ console.log(` ✓ Installed ${relative(process.cwd(), dest)}`);
257
+ }
258
+ }
259
+
260
+ async function resolveStack(cliStack) {
261
+ if (cliStack) {
262
+ if (!STACKS[cliStack]) {
263
+ console.error(`❌ Unknown stack "${cliStack}". Available: ${Object.keys(STACKS).join(', ')}`);
264
+ process.exit(1);
265
+ }
266
+ return cliStack;
267
+ }
268
+ return selectPrompt(
269
+ 'Which stack are you scaffolding?',
270
+ Object.entries(STACKS).map(([key, cfg]) => ({ label: `${key} — ${cfg.label}`, value: key })),
271
+ 0,
272
+ );
273
+ }
274
+
275
+ async function installStack(stackKey, stackCfg, options = {}) {
276
+ console.log('🚀 Eventmodelers CLI\n');
277
+ console.log(`Using: ${stackKey} (${stackCfg.label})\n`);
278
+
279
+ const targetDir = process.cwd();
280
+ const templatesSource = join(__dirname, 'stacks', stackKey, 'templates');
281
+ const sharedBuildKit = join(__dirname, 'shared', 'build-kit');
282
+
283
+ if (!existsSync(templatesSource)) {
284
+ console.error('❌ Templates directory not found at:', templatesSource);
285
+ process.exit(1);
286
+ }
287
+
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'));
292
+
293
+ // --- 2. Spread stack scaffold files into the project root ---
294
+ const rootSrc = join(templatesSource, 'root');
295
+ if (existsSync(rootSrc)) {
296
+ console.log('\n📦 Installing project files...\n');
297
+ copyDirContents(rootSrc, targetDir);
298
+ }
299
+
300
+ // --- 3. Create the kit dir and install the agent runner ---
301
+ const kitDir = join(targetDir, stackCfg.kitDirName);
302
+ mkdirSync(kitDir, { recursive: true });
303
+ console.log(`\n📦 Installing agent kit into ${stackCfg.kitDirName}/...`);
304
+ console.log(' Sets up the Ralph agent loop, scripts, and configuration that drive realtime modeling.\n');
305
+
306
+ if (stackCfg.useShared) {
307
+ copyDirContents(sharedBuildKit, kitDir);
308
+ }
309
+ copyDirContents(join(templatesSource, stackCfg.kitSubdir), kitDir, { skip: ['.eventmodelers'] });
310
+
311
+ // Make scripts executable
312
+ for (const script of ['ralph.sh', 'lib/agent.sh', 'ralph-claude.js', 'ralph-ollama.js']) {
313
+ const p = join(kitDir, script);
314
+ if (existsSync(p)) {
315
+ try { execSync(`chmod +x "${p}"`); } catch {}
316
+ }
317
+ }
318
+
319
+ // --- 4. Install kit dependencies ---
320
+ if (existsSync(join(kitDir, 'package.json'))) {
321
+ console.log('\n📦 Installing kit dependencies...');
322
+ console.log(' Installs npm packages required by the agent scripts (e.g. websocket client, utilities).');
323
+ try {
324
+ execSync('npm install', { cwd: kitDir, stdio: ['ignore', 'inherit', 'inherit'] });
325
+ console.log(' ✓ kit dependencies installed');
326
+ } catch {
327
+ console.error(' ⚠️ npm install failed in kit — run it manually');
328
+ }
329
+ }
330
+
331
+ // --- 5. Credentials ---
332
+ console.log('\n🔐 Configuring credentials...');
333
+ console.log(' Stores your Organization ID (and Board ID, if this stack needs one) and token');
334
+ console.log(' so the agent can connect to app.eventmodelers.ai.\n');
335
+
336
+ const configPath = options.configPath
337
+ ? resolve(targetDir, options.configPath)
338
+ : join(kitDir, '.eventmodelers', 'config.json');
339
+ const configDir = dirname(configPath);
340
+ mkdirSync(configDir, { recursive: true });
341
+
342
+ const gitignorePath = join(targetDir, '.gitignore');
343
+ const relConfigDir = relative(targetDir, configDir);
344
+ if (relConfigDir && !relConfigDir.startsWith('..')) {
345
+ const gitignoreEntry = `${relConfigDir}/`;
346
+ if (existsSync(gitignorePath)) {
347
+ const content = readFileSync(gitignorePath, 'utf-8');
348
+ if (!content.includes(gitignoreEntry)) {
349
+ appendFileSync(gitignorePath, `\n${gitignoreEntry}\n`);
350
+ }
351
+ } else {
352
+ writeFileSync(gitignorePath, `${gitignoreEntry}\n`);
353
+ }
354
+ }
355
+
356
+ const requiredFields = stackCfg.needsBoardId
357
+ ? ['organizationId', 'boardId', 'token']
358
+ : ['organizationId', 'token'];
359
+
360
+ const effective = loadEffectiveConfig(targetDir, kitDir, options.configPath);
361
+ let config = effective.config;
362
+ if (effective.sources.length > 1) {
363
+ console.log(`\n ✓ Found shared defaults in ${effective.sources[0]}`);
364
+ }
365
+ const hasConfig = requiredFields.every((f) => config[f]);
366
+
367
+ const stillMissing = requiredFields.some((f) => !config[f]);
368
+ if (stillMissing && options.print) {
369
+ console.log('\n ℹ️ --print — skipping credential prompt, missing fields must be set via EVENTMODELERS_* env vars or config.json');
370
+ } else if (stillMissing) {
371
+ const choice = await selectPrompt('How do you want to configure credentials?', [
372
+ { label: 'Paste JSON copied from app.eventmodelers.ai/account', value: 'paste' },
373
+ { label: 'Enter values manually now', value: 'manual' },
374
+ { label: 'Skip — configure later with /connect', value: 'skip' },
375
+ ], 1);
376
+
377
+ if (choice === 'paste') {
378
+ console.log(`\n Paste your credentials JSON into one of these locations:\n`);
379
+ console.log(` (a) ${configPath}`);
380
+ console.log(` (b) .eventmodelers/config.json in this directory or any parent directory\n`);
381
+ console.log(` The file should look like:`);
382
+ const sample = stackCfg.needsBoardId
383
+ ? ` {\n "token": "...",\n "boardId": "...",\n "organizationId": "...",\n "baseUrl": "https://api.eventmodelers.ai"\n }\n`
384
+ : ` {\n "token": "...",\n "organizationId": "...",\n "baseUrl": "https://api.eventmodelers.ai"\n }\n`;
385
+ console.log(sample);
386
+ console.log(' Then re-run this installer, or just run the agent afterwards.\n');
387
+ } else if (choice === 'manual') {
388
+ console.log('\n🔑 Enter your Eventmodelers credentials:\n');
389
+ config.organizationId = await prompt(' Organization ID: ');
390
+ if (stackCfg.needsBoardId) {
391
+ config.boardId = await prompt(' Board ID: ');
392
+ }
393
+ config.token = await prompt(' Token: ');
394
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
395
+ console.log(`\n ✓ Credentials saved to ${relative(targetDir, configPath)}`);
396
+ } else {
397
+ console.log('\n ℹ️ Skipped — use /connect in Claude Code to add credentials later');
398
+ }
399
+ } else {
400
+ console.log('\n ✓ Config already present — skipping credential prompt');
401
+ }
402
+
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
+
434
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
435
+ console.log(`\n ✓ Saved to ${relative(targetDir, configPath)}`);
436
+
437
+ // --- 7. MCP server in .claude/settings.json ---
438
+ console.log('\n🔌 Configuring MCP server...');
439
+ console.log(' Registers the Eventmodelers MCP server in .claude/settings.json so Claude Code can call modeling tools directly.\n');
440
+ const claudeSettingsDir = join(targetDir, '.claude');
441
+ const settingsPath = join(claudeSettingsDir, 'settings.json');
442
+ mkdirSync(claudeSettingsDir, { recursive: true });
443
+
444
+ let settings = {};
445
+ if (existsSync(settingsPath)) {
446
+ try {
447
+ settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
448
+ } catch {
449
+ settings = {};
450
+ }
451
+ }
452
+
453
+ const baseUrl = config.baseUrl || 'https://api.eventmodelers.ai';
454
+ settings.mcpServers = settings.mcpServers || {};
455
+ settings.mcpServers.eventmodelers = {
456
+ type: 'http',
457
+ url: `${baseUrl}/mcp`,
458
+ };
459
+
460
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
461
+ console.log(' ✓ MCP server configured in .claude/settings.json');
462
+
463
+ const mcpUrl = `${baseUrl}/mcp`;
464
+ if (options.print) {
465
+ console.log('\nConnect the same MCP server in another harness:');
466
+ for (const client of Object.values(MCP_CLIENTS)) {
467
+ console.log(` ${client.label.padEnd(12)} ${client.command(mcpUrl)}`);
468
+ }
469
+ for (const client of MCP_MANUAL_CLIENTS) {
470
+ console.log(` ${client.label.padEnd(12)} ${client.hint(mcpUrl)}`);
471
+ }
472
+ } else {
473
+ const clientChoice = await selectPrompt('\nConnect the MCP globally to another harness?', [
474
+ { label: 'Skip', value: 'skip' },
475
+ ...Object.entries(MCP_CLIENTS).map(([key, c]) => ({ label: c.label, value: key })),
476
+ ], 0);
477
+
478
+ if (clientChoice !== 'skip') {
479
+ const client = MCP_CLIENTS[clientChoice];
480
+ const cmd = client.command(mcpUrl);
481
+ try {
482
+ execSync(cmd, { stdio: 'inherit' });
483
+ console.log(` ✓ ${client.label} connected via: ${cmd}`);
484
+ } catch {
485
+ console.error(` ⚠️ Command failed — you can run it manually:`);
486
+ console.error(` ${cmd}`);
487
+ }
488
+ }
489
+
490
+ if (MCP_MANUAL_CLIENTS.length) {
491
+ console.log('\nOther harnesses without a scriptable installer:');
492
+ MCP_MANUAL_CLIENTS.forEach((c) => console.log(` ${c.label.padEnd(12)} ${c.hint(mcpUrl)}`));
493
+ }
494
+ }
495
+
496
+ console.log('\n✅ Done!\n');
497
+ console.log('Start the agent (realtime + task loop in one process):');
498
+ console.log(' npx @eventmodelers/cli run\n');
499
+ console.log('Or using Ollama (run `ollama serve` first):');
500
+ console.log(' npx @eventmodelers/cli run --ollama\n');
501
+ console.log('Or using the bash loop only (no realtime):');
502
+ 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`);
504
+ console.log('💡 Recommended: add Chrome DevTools MCP for browser inspection:');
505
+ console.log(' claude mcp add chrome-devtools --scope user -- npx chrome-devtools-mcp@latest\n');
506
+ }
507
+
508
+ const program = new Command();
509
+
510
+ program
511
+ .name('eventmodelers')
512
+ .description('Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack')
513
+ .version('1.0.0')
514
+ .option('--config <path>', 'Path to an explicit config.json, overriding directory-based resolution (individual fields can also be set via EVENTMODELERS_* env vars, which always win)')
515
+ .option('--print', 'Print follow-up commands (e.g. claude mcp add) instead of prompting to run them');
516
+
517
+ program
518
+ .command('init')
519
+ .alias('install')
520
+ .description('Scaffold a stack + install the agent modeling kit into the current directory')
521
+ .option('--stack <name>', `Stack to install (${Object.keys(STACKS).join(', ')})`)
522
+ .action(async (opts, command) => {
523
+ const stackKey = await resolveStack(opts.stack);
524
+ const globalOpts = command.optsWithGlobals();
525
+ await installStack(stackKey, STACKS[stackKey], { configPath: globalOpts.config, print: globalOpts.print });
526
+ });
527
+
528
+ program
529
+ .command('init-modeling')
530
+ .alias('modeling')
531
+ .description('Install skills + the agent loop only — no backend scaffold')
532
+ .action(async (opts, command) => {
533
+ const globalOpts = command.optsWithGlobals();
534
+ await installStack(MODELING_KIT.key, MODELING_KIT, { configPath: globalOpts.config, print: globalOpts.print });
535
+ });
536
+
537
+ program
538
+ .command('run')
539
+ .description('Start the agent loop from the installed kit dir (default: ralph-claude.js)')
540
+ .option('--ollama', 'Use ralph-ollama.js instead of the default Claude runner')
541
+ .option('--bash', 'Use the bash-only ralph.sh loop (no realtime)')
542
+ .action((opts) => {
543
+ const cwd = process.cwd();
544
+ const kitDir = findInstalledKitDir(cwd);
545
+ if (!kitDir) {
546
+ console.error(`❌ No installed kit dir found (checked: ${KIT_DIR_NAMES.join(', ')}) — run \`eventmodelers init\` first.`);
547
+ process.exit(1);
548
+ }
549
+
550
+ // The actual agent loop lives in the scaffolded kit dir, not in this package — this
551
+ // is just a thin dispatcher so users don't have to remember the kit-dir name or which
552
+ // runner file to invoke. Users (and the agent itself, via AGENT.md) may customize these
553
+ // files freely; `run` always executes whatever is currently on disk.
554
+ const runner = opts.bash ? 'ralph.sh' : opts.ollama ? 'ralph-ollama.js' : 'ralph-claude.js';
555
+ const runnerPath = join(kitDir, runner);
556
+ if (!existsSync(runnerPath)) {
557
+ console.error(`❌ ${relative(cwd, runnerPath)} not found.`);
558
+ process.exit(1);
559
+ }
560
+
561
+ console.log(`▶ Starting ${relative(cwd, runnerPath)}...\n`);
562
+ const cmd = runner.endsWith('.sh') ? `"${runnerPath}"` : `node "${runnerPath}"`;
563
+ try {
564
+ execSync(cmd, { cwd: kitDir, stdio: 'inherit' });
565
+ } catch (err) {
566
+ process.exit(err.status || 1);
567
+ }
568
+ });
569
+
570
+ program
571
+ .command('stacks')
572
+ .description('List available stacks (for `init --stack`)')
573
+ .action(() => {
574
+ console.log('Available stacks:\n');
575
+ for (const [key, cfg] of Object.entries(STACKS)) {
576
+ console.log(` ${key.padEnd(16)} ${cfg.label}`);
577
+ }
578
+ console.log('\nUse: npx @eventmodelers/cli init --stack <name>');
579
+ console.log(`\nNot a stack — skills + agent loop only, no backend: npx @eventmodelers/cli init-modeling`);
580
+ });
581
+
582
+ program
583
+ .command('uninstall')
584
+ .description('Remove the installed kit dir from the current directory')
585
+ .option('--build-kit', `Remove ${STACKS.node.kitDirName}/ (the backend-stack kit dir)`)
586
+ .option('--modeling-kit', `Remove ${MODELING_KIT.kitDirName}/ (the modeling-only kit dir)`)
587
+ .action((opts) => {
588
+ const cwd = process.cwd();
589
+ let targets;
590
+
591
+ if (opts.buildKit || opts.modelingKit) {
592
+ targets = [];
593
+ if (opts.buildKit) targets.push(join(cwd, STACKS.node.kitDirName));
594
+ if (opts.modelingKit) targets.push(join(cwd, MODELING_KIT.kitDirName));
595
+ targets = targets.filter((p) => existsSync(p));
596
+ if (!targets.length) {
597
+ console.log('ℹ️ Nothing to remove for the requested option(s).');
598
+ return;
599
+ }
600
+ } else {
601
+ targets = findAllInstalledKitDirs(cwd);
602
+ if (!targets.length) {
603
+ console.log('ℹ️ No installed kit dir found (checked: ' + KIT_DIR_NAMES.join(', ') + ')');
604
+ return;
605
+ }
606
+ if (targets.length > 1) {
607
+ console.log('⚠️ Multiple kit dirs found — re-run with --build-kit or --modeling-kit to pick one, or both to remove everything.');
608
+ targets.forEach((t) => console.log(` ${t}`));
609
+ return;
610
+ }
611
+ }
612
+
613
+ for (const t of targets) {
614
+ rmSync(t, { recursive: true, force: true });
615
+ console.log(` ✓ Removed ${t}`);
616
+ }
617
+ console.log('✅ Uninstalled');
618
+ });
619
+
620
+ program
621
+ .command('status')
622
+ .description('Check installation status')
623
+ .action((opts, command) => {
624
+ const cwd = process.cwd();
625
+ const kitDir = findInstalledKitDir(cwd);
626
+ const skillsDir = join(cwd, '.claude', 'skills');
627
+ const explicitConfig = command.optsWithGlobals().config;
628
+ const ralphPath = kitDir ? join(kitDir, 'ralph-claude.js') : null;
629
+ const { sources, config: cfg } = loadEffectiveConfig(cwd, kitDir, explicitConfig);
630
+
631
+ console.log('Eventmodelers CLI Status\n');
632
+ console.log(`Kit dir: ${kitDir ? `✅ installed (${relative(cwd, kitDir)})` : '❌ not found'}`);
633
+ console.log(`Skills: ${existsSync(skillsDir) ? '✅ installed' : '❌ not found'}`);
634
+ console.log(`Config: ${sources.length ? `✅ present${sources.length > 1 ? ` (merged from ${sources.length} files)` : ''}` : '❌ missing'}`);
635
+ console.log(`Ralph agent: ${ralphPath && existsSync(ralphPath) ? '✅ present' : '❌ missing'}`);
636
+
637
+ if (sources.length) {
638
+ console.log(`\nConnected to: ${cfg.baseUrl || 'https://api.eventmodelers.ai'}`);
639
+ console.log(`Organization: ${cfg.organizationId}`);
640
+ if (cfg.boardId) console.log(`Board: ${cfg.boardId}`);
641
+ console.log(`\nConfig source${sources.length > 1 ? 's (later overrides earlier)' : ''}:`);
642
+ sources.forEach((s) => console.log(` - ${s}`));
643
+ }
644
+
645
+ const activeEnvVars = Object.keys(ENV_CONFIG_MAP).filter((k) => process.env[k]);
646
+ if (activeEnvVars.length) {
647
+ console.log(`\nOverridden by env: ${activeEnvVars.join(', ')}`);
648
+ }
649
+ });
650
+
651
+ program
652
+ .command('config')
653
+ .description('Print the fully resolved config (merged across the directory hierarchy + EVENTMODELERS_* env vars), with the token masked')
654
+ .action((opts, command) => {
655
+ const cwd = process.cwd();
656
+ const kitDir = findInstalledKitDir(cwd);
657
+ const explicitConfig = command.optsWithGlobals().config;
658
+ const { sources, config } = loadEffectiveConfig(cwd, kitDir, explicitConfig);
659
+
660
+ const resolved = { ...config };
661
+ if (resolved.token) resolved.token = maskSecret(resolved.token);
662
+
663
+ console.log(`Config source${sources.length > 1 ? 's (later overrides earlier)' : ''}:`);
664
+ if (sources.length) sources.forEach((s) => console.log(` - ${s}`));
665
+ else console.log(' (none found)');
666
+ console.log();
667
+ console.log(JSON.stringify(resolved, null, 2));
668
+
669
+ const activeEnvVars = Object.keys(ENV_CONFIG_MAP).filter((k) => process.env[k]);
670
+ if (activeEnvVars.length) {
671
+ console.log(`\nOverridden by env: ${activeEnvVars.join(', ')}`);
672
+ }
673
+ });
674
+
675
+ await program.parseAsync();
676
+ if (sharedRl) sharedRl.close();