@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
@@ -0,0 +1,302 @@
1
+ // Common runtime for the ralph loop + realtime agent.
2
+ // Not meant to be run directly — use ralph-claude.js or ralph-ollama.js.
3
+ //
4
+ // startRalph({ kitDir, projectDir, onTask, onPlannedSlice })
5
+ // onTask(prompt) — called when tasks.json has entries
6
+ // onPlannedSlice(prompt) — called when .slices/ has a "Planned" entry (omit to skip)
7
+
8
+ import { createClient } from '@supabase/supabase-js';
9
+ import { readFileSync, mkdirSync, writeFileSync, existsSync, readdirSync } from 'fs';
10
+ import { join } from 'path';
11
+ import { randomUUID } from 'crypto';
12
+
13
+ // ── HTTP helpers ──────────────────────────────────────────────────────────────
14
+
15
+ class HttpError extends Error {
16
+ constructor(status, body) {
17
+ super(`HTTP ${status}: ${body}`);
18
+ this.status = status;
19
+ }
20
+ }
21
+
22
+ async function fetchJSON(url, options) {
23
+ const res = await fetch(url, options);
24
+ if (!res.ok) throw new HttpError(res.status, await res.text());
25
+ return res.json();
26
+ }
27
+
28
+ async function retryOn401(label, fn, maxRetries = 3) {
29
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
30
+ try {
31
+ return await fn();
32
+ } catch (err) {
33
+ if (err instanceof HttpError && err.status === 401) {
34
+ if (attempt < maxRetries) {
35
+ console.warn(`[agent] ${label} — 401, retrying (${attempt}/${maxRetries})...`);
36
+ continue;
37
+ }
38
+ console.error(`[agent] ${label} — 401 after ${maxRetries} retries, shutting down`);
39
+ process.exit(1);
40
+ }
41
+ throw err;
42
+ }
43
+ }
44
+ }
45
+
46
+ // ── Config ────────────────────────────────────────────────────────────────────
47
+
48
+ function loadLocalConfig(kitDir) {
49
+ const configPath = join(kitDir, '.eventmodelers', 'config.json');
50
+ if (!existsSync(configPath)) {
51
+ console.warn(`[ralph] Note: no .eventmodelers/config.json found — platform sync disabled.`);
52
+ console.warn(` To enable board sync, follow: https://app.eventmodelers.ai/documentation#build-cratis-csharp`);
53
+ console.warn(` Code generation from local slice definitions will still run.`);
54
+ return {};
55
+ }
56
+ const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));
57
+ if (process.env.BASE_URL) cfg.baseUrl = process.env.BASE_URL;
58
+ return cfg;
59
+ }
60
+
61
+ function hasCredentials(cfg) {
62
+ return !!(cfg.token && cfg.organizationId && cfg.boardId && cfg.baseUrl);
63
+ }
64
+
65
+ async function fetchPlatformConfig(local) {
66
+ const remote = await fetchJSON(`${local.baseUrl}/api/config`, {
67
+ headers: { 'x-token': local.token },
68
+ });
69
+ return { ...local, ...remote };
70
+ }
71
+
72
+ // ── Realtime agent ────────────────────────────────────────────────────────────
73
+
74
+ async function getRealtimeToken(cfg) {
75
+ const { token } = await fetchJSON(
76
+ `${cfg.baseUrl}/api/org/${cfg.organizationId}/prompts/realtime-token`,
77
+ { headers: { 'x-token': cfg.token } },
78
+ );
79
+ return token;
80
+ }
81
+
82
+ function slugify(str) {
83
+ return str.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
84
+ }
85
+
86
+ async function fetchAndPersistSlices(cfg, kitDir) {
87
+ const url = `${cfg.baseUrl}/api/org/${cfg.organizationId}/boards/${cfg.boardId}/slicedata/slices`;
88
+ const { slices } = await fetchJSON(url, {
89
+ headers: { 'x-token': cfg.token, 'x-board-id': cfg.boardId },
90
+ });
91
+ const slicesDir = join(kitDir, '.slices');
92
+ mkdirSync(slicesDir, { recursive: true });
93
+
94
+ // Group by context slug
95
+ const contexts = {};
96
+ for (const slice of slices) {
97
+ const contextSlug = slice.contextName ? slugify(slice.contextName) : 'default';
98
+ if (!contexts[contextSlug]) contexts[contextSlug] = { name: slice.contextName || 'default', slices: [] };
99
+ contexts[contextSlug].slices.push(slice);
100
+ }
101
+
102
+ // Write current_context.json pointing to the context with planned work (or first)
103
+ const ctxPath = join(slicesDir, 'current_context.json');
104
+ const plannedCtx = Object.keys(contexts).find(c => contexts[c].slices.some(s => (s.status || '').toLowerCase() === 'planned'));
105
+ const activeCtx = plannedCtx || Object.keys(contexts)[0] || 'default';
106
+ writeFileSync(ctxPath, JSON.stringify({ name: activeCtx }, null, 2), 'utf-8');
107
+
108
+ // Write per-context index.json and per-slice slice.json
109
+ for (const [contextSlug, { slices: ctxSlices }] of Object.entries(contexts)) {
110
+ const contextDir = join(slicesDir, contextSlug);
111
+ mkdirSync(contextDir, { recursive: true });
112
+
113
+ const indexSlices = ctxSlices.map((s, i) => {
114
+ const folder = (s.title ?? s.id).replaceAll(' ', '').toLowerCase();
115
+ return {
116
+ id: s.id,
117
+ slice: s.title,
118
+ index: i,
119
+ contextName: s.contextName || contextSlug,
120
+ contextSlug,
121
+ folder,
122
+ status: s.status,
123
+ definition: { id: s.id, title: s.title, status: s.status },
124
+ };
125
+ });
126
+ writeFileSync(join(contextDir, 'index.json'), JSON.stringify({ slices: indexSlices }, null, 2), 'utf-8');
127
+
128
+ for (const slice of ctxSlices) {
129
+ const folder = (slice.title ?? slice.id).replaceAll(' ', '').toLowerCase();
130
+ const sliceDir = join(contextDir, folder);
131
+ mkdirSync(sliceDir, { recursive: true });
132
+ writeFileSync(join(sliceDir, 'slice.json'), JSON.stringify(slice, null, 2), 'utf-8');
133
+ }
134
+ }
135
+
136
+ console.log(`[agent] Persisted ${slices.length} slice(s)`);
137
+ }
138
+
139
+ async function writeTask(payload, kitDir) {
140
+ const tasksPath = join(kitDir, 'tasks.json');
141
+ const existing = existsSync(tasksPath) ? JSON.parse(readFileSync(tasksPath, 'utf-8')) : [];
142
+ const filtered = existing.filter(t => t.payload?.sliceId !== payload.sliceId);
143
+ const task = { id: randomUUID(), createdAt: new Date().toISOString(), payload };
144
+ filtered.push(task);
145
+ writeFileSync(tasksPath, JSON.stringify(filtered, null, 2), 'utf-8');
146
+ console.log(`[agent] Task written — slice="${payload.sliceTitle}" status="${payload.sliceStatus}"`);
147
+ }
148
+
149
+ async function startRealtimeAgent(cfg, kitDir) {
150
+ let realtimeToken = await retryOn401('getRealtimeToken', () => getRealtimeToken(cfg));
151
+
152
+ await retryOn401('fetchAndPersistSlices', () => fetchAndPersistSlices(cfg, kitDir)).catch((err) =>
153
+ console.error('[agent] Initial slice fetch error:', err),
154
+ );
155
+
156
+ const supabase = createClient(cfg.supabaseUrl, cfg.supabaseAnonKey, {
157
+ realtime: { params: { apikey: cfg.supabaseAnonKey } },
158
+ });
159
+ await supabase.realtime.setAuth(realtimeToken);
160
+
161
+ const channelName = `board:${cfg.boardId}-slicechanged`;
162
+
163
+ supabase
164
+ .channel(channelName, { config: { private: true } })
165
+ .on('broadcast', { event: 'message' }, (msg) => {
166
+ if (msg.payload === 'Exit') {
167
+ console.log('[agent] Received "Exit" — shutting down');
168
+ process.exit(0);
169
+ }
170
+ })
171
+ .on('broadcast', { event: 'slice:changed' }, async (msg) => {
172
+ const payload = msg.payload;
173
+ console.log(`[agent] slice:changed — slice="${payload.sliceTitle}" status="${payload.sliceStatus}"`);
174
+ await retryOn401('fetchAndPersistSlices', () => fetchAndPersistSlices(cfg, kitDir)).catch((err) =>
175
+ console.error('[agent] Slice persist error:', err),
176
+ );
177
+ // Planned slices are handled by onPlannedSlice directly — no task needed
178
+ if ((payload.sliceStatus || '').toLowerCase() !== 'planned') {
179
+ await writeTask(payload, kitDir).catch((err) => console.error('[agent] writeTask error:', err));
180
+ }
181
+ })
182
+ .subscribe((status) => console.log(`[agent] Channel "${channelName}": ${status}`));
183
+
184
+ setInterval(async () => {
185
+ try {
186
+ realtimeToken = await retryOn401('getRealtimeToken (refresh)', () => getRealtimeToken(cfg));
187
+ supabase.realtime.setAuth(realtimeToken);
188
+ console.log('[agent] Token refreshed');
189
+ } catch (err) {
190
+ console.error('[agent] Token refresh failed:', err);
191
+ }
192
+ }, 10 * 60 * 1000);
193
+
194
+ const ping = async () => {
195
+ try {
196
+ const res = await fetch(`${cfg.baseUrl}/api/agent-alive`, {
197
+ method: 'POST',
198
+ headers: { Authorization: `Bearer ${realtimeToken}`, 'Content-Type': 'application/json' },
199
+ body: JSON.stringify({ token: cfg.token }),
200
+ });
201
+ if (!res.ok) console.error(`[agent] Ping failed: ${res.status}`);
202
+ } catch (err) {
203
+ console.error('[agent] Ping error:', err);
204
+ }
205
+ };
206
+ await ping();
207
+ setInterval(ping, 30_000);
208
+ }
209
+
210
+ // ── Ralph loop ────────────────────────────────────────────────────────────────
211
+
212
+ function hasPendingTasks(kitDir) {
213
+ const tasksPath = join(kitDir, 'tasks.json');
214
+ if (!existsSync(tasksPath)) return false;
215
+ try {
216
+ const tasks = JSON.parse(readFileSync(tasksPath, 'utf-8'));
217
+ return Array.isArray(tasks) && tasks.length > 0;
218
+ } catch {
219
+ return false;
220
+ }
221
+ }
222
+
223
+ function getFirstPlannedSliceTitle(kitDir) {
224
+ const slicesDir = join(kitDir, '.slices');
225
+ if (!existsSync(slicesDir)) return null;
226
+ for (const entry of readdirSync(slicesDir)) {
227
+ const indexPath = join(slicesDir, entry, 'index.json');
228
+ if (!existsSync(indexPath)) continue;
229
+ try {
230
+ const { slices } = JSON.parse(readFileSync(indexPath, 'utf-8'));
231
+ const planned = slices && slices.find((s) => (s.status || '').toLowerCase() === 'planned');
232
+ if (planned) return planned.slice || planned.id || null;
233
+ } catch {}
234
+ }
235
+ return null;
236
+ }
237
+
238
+ async function runWithRetry(label, fn) {
239
+ while (true) {
240
+ try {
241
+ console.log(`[ralph] ${label}`);
242
+ await fn();
243
+ return;
244
+ } catch (err) {
245
+ console.error(`[ralph] Error — retrying in 60s:`, err.message);
246
+ await new Promise((r) => setTimeout(r, 60_000));
247
+ }
248
+ }
249
+ }
250
+
251
+ async function ralphLoop(kitDir, cfg, onTask, onPlannedSlice) {
252
+ const promptFile = join(kitDir, 'lib', 'prompt.md');
253
+ const backendPromptFile = join(kitDir, 'lib', 'backend-prompt.md');
254
+ const credentialed = hasCredentials(cfg);
255
+
256
+ while (true) {
257
+ let didWork = false;
258
+
259
+ if (credentialed && hasPendingTasks(kitDir)) {
260
+ const prompt = readFileSync(promptFile, 'utf-8');
261
+ await runWithRetry('onTask: loading slice from board...', () => onTask(prompt));
262
+ await fetchAndPersistSlices(cfg, kitDir).catch(() => {});
263
+ didWork = true;
264
+ }
265
+
266
+ const plannedTitle = onPlannedSlice && getFirstPlannedSliceTitle(kitDir);
267
+ if (plannedTitle) {
268
+ const prompt = readFileSync(backendPromptFile, 'utf-8');
269
+ await runWithRetry(`onPlannedSlice: building slice "${plannedTitle}"...`, () => onPlannedSlice(prompt));
270
+ console.log(`[ralph] Slice build complete — waiting for next slice`);
271
+ if (credentialed) await fetchAndPersistSlices(cfg, kitDir).catch(() => {});
272
+ didWork = true;
273
+ }
274
+
275
+ if (!didWork) await new Promise((r) => setTimeout(r, 10_000));
276
+ }
277
+ }
278
+
279
+ // ── Public API ────────────────────────────────────────────────────────────────
280
+
281
+ export { loadLocalConfig, fetchPlatformConfig, retryOn401, startRealtimeAgent };
282
+
283
+ export async function startRalph({ kitDir, projectDir, onTask, onPlannedSlice }) {
284
+ const local = loadLocalConfig(kitDir);
285
+
286
+ console.log(`Ralph — kit: ${kitDir}`);
287
+ console.log(` project: ${projectDir}`);
288
+
289
+ if (!hasCredentials(local)) {
290
+ console.log(` mode: local-only (no platform sync)\n`);
291
+ await ralphLoop(kitDir, local, onTask, onPlannedSlice);
292
+ return;
293
+ }
294
+
295
+ const cfg = await retryOn401('fetchPlatformConfig', () => fetchPlatformConfig(local));
296
+ console.log(` org=${cfg.organizationId}, board=${cfg.boardId}, base=${cfg.baseUrl}\n`);
297
+
298
+ await Promise.all([
299
+ startRealtimeAgent(cfg, kitDir),
300
+ ralphLoop(kitDir, cfg, onTask, onPlannedSlice),
301
+ ]);
302
+ }
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ // Ralph loop + realtime agent using Claude Code as the executor.
3
+ // Usage: node ralph-claude.js [project_dir]
4
+
5
+ import { startRalph, loadLocalConfig } from './lib/ralph.js';
6
+ import { spawn } from 'child_process';
7
+ import { dirname, resolve } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+
10
+ const kitDir = dirname(fileURLToPath(import.meta.url));
11
+ const projectDir = process.argv[2] ? resolve(process.argv[2]) : resolve(kitDir, '..');
12
+
13
+ const cfg = loadLocalConfig(kitDir);
14
+ const inlineHeader = cfg.boardId
15
+ ? `board=${cfg.boardId} token=${cfg.token} org=${cfg.organizationId} baseUrl=${cfg.baseUrl}\n\n`
16
+ : '';
17
+
18
+ function runClaude(prompt) {
19
+ return new Promise((resolve, reject) => {
20
+ const proc = spawn('claude', ['--dangerously-skip-permissions', '-p', inlineHeader + prompt], {
21
+ cwd: projectDir,
22
+ stdio: 'inherit',
23
+ });
24
+ proc.on('close', (code) => (code === 0 ? resolve() : reject(new Error(`Claude exited ${code}`))));
25
+ proc.on('error', reject);
26
+ });
27
+ }
28
+
29
+ startRalph({
30
+ kitDir,
31
+ projectDir,
32
+ onTask: runClaude,
33
+ onPlannedSlice: runClaude,
34
+ }).catch((err) => {
35
+ console.error('[ralph] Fatal:', err);
36
+ process.exit(1);
37
+ });
@@ -0,0 +1,98 @@
1
+ #!/bin/bash
2
+ # Ralph agent loop — two independent loops, each triggered by their own condition
3
+ #
4
+ # onTask: tasks.json has entries → load slice from board, update .build-kit/.slices/
5
+ # onPlannedSlice: .build-kit/.slices/ has a "Planned" slice → build it
6
+ #
7
+ # The loops are NOT causally linked — either can trigger on its own.
8
+ #
9
+ # Usage: ./ralph.sh [iterations] [project_dir]
10
+ # iterations — number of loop cycles to run; 0 or omitted means run forever
11
+ # project_dir — path to the project root; defaults to the parent of .build-kit
12
+
13
+ set -euo pipefail
14
+
15
+ KIT_DIR="$(cd "$(dirname "$0")" && pwd)"
16
+ ITERATIONS="${1:-0}"
17
+ PROJECT_DIR="${2:-"$KIT_DIR/.."}"
18
+ TASKS_FILE="$KIT_DIR/tasks.json"
19
+ PROMPT_FILE="$KIT_DIR/lib/prompt.md"
20
+ BACKEND_PROMPT_FILE="$KIT_DIR/lib/backend-prompt.md"
21
+ AGENT_SCRIPT="$KIT_DIR/lib/agent.sh"
22
+
23
+ HAS_CREDENTIALS=true
24
+ if [[ ! -f "$KIT_DIR/.eventmodelers/config.json" ]]; then
25
+ echo "[ralph] Note: no .eventmodelers/config.json found — platform sync disabled." >&2
26
+ echo " To enable board sync, follow: https://app.eventmodelers.ai/documentation#build-cratis-csharp" >&2
27
+ echo " Code generation from local slice definitions will still run." >&2
28
+ HAS_CREDENTIALS=false
29
+ fi
30
+
31
+ echo "Ralph — kit: $KIT_DIR project: $PROJECT_DIR"
32
+
33
+ # Returns 0 if tasks.json has at least one task
34
+ has_pending_tasks() {
35
+ [[ -f "$TASKS_FILE" ]] || return 1
36
+ local content
37
+ content=$(cat "$TASKS_FILE")
38
+ [[ "$content" != "[]" && -n "$content" ]]
39
+ }
40
+
41
+ # Returns 0 if any JSON under .build-kit/.slices/ contains a "Planned" status
42
+ has_planned_slices() {
43
+ grep -rqi '"status"[[:space:]]*:[[:space:]]*"planned"' "$KIT_DIR/.slices/" --include='index.json' 2>/dev/null
44
+ }
45
+
46
+ # Returns the title of the first "Planned" slice, or empty string
47
+ get_planned_slice_title() {
48
+ for index_file in "$KIT_DIR/.slices/"*/index.json; do
49
+ [[ -f "$index_file" ]] || continue
50
+ local title
51
+ title=$(node -e "
52
+ try {
53
+ const d = JSON.parse(require('fs').readFileSync(process.argv[1], 'utf-8'));
54
+ const s = (d.slices||[]).find(s => (s.status||'').toLowerCase() === 'planned');
55
+ if (s) process.stdout.write(s.slice || s.id || '');
56
+ } catch(e) {}
57
+ " "$index_file" 2>/dev/null)
58
+ if [[ -n "$title" ]]; then
59
+ echo "$title"
60
+ return
61
+ fi
62
+ done
63
+ }
64
+
65
+ # Runs agent.sh with the given prompt; retries on non-zero exit
66
+ run_agent() {
67
+ local label="$1"
68
+ local prompt="$2"
69
+ while true; do
70
+ echo "[$(date -u +%H:%M:%S)] $label"
71
+ (cd "$PROJECT_DIR" && bash "$AGENT_SCRIPT" "$prompt") 2>&1 && return 0
72
+ echo "[$(date -u +%H:%M:%S)] Agent error — retrying in 60s..."
73
+ sleep 60
74
+ done
75
+ }
76
+
77
+ cycle=0
78
+ while [[ "$ITERATIONS" -eq 0 || "$cycle" -lt "$ITERATIONS" ]]; do
79
+ ran_something=false
80
+
81
+ if [[ "$HAS_CREDENTIALS" == true ]] && has_pending_tasks; then
82
+ run_agent "onTask: loading slice from board..." "$(cat "$PROMPT_FILE")"
83
+ ran_something=true
84
+ fi
85
+
86
+ if has_planned_slices; then
87
+ slice_title=$(get_planned_slice_title)
88
+ run_agent "onPlannedSlice: building \"$slice_title\"..." "$(cat "$BACKEND_PROMPT_FILE")"
89
+ echo "[$(date -u +%H:%M:%S)] Slice \"$slice_title\" build complete — waiting for next slice"
90
+ ran_something=true
91
+ fi
92
+
93
+ if [[ "$ran_something" == false ]]; then
94
+ sleep 3
95
+ fi
96
+
97
+ (( cycle++ )) || true
98
+ done
@@ -0,0 +1,29 @@
1
+ @import "tailwindcss";
2
+ @import 'primereact/resources/themes/lara-dark-blue/theme.css';
3
+ @source '../';
4
+
5
+ :root {
6
+ font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
7
+ font-size: 14px;
8
+ line-height: 1.5;
9
+ font-weight: 400;
10
+
11
+ color-scheme: dark;
12
+
13
+ font-synthesis: none;
14
+ text-rendering: optimizeLegibility;
15
+ -webkit-font-smoothing: antialiased;
16
+ -moz-osx-font-smoothing: grayscale;
17
+ }
18
+
19
+ body {
20
+ margin: 0;
21
+ min-width: 320px;
22
+ min-height: 100vh;
23
+ background-color: var(--surface-ground);
24
+ color: var(--text-color);
25
+ }
26
+
27
+ #root {
28
+ height: 100vh;
29
+ }
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
+ <meta name="base-path" content="" />
9
+ <title>CratisApp</title>
10
+ </head>
11
+
12
+ <body>
13
+ <div id="root"></div>
14
+ <script type="module" src="/main.tsx"></script>
15
+ </body>
16
+
17
+ </html>
@@ -0,0 +1,18 @@
1
+ import 'reflect-metadata';
2
+ import { PrimeReactProvider } from 'primereact/api';
3
+ import ReactDOM from 'react-dom/client';
4
+ import 'primeicons/primeicons.css';
5
+ import './index.css';
6
+ import React from 'react';
7
+ import { Arc } from '@cratis/arc.react';
8
+ import App from '../App.tsx';
9
+
10
+ ReactDOM.createRoot(document.getElementById('root')!).render(
11
+ <React.StrictMode>
12
+ <PrimeReactProvider value={{ ripple: true }}>
13
+ <Arc>
14
+ <App />
15
+ </Arc>
16
+ </PrimeReactProvider>
17
+ </React.StrictMode>
18
+ );
@@ -0,0 +1,42 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": false,
5
+ "lib": [
6
+ "ES2020",
7
+ "DOM",
8
+ "DOM.Iterable"
9
+ ],
10
+ "module": "ESNext",
11
+ "skipLibCheck": true,
12
+ /* Bundler mode */
13
+ "moduleResolution": "bundler",
14
+ "allowImportingTsExtensions": true,
15
+ "isolatedModules": true,
16
+ "moduleDetection": "force",
17
+ "noEmit": true,
18
+ "jsx": "react-jsx",
19
+ /* Decorators */
20
+ "experimentalDecorators": true,
21
+ "emitDecoratorMetadata": true,
22
+ /* Linting */
23
+ "strict": true,
24
+ "noUnusedLocals": true,
25
+ "noUnusedParameters": true,
26
+ "noFallthroughCasesInSwitch": true,
27
+ /* Path aliases */
28
+ "baseUrl": "..",
29
+ "paths": {
30
+ "Api/*": ["Features/*"]
31
+ }
32
+ },
33
+ "include": [
34
+ "../**/*.ts",
35
+ "../**/*.tsx"
36
+ ],
37
+ "exclude": [
38
+ "vite.config.ts",
39
+ "../node_modules/**",
40
+ "../wwwroot/**"
41
+ ]
42
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true,
8
+ "strict": true
9
+ },
10
+ "include": ["vite.config.ts"]
11
+ }
@@ -0,0 +1,56 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { EmitMetadataPlugin } from '@cratis/arc.vite';
5
+ import tailwindcss from '@tailwindcss/vite';
6
+
7
+ export default defineConfig({
8
+ root: fileURLToPath(new URL('./', import.meta.url)),
9
+ optimizeDeps: {
10
+ exclude: ['tslib'],
11
+ },
12
+ esbuild: {
13
+ supported: {
14
+ 'top-level-await': true,
15
+ },
16
+ },
17
+ build: {
18
+ outDir: '../wwwroot',
19
+ assetsDir: '',
20
+ modulePreload: false,
21
+ target: 'esnext',
22
+ minify: false,
23
+ cssCodeSplit: false,
24
+ rollupOptions: {
25
+ external: [],
26
+ },
27
+ },
28
+ plugins: [
29
+ react(),
30
+ tailwindcss(),
31
+ EmitMetadataPlugin({ tsconfigPath: fileURLToPath(new URL('./tsconfig.json', import.meta.url)) }) as any
32
+ ],
33
+ server: {
34
+ port: 9000,
35
+ open: true,
36
+ proxy: {
37
+ "/.cratis": {
38
+ target: 'http://localhost:5000',
39
+ ws: true
40
+ },
41
+ '/api': {
42
+ target: 'http://localhost:5000',
43
+ ws: true
44
+ },
45
+ '/swagger': {
46
+ target: 'http://localhost:5000',
47
+ ws: true
48
+ }
49
+ }
50
+ },
51
+ resolve: {
52
+ alias: {
53
+ 'Api': fileURLToPath(new URL('../Features', import.meta.url)),
54
+ }
55
+ }
56
+ });
@@ -0,0 +1,23 @@
1
+ import { BrowserRouter, Route, Routes } from 'react-router-dom';
2
+ import { Arc } from '@cratis/arc.react';
3
+ import { DialogComponents } from '@cratis/arc.react/dialogs';
4
+ import { BusyIndicatorDialog, ConfirmationDialog } from '@cratis/components/Dialogs';
5
+ import { Home } from './Home';
6
+ import { SomeFeature } from './SomeModule/SomeFeature';
7
+
8
+ function App() {
9
+ return (
10
+ <Arc>
11
+ <DialogComponents confirmation={ConfirmationDialog} busyIndicator={BusyIndicatorDialog}>
12
+ <BrowserRouter>
13
+ <Routes>
14
+ <Route path='/' element={<Home />} />
15
+ <Route path='/demo' element={<SomeFeature />} />
16
+ </Routes>
17
+ </BrowserRouter>
18
+ </DialogComponents>
19
+ </Arc>
20
+ );
21
+ }
22
+
23
+ export default App;