@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/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@eventmodelers/cli",
3
+ "version": "0.0.1",
4
+ "description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, Cratis, or modeling-only)",
5
+ "type": "module",
6
+ "bin": {
7
+ "eventmodelers": "cli.js"
8
+ },
9
+ "files": [
10
+ "cli.js",
11
+ "shared",
12
+ "stacks",
13
+ "README.md"
14
+ ],
15
+ "keywords": [
16
+ "claude",
17
+ "claude-code",
18
+ "eventmodelers",
19
+ "event-storming",
20
+ "event-driven",
21
+ "realtime-agent",
22
+ "ddd"
23
+ ],
24
+ "author": "Martin Dilger",
25
+ "license": "MIT",
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "dependencies": {
30
+ "commander": "^12.0.0"
31
+ },
32
+ "engines": {
33
+ "node": ">=18.0.0"
34
+ }
35
+ }
@@ -0,0 +1,560 @@
1
+ #!/usr/bin/env node
2
+ import { createServer } from 'http';
3
+ import { readFileSync, writeFileSync, existsSync, readdirSync, statSync, unlinkSync, mkdirSync } from 'fs';
4
+ import { join, dirname, relative } from 'path';
5
+ import { execSync } from 'child_process';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+ const ROOT = __dirname;
11
+ const CONFIG_PATH = join(ROOT, 'config.json');
12
+ const SLICES_DIR = join(ROOT, '.slices');
13
+ const repoPath = process.env.WORKSPACE_PATH ?? join(__dirname, '..');
14
+
15
+ // ---------------------------------------
16
+ // Helpers
17
+ // ---------------------------------------
18
+ function sendJSON(res, data, status = 200) {
19
+ res.writeHead(status, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization' });
20
+ res.end(JSON.stringify(data, null, 2));
21
+ }
22
+
23
+ function parseIdWithRegex(filename) {
24
+ const match = filename.match(/screen-(\d+)\.png$/);
25
+ return match ? match[1] : null;
26
+ }
27
+
28
+ function slugify(text) {
29
+ return text
30
+ .toString()
31
+ .toLowerCase()
32
+ .trim()
33
+ .replace(/\s+/g, '-')
34
+ .replace(/[^\w\-]+/g, '')
35
+ .replace(/\-\-+/g, '-')
36
+ .replace(/^-+/, '')
37
+ .replace(/-+$/, '');
38
+ }
39
+
40
+ function findFilesRecursive(dir, filterFn, results = []) {
41
+ if (!existsSync(dir)) return results;
42
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
43
+ const fullPath = join(dir, entry.name);
44
+ if (entry.isDirectory()) findFilesRecursive(fullPath, filterFn, results);
45
+ else if (entry.isFile() && filterFn(entry.name)) results.push(fullPath);
46
+ }
47
+ return results;
48
+ }
49
+
50
+ function isGitRepo(path) {
51
+ try {
52
+ execSync('git rev-parse --git-dir', { cwd: path, stdio: 'ignore' });
53
+ return true;
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ function gitLsTree(path, revision, dir) {
60
+ try {
61
+ const output = execSync(`git ls-tree -r --name-only ${revision} -- ${dir}`, { cwd: path, encoding: 'utf-8' });
62
+ return output.split('\n').filter(Boolean);
63
+ } catch {
64
+ return [];
65
+ }
66
+ }
67
+
68
+ function gitShow(path, ref) {
69
+ try {
70
+ return execSync(`git show ${ref}`, { cwd: path, encoding: 'utf-8' });
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+
76
+ function gitInit(path) {
77
+ try {
78
+ execSync('git init', { cwd: path, stdio: 'ignore' });
79
+ return true;
80
+ } catch {
81
+ return false;
82
+ }
83
+ }
84
+
85
+ function gitAdd(path, files) {
86
+ try {
87
+ execSync(`git add ${files}`, { cwd: path, stdio: 'ignore' });
88
+ return true;
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
93
+
94
+ function gitStatus(path) {
95
+ try {
96
+ const output = execSync('git status --porcelain', { cwd: path, encoding: 'utf-8' });
97
+ const lines = output.split('\n').filter(Boolean);
98
+ const staged = lines.filter(line => /^[MARC]/.test(line)).map(line => line.substring(3));
99
+ return { staged };
100
+ } catch {
101
+ return { staged: [] };
102
+ }
103
+ }
104
+
105
+ function gitCommit(path, message, files) {
106
+ try {
107
+ const output = execSync(`git commit -m "${message}" ${files}`, { cwd: path, encoding: 'utf-8' });
108
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: path, encoding: 'utf-8' }).trim();
109
+ const commit = execSync('git rev-parse HEAD', { cwd: path, encoding: 'utf-8' }).trim();
110
+ return { branch, commit, summary: output };
111
+ } catch (err) {
112
+ return null;
113
+ }
114
+ }
115
+
116
+ // ---------------------------------------
117
+ // Server
118
+ // ---------------------------------------
119
+ const server = createServer(async (req, res) => {
120
+ // CORS preflight
121
+ if (req.method === 'OPTIONS') {
122
+ res.writeHead(200, {
123
+ 'Access-Control-Allow-Origin': '*',
124
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
125
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization'
126
+ });
127
+ return res.end();
128
+ }
129
+
130
+ const url = new URL(req.url, `http://${req.headers.host}`);
131
+ const pathname = url.pathname;
132
+
133
+ // ---------------------------------------
134
+ // /api/ping
135
+ // ---------------------------------------
136
+ if (pathname === '/api/ping' && req.method === 'GET') {
137
+ return sendJSON(res, { ok: true, message: 'pong' });
138
+ }
139
+
140
+ // ---------------------------------------
141
+ // /api/generate
142
+ // ---------------------------------------
143
+ if (pathname === '/api/generate' && req.method === 'POST') {
144
+ let body = '';
145
+ req.on('data', chunk => { body += chunk; });
146
+ req.on('end', () => {
147
+ try {
148
+ const config = JSON.parse(body);
149
+ const urlObj = new URL(req.url, `http://${req.headers.host}`);
150
+ const exportAsConfig = urlObj.searchParams.get("exportAsConfig") === "true";
151
+ let fileName = "config.json";
152
+
153
+ if (exportAsConfig) {
154
+ fileName = "config.json";
155
+ } else if (config.context) {
156
+ fileName = slugify(config.context) + ".json";
157
+ }
158
+
159
+ writeFileSync(join(ROOT, fileName), JSON.stringify(config, null, 2));
160
+ console.log(`✅ ${fileName} written to ${ROOT}`);
161
+
162
+ // Read or initialize index.json
163
+ const slice = config.slices.find(it => it.title)
164
+ const baseFolder = join(SLICES_DIR, slice.context ?? "default");
165
+
166
+ if (!existsSync(baseFolder)) {
167
+ mkdirSync(baseFolder, { recursive: true });
168
+ }
169
+ writeFileSync(join(baseFolder, 'config.json'), JSON.stringify(config, null, 2));
170
+ console.log(`✅ config.json written to ${baseFolder}`);
171
+
172
+ const contextName = slice.context ?? "default";
173
+ writeFileSync(join(SLICES_DIR, 'current_context.json'), JSON.stringify({ name: contextName }, null, 2));
174
+ console.log(`✅ current_context.json updated with context: ${contextName}`);
175
+
176
+ const indexFile = join(baseFolder, 'index.json');
177
+ let sliceIndices = { slices: [] };
178
+ if (existsSync(indexFile)) {
179
+ try {
180
+ sliceIndices = JSON.parse(readFileSync(indexFile, 'utf-8'));
181
+ } catch {
182
+ sliceIndices = { slices: [] };
183
+ }
184
+ }
185
+
186
+ // Write all slices
187
+ if (config.slices) {
188
+ // Build a map of slice ID to group ID from sliceGroups
189
+ const sliceToGroupMap = new Map();
190
+ if (config.sliceGroups) {
191
+ config.sliceGroups.forEach((group) => {
192
+ if (group.sliceIds) {
193
+ group.sliceIds.forEach((sliceId) => {
194
+ sliceToGroupMap.set(sliceId, group.id);
195
+ });
196
+ }
197
+ });
198
+ }
199
+
200
+ config.slices.forEach((slice) => {
201
+ const sliceFolder = slice.title?.replaceAll(" ", "")?.replaceAll("slice:", "")?.toLowerCase();
202
+ const folder = join(baseFolder, sliceFolder);
203
+
204
+ if (!existsSync(folder)) {
205
+ mkdirSync(folder, { recursive: true });
206
+ }
207
+
208
+ const filePath = join(folder, 'slice.json');
209
+ const sliceData = { ...slice };
210
+ delete sliceData.index;
211
+
212
+ // Add group ID to slice data if it belongs to a group
213
+ const groupId = sliceToGroupMap.get(slice.id);
214
+ if (groupId) {
215
+ sliceData.group = groupId;
216
+ }
217
+
218
+ writeFileSync(filePath, JSON.stringify(sliceData, null, 2));
219
+
220
+ const sliceIndex = {
221
+ id: slice.id,
222
+ slice: slice.title,
223
+ index: slice.index,
224
+ context: slice.context ?? "default",
225
+ folder: sliceFolder,
226
+ status: slice.status,
227
+ };
228
+ // Add group ID to index entry if it belongs to a group
229
+ if (groupId) {
230
+ sliceIndex.group = groupId;
231
+ }
232
+
233
+ const sliceId = sliceIndices.slices.findIndex(it => it.slice == slice.title);
234
+ if (sliceId == -1) {
235
+ sliceIndices.slices.push(sliceIndex);
236
+ } else {
237
+ sliceIndices.slices[sliceId] = {...sliceIndices.slices[sliceId], ...sliceIndex, assigned: undefined};
238
+ }
239
+ });
240
+ writeFileSync(indexFile, JSON.stringify(sliceIndices, null, 2));
241
+
242
+ // Write context.json
243
+ const contextFile = join(baseFolder, 'context.json');
244
+ const contextData = {
245
+ name: config.context,
246
+ contextPackage: config.codeGen?.contextPackage
247
+ };
248
+ writeFileSync(contextFile, JSON.stringify(contextData, null, 2));
249
+ console.log(`✅ context.json written to ${baseFolder}`);
250
+ }
251
+
252
+ // Write slice images
253
+ if (config.sliceImages) {
254
+ config.sliceImages.forEach((sliceImage) => {
255
+ const sliceFolder = sliceImage.slice?.replaceAll(" ", "")?.replaceAll("slice:", "")?.toLowerCase();
256
+ const folder = join(SLICES_DIR, sliceImage.context ?? "default", sliceFolder);
257
+
258
+ if (!existsSync(folder)) {
259
+ mkdirSync(folder, { recursive: true });
260
+ }
261
+
262
+ const base64String = sliceImage.base64Image.replace(/^data:image\/[a-z]+;base64,/, '');
263
+ const buffer = Buffer.from(base64String, 'base64');
264
+ const filePath = join(folder, `screen-${sliceImage.id}.png`);
265
+ writeFileSync(filePath, buffer);
266
+ });
267
+ }
268
+
269
+ sendJSON(res, { success: true, path: join(ROOT, fileName) });
270
+ } catch (err) {
271
+ sendJSON(res, { success: false, error: err.message }, 400);
272
+ }
273
+ });
274
+ return;
275
+ }
276
+
277
+ // ---------------------------------------
278
+ // /api/slice-info
279
+ // ---------------------------------------
280
+ if (pathname === '/api/slice-info' && req.method === 'GET') {
281
+ try {
282
+ const currentContextPath = join(SLICES_DIR, 'current_context.json');
283
+ let contextName = null;
284
+ if (existsSync(currentContextPath)) {
285
+ contextName = JSON.parse(readFileSync(currentContextPath, 'utf-8')).name;
286
+ }
287
+ const indexPath = contextName
288
+ ? join(SLICES_DIR, contextName, 'index.json')
289
+ : join(SLICES_DIR, 'index.json');
290
+ if (!existsSync(indexPath)) return sendJSON(res, { error: 'index.json not found' }, 404);
291
+ const indexData = JSON.parse(readFileSync(indexPath, 'utf-8'));
292
+ const specificationsMap = new Map();
293
+
294
+ const searchBase = contextName ? join(SLICES_DIR, contextName) : SLICES_DIR;
295
+ const codeFiles = findFilesRecursive(searchBase, name => name === 'code-slice.json');
296
+ for (const file of codeFiles) {
297
+ try {
298
+ const cs = JSON.parse(readFileSync(file, 'utf-8'));
299
+ if (cs.id && cs.specifications) specificationsMap.set(cs.id, cs.specifications);
300
+ } catch {}
301
+ }
302
+
303
+ const allSlices = indexData.slices.map(s => ({
304
+ title: s.slice,
305
+ status: s.status,
306
+ assigned: s.assigned,
307
+ id: s.id,
308
+ specifications: specificationsMap.get(s.id)
309
+ }));
310
+
311
+ return sendJSON(res, { slices: allSlices });
312
+ } catch (err) {
313
+ return sendJSON(res, { error: 'Failed to load slice-info', message: err.message }, 500);
314
+ }
315
+ }
316
+
317
+ // ---------------------------------------
318
+ // /api/slicepath
319
+ // ---------------------------------------
320
+ if (pathname === '/api/slicepath' && req.method === 'GET') {
321
+ try {
322
+ const sliceFiles = findFilesRecursive(SLICES_DIR, name => name.endsWith('.slice.json'));
323
+ const slices = sliceFiles.map(f => ({
324
+ path: relative(ROOT, f),
325
+ name: f.split('/').pop()
326
+ }));
327
+ return sendJSON(res, { slices });
328
+ } catch (err) {
329
+ return sendJSON(res, { error: 'Failed to list slice paths', message: err.message }, 500);
330
+ }
331
+ }
332
+
333
+ // ---------------------------------------
334
+ // /api/slices
335
+ // ---------------------------------------
336
+ if (pathname === '/api/slices' && req.method === 'GET') {
337
+ try {
338
+ const includeImages = url.searchParams.get('includeImages') === 'true';
339
+ const revision = url.searchParams.get('revision') ?? 'HEAD';
340
+ const isHEAD = revision === 'HEAD';
341
+ const isRepo = isGitRepo(repoPath);
342
+
343
+ // slice.json files
344
+ const trackedFiles = isRepo ? gitLsTree(repoPath, revision, '.slices') : [];
345
+ const sliceFiles = trackedFiles.filter(f => f.endsWith('slice.json'));
346
+ const trackedScreens = trackedFiles.filter(f => f.match(/screen-\d+\.png$/));
347
+
348
+ // filesystem screens if HEAD
349
+ let allScreens = [];
350
+ if (isHEAD) {
351
+ const fsScreens = findFilesRecursive(SLICES_DIR, name => name.match(/screen-\d+\.png$/));
352
+ allScreens = [...new Set([...fsScreens, ...trackedScreens])];
353
+ } else allScreens = trackedScreens;
354
+
355
+ const slices = [];
356
+ for (const file of sliceFiles) {
357
+ if (isHEAD && existsSync(join(ROOT, file))) {
358
+ slices.push(JSON.parse(readFileSync(join(ROOT, file), 'utf-8')));
359
+ } else if (isRepo) {
360
+ const content = gitShow(repoPath, `${revision}:${file}`);
361
+ if (content) slices.push(JSON.parse(content));
362
+ }
363
+ }
364
+
365
+ const sliceImages = [];
366
+ if (includeImages) {
367
+ for (const screenPath of allScreens) {
368
+ try {
369
+ const buffer = isHEAD && existsSync(screenPath)
370
+ ? readFileSync(screenPath)
371
+ : execSync(`git show ${revision}:${screenPath}`, { cwd: ROOT, encoding: 'buffer' });
372
+
373
+ sliceImages.push({
374
+ id: parseIdWithRegex(screenPath.split('/').pop()) ?? '',
375
+ slice: '',
376
+ title: '',
377
+ base64Image: `data:image/png;base64,${buffer.toString('base64')}`
378
+ });
379
+ } catch {}
380
+ }
381
+ }
382
+
383
+ return sendJSON(res, { slices, sliceImages, meta: { revision, sliceCount: slices.length, screenCount: sliceImages.length } });
384
+ } catch (err) {
385
+ return sendJSON(res, { error: 'Failed to load slices', message: err.message }, 500);
386
+ }
387
+ }
388
+
389
+ // ---------------------------------------
390
+ // /api/config
391
+ // ---------------------------------------
392
+ if (pathname === '/api/config' && req.method === 'GET') {
393
+ const storedData = { version: 1.0 };
394
+ try {
395
+ const gitRepo = isGitRepo(repoPath);
396
+ if (storedData) storedData.gitRepo = gitRepo;
397
+ } catch (e) {}
398
+ return sendJSON(res, storedData);
399
+ }
400
+
401
+ if (pathname === '/api/config' && req.method === 'POST') {
402
+ let body = '';
403
+ req.on('data', chunk => { body += chunk; });
404
+ req.on('end', () => {
405
+ try {
406
+ JSON.parse(body);
407
+ return sendJSON(res, { success: 'Data stored successfully!' });
408
+ } catch (err) {
409
+ return sendJSON(res, { error: 'Failed to store data' }, 500);
410
+ }
411
+ });
412
+ return;
413
+ }
414
+
415
+ // ---------------------------------------
416
+ // /api/progress
417
+ // ---------------------------------------
418
+ if (pathname === '/api/progress' && req.method === 'GET') {
419
+ try {
420
+ const progressPath = join(repoPath, 'progress.txt');
421
+ if (!existsSync(progressPath)) {
422
+ return sendJSON(res, { available: false, progress: [] });
423
+ }
424
+ const content = readFileSync(progressPath, 'utf-8');
425
+ const paragraphs = content
426
+ .split(/(?=>>> Iteration \d+)/)
427
+ .map(p => p.trim())
428
+ .filter(p => p.length > 0);
429
+ return sendJSON(res, { available: true, progress: paragraphs });
430
+ } catch (err) {
431
+ return sendJSON(res, { error: 'Failed to load progress', message: err.message }, 500);
432
+ }
433
+ }
434
+
435
+ // ---------------------------------------
436
+ // /api/delete-slice
437
+ // ---------------------------------------
438
+ if (pathname === '/api/delete-slice' && req.method === 'POST') {
439
+ let body = '';
440
+ req.on('data', chunk => { body += chunk; });
441
+ req.on('end', () => {
442
+ try {
443
+ const data = JSON.parse(body);
444
+ const sliceId = data.id;
445
+
446
+ if (!sliceId) {
447
+ return sendJSON(res, { error: 'Missing required parameter: id' }, 400);
448
+ }
449
+
450
+ function findAndDeleteCodeSliceById(dir, targetId) {
451
+ if (!existsSync(dir)) return { found: false };
452
+ const entries = readdirSync(dir, { withFileTypes: true });
453
+
454
+ for (const entry of entries) {
455
+ const fullPath = join(dir, entry.name);
456
+ if (entry.isDirectory()) {
457
+ const result = findAndDeleteCodeSliceById(fullPath, targetId);
458
+ if (result.found) return result;
459
+ } else if (entry.name === 'code-slice.json') {
460
+ try {
461
+ const codeSliceContent = readFileSync(fullPath, 'utf-8');
462
+ const codeSlice = JSON.parse(codeSliceContent);
463
+ if (codeSlice.id === targetId) {
464
+ unlinkSync(fullPath);
465
+ return { found: true, deletedPath: fullPath };
466
+ }
467
+ } catch (error) {
468
+ console.error(`Error reading code-slice.json at ${fullPath}:`, error);
469
+ }
470
+ }
471
+ }
472
+ return { found: false };
473
+ }
474
+
475
+ if (!existsSync(SLICES_DIR)) {
476
+ return sendJSON(res, { error: '.slices directory not found' }, 404);
477
+ }
478
+
479
+ const result = findAndDeleteCodeSliceById(SLICES_DIR, sliceId);
480
+ if (result.found) {
481
+ return sendJSON(res, {
482
+ success: true,
483
+ message: `Successfully deleted code-slice.json with id: ${sliceId}`,
484
+ deletedPath: result.deletedPath
485
+ });
486
+ } else {
487
+ return sendJSON(res, { error: `No code-slice.json file found with id: ${sliceId}` }, 404);
488
+ }
489
+ } catch (err) {
490
+ return sendJSON(res, { error: 'Failed to delete code-slice.json', message: err.message }, 500);
491
+ }
492
+ });
493
+ return;
494
+ }
495
+
496
+ // ---------------------------------------
497
+ // /api/git
498
+ // ---------------------------------------
499
+ if (pathname === '/api/git' && req.method === 'GET') {
500
+ const storedData = { version: 1.0 };
501
+ try {
502
+ const gitRepo = isGitRepo(repoPath);
503
+ if (storedData) storedData.gitRepo = gitRepo;
504
+ } catch (e) {}
505
+ return sendJSON(res, storedData);
506
+ }
507
+
508
+ if (pathname === '/api/git' && req.method === 'POST') {
509
+ try {
510
+ let isRepo = isGitRepo(repoPath);
511
+ if (!isRepo) {
512
+ gitInit(repoPath);
513
+ }
514
+ gitAdd(repoPath, '.slices');
515
+ const statusResult = gitStatus(repoPath);
516
+
517
+ let commitResult = undefined;
518
+ if (statusResult.staged?.length > 0) {
519
+ commitResult = gitCommit(repoPath, '(chore) slices', '.slices');
520
+ }
521
+
522
+ return sendJSON(res, {
523
+ branch: commitResult?.branch,
524
+ revision: commitResult?.commit
525
+ });
526
+ } catch (error) {
527
+ console.error('Error committing:', error);
528
+ return sendJSON(res, {
529
+ error: 'Failed to commit slices',
530
+ message: error instanceof Error ? error.message : 'Unknown error'
531
+ }, 500);
532
+ }
533
+ }
534
+
535
+ // ---------------------------------------
536
+ // 404
537
+ // ---------------------------------------
538
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
539
+ res.end('Not Found');
540
+ });
541
+
542
+ // ---------------------------------------
543
+ // Listen
544
+ // ---------------------------------------
545
+ const port = parseInt(process.env.PORT || '3001', 10);
546
+ server.listen(port, () => {
547
+ console.log(`🚀 Server running at http://localhost:${port}`);
548
+ console.log(`📁 ROOT: ${ROOT}`);
549
+ console.log(`📁 SLICES_DIR: ${SLICES_DIR}`);
550
+ console.log(`📁 repoPath: ${repoPath}`);
551
+ console.log('💓 GET /api/ping');
552
+ console.log('📥 POST /api/generate');
553
+ console.log('📄 GET /api/slice-info');
554
+ console.log('📂 GET /api/slicepath');
555
+ console.log('📦 GET /api/slices?includeImages=true');
556
+ console.log('⚙️ GET/POST /api/config');
557
+ console.log('📊 GET /api/progress');
558
+ console.log('🗑️ POST /api/delete-slice');
559
+ console.log('🔧 GET/POST /api/git');
560
+ });