@rashidee/co2 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/dist/index.js +25449 -0
  2. package/drizzle/0000_remarkable_charles_xavier.sql +210 -0
  3. package/drizzle/0001_wise_wolf_cub.sql +13 -0
  4. package/drizzle/0002_secret_spyke.sql +16 -0
  5. package/drizzle/0003_workspace_config_singleton.sql +16 -0
  6. package/drizzle/0004_terminal_claim_v120.sql +11 -0
  7. package/drizzle/0005_brainstorm_v124.sql +10 -0
  8. package/drizzle/0006_project_v140.sql +6 -0
  9. package/drizzle/0007_prd_wizard_v124.sql +14 -0
  10. package/drizzle/0008_workflow_artifact_v126.sql +24 -0
  11. package/drizzle/0009_project_import_v150.sql +7 -0
  12. package/drizzle/0010_quality_v151.sql +3 -0
  13. package/drizzle/meta/0000_snapshot.json +1335 -0
  14. package/drizzle/meta/0001_snapshot.json +1416 -0
  15. package/drizzle/meta/0002_snapshot.json +1510 -0
  16. package/drizzle/meta/_journal.json +83 -0
  17. package/package.json +41 -0
  18. package/plugin/.claude-plugin/marketplace.json +36 -0
  19. package/plugin/.claude-plugin/plugin.json +22 -0
  20. package/plugin/README.md +625 -0
  21. package/plugin/SKILLS.md +350 -0
  22. package/plugin/co2.svg +5 -0
  23. package/plugin/commands/brainstorm-loop.md +129 -0
  24. package/plugin/skills/conductor-defect/SKILL.md +898 -0
  25. package/plugin/skills/conductor-feature-develop/SKILL.md +1383 -0
  26. package/plugin/skills/conductor-feature-develop/references/implementation-domain-template.md +131 -0
  27. package/plugin/skills/conductor-feature-develop/references/implementation-master-template.md +89 -0
  28. package/plugin/skills/conductor-feature-develop/references/playwright-setup.md +224 -0
  29. package/plugin/skills/conductor-feature-prepare/SKILL.md +604 -0
  30. package/plugin/skills/depgen-k8s/SKILL.md +468 -0
  31. package/plugin/skills/depgen-k8s/references/dockerfile-laravel.md +197 -0
  32. package/plugin/skills/depgen-k8s/references/dockerfile-nodejs.md +163 -0
  33. package/plugin/skills/depgen-k8s/references/dockerfile-spring-boot.md +174 -0
  34. package/plugin/skills/depgen-k8s/references/k8s-patterns.md +869 -0
  35. package/plugin/skills/mockgen-shadcn/SKILL.md +1067 -0
  36. package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +1467 -0
  37. package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +498 -0
  38. package/plugin/skills/mockgen-shadcn/references/mockup-index-template.md +216 -0
  39. package/plugin/skills/mockgen-tailwind/SKILL.md +904 -0
  40. package/plugin/skills/mockgen-tailwind/references/admin-layout-template.md +720 -0
  41. package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +498 -0
  42. package/plugin/skills/mockgen-tailwind/references/mockup-index-template.md +190 -0
  43. package/plugin/skills/modelgen-nosql/SKILL.md +617 -0
  44. package/plugin/skills/modelgen-nosql/references/model-extraction-methodology.md +497 -0
  45. package/plugin/skills/modelgen-nosql/references/nosql-design-guide.md +258 -0
  46. package/plugin/skills/modelgen-relational/SKILL.md +707 -0
  47. package/plugin/skills/modelgen-relational/references/model-extraction-methodology.md +499 -0
  48. package/plugin/skills/specgen-flutter-riverpod/SKILL.md +898 -0
  49. package/plugin/skills/specgen-flutter-riverpod/references/component-patterns.md +971 -0
  50. package/plugin/skills/specgen-flutter-riverpod/references/notification-patterns.md +457 -0
  51. package/plugin/skills/specgen-flutter-riverpod/references/routing-patterns.md +362 -0
  52. package/plugin/skills/specgen-flutter-riverpod/references/security-patterns.md +709 -0
  53. package/plugin/skills/specgen-flutter-riverpod/references/spec-template.md +1566 -0
  54. package/plugin/skills/specgen-flutter-riverpod/references/state-patterns.md +518 -0
  55. package/plugin/skills/specgen-flutter-riverpod/references/storage-patterns.md +370 -0
  56. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/SKILL.md +984 -0
  57. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/batch-patterns.md +359 -0
  58. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/messaging-patterns.md +468 -0
  59. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/modulith-patterns.md +461 -0
  60. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/reporting-patterns.md +803 -0
  61. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/security-patterns.md +501 -0
  62. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/spec-template.md +1268 -0
  63. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/web-patterns.md +301 -0
  64. package/plugin/skills/specgen-node-cli-web/SKILL.md +561 -0
  65. package/plugin/skills/specgen-node-cli-web/references/auth-patterns.md +293 -0
  66. package/plugin/skills/specgen-node-cli-web/references/cli-packaging-patterns.md +250 -0
  67. package/plugin/skills/specgen-node-cli-web/references/server-patterns.md +247 -0
  68. package/plugin/skills/specgen-node-cli-web/references/spec-template.md +401 -0
  69. package/plugin/skills/specgen-node-cli-web/references/testing-patterns.md +174 -0
  70. package/plugin/skills/specgen-react-mui/SKILL.md +789 -0
  71. package/plugin/skills/specgen-react-mui/references/component-patterns.md +1077 -0
  72. package/plugin/skills/specgen-react-mui/references/reporting-patterns.md +1998 -0
  73. package/plugin/skills/specgen-react-mui/references/routing-patterns.md +461 -0
  74. package/plugin/skills/specgen-react-mui/references/security-patterns.md +599 -0
  75. package/plugin/skills/specgen-react-mui/references/spec-template.md +1030 -0
  76. package/plugin/skills/specgen-react-mui/references/state-patterns.md +327 -0
  77. package/plugin/skills/specgen-react-tailwind/SKILL.md +859 -0
  78. package/plugin/skills/specgen-react-tailwind/references/component-patterns.md +1212 -0
  79. package/plugin/skills/specgen-react-tailwind/references/reporting-patterns.md +1529 -0
  80. package/plugin/skills/specgen-react-tailwind/references/routing-patterns.md +669 -0
  81. package/plugin/skills/specgen-react-tailwind/references/security-patterns.md +656 -0
  82. package/plugin/skills/specgen-react-tailwind/references/spec-template.md +1237 -0
  83. package/plugin/skills/specgen-react-tailwind/references/state-patterns.md +327 -0
  84. package/plugin/skills/specgen-sdk-java/SKILL.md +933 -0
  85. package/plugin/skills/specgen-sdk-java/references/api-extraction-patterns.md +247 -0
  86. package/plugin/skills/specgen-sdk-java/references/http-client-patterns.md +495 -0
  87. package/plugin/skills/specgen-sdk-java/references/packaging-patterns.md +366 -0
  88. package/plugin/skills/specgen-sdk-java/references/spec-template.md +956 -0
  89. package/plugin/skills/specgen-spring-jpa-jtehtmx/SKILL.md +1284 -0
  90. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/batch-patterns.md +1224 -0
  91. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/jasper-patterns.md +1638 -0
  92. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/messaging-patterns.md +512 -0
  93. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/modulith-patterns.md +354 -0
  94. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/security-patterns.md +551 -0
  95. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/spec-template.md +2502 -0
  96. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/web-patterns.md +503 -0
  97. package/plugin/skills/specgen-spring-jpa-restapi/SKILL.md +990 -0
  98. package/plugin/skills/specgen-spring-jpa-restapi/references/batch-patterns.md +1224 -0
  99. package/plugin/skills/specgen-spring-jpa-restapi/references/messaging-patterns.md +512 -0
  100. package/plugin/skills/specgen-spring-jpa-restapi/references/modulith-patterns.md +279 -0
  101. package/plugin/skills/specgen-spring-jpa-restapi/references/reporting-patterns.md +1536 -0
  102. package/plugin/skills/specgen-spring-jpa-restapi/references/restapi-patterns.md +722 -0
  103. package/plugin/skills/specgen-spring-jpa-restapi/references/security-patterns.md +351 -0
  104. package/plugin/skills/specgen-spring-jpa-restapi/references/spec-template.md +553 -0
  105. package/plugin/skills/specgen-ts-cli/SKILL.md +861 -0
  106. package/plugin/skills/specgen-ts-cli/references/async-patterns.md +1101 -0
  107. package/plugin/skills/specgen-ts-cli/references/command-patterns.md +383 -0
  108. package/plugin/skills/specgen-ts-cli/references/config-patterns.md +430 -0
  109. package/plugin/skills/specgen-ts-cli/references/database-patterns.md +750 -0
  110. package/plugin/skills/specgen-ts-cli/references/packaging-patterns.md +426 -0
  111. package/plugin/skills/specgen-ts-cli/references/spec-template.md +1391 -0
  112. package/plugin/skills/specgen-ts-cli/references/testing-patterns.md +909 -0
  113. package/plugin/skills/testgen-functional/SKILL.md +1381 -0
  114. package/plugin/skills/tracegen-matrix/SKILL.md +243 -0
  115. package/plugin/skills/util-preparek8senv/SKILL.md +422 -0
  116. package/plugin/skills/util-projectinit/SKILL.md +307 -0
  117. package/plugin/skills/util-projectinit/reference/brainstorm-loop.md +129 -0
  118. package/plugin/skills/util-projectinit/reference/brainstorm-protocol.md +144 -0
  119. package/plugin/skills/util-projectinit/reference/brainstorm-setup-guide.md +153 -0
  120. package/plugin/skills/util-projectinit/reference/brainstorm-template-appdev.md +87 -0
  121. package/plugin/skills/util-projectinit/reference/co2-context-generation-guide.md +761 -0
  122. package/plugin/skills/util-projectsync/SKILL.md +501 -0
  123. package/plugin/skills/util-updprd/SKILL.md +324 -0
  124. package/plugin/skills/util-usanalyzer/SKILL.md +307 -0
  125. package/plugin/skills/util-ustagger/SKILL.md +154 -0
  126. package/static/assets/abnfDiagram-VRR7QNED-DvPuwyCV.js +1 -0
  127. package/static/assets/arc-DRKw7sjs.js +1 -0
  128. package/static/assets/architectureDiagram-ZJ3FMSHR-CaNcxUbC.js +36 -0
  129. package/static/assets/blockDiagram-677ZJIJ3-Demd4AOK.js +132 -0
  130. package/static/assets/c4Diagram-LMCZKHZV-PVKUfc2x.js +10 -0
  131. package/static/assets/channel-C2nVbuwv.js +1 -0
  132. package/static/assets/chunk-2Q5K7J3B-CExf8ww_.js +1 -0
  133. package/static/assets/chunk-32BRIVSS-CFPaoONF.js +1 -0
  134. package/static/assets/chunk-5VM5RSS4-DE2esM67.js +15 -0
  135. package/static/assets/chunk-EX3LRPZG-SON_YdDG.js +231 -0
  136. package/static/assets/chunk-JWPE2WC7-Qxah8LNr.js +1 -0
  137. package/static/assets/chunk-MOJQB5TN-BcWGQV3j.js +88 -0
  138. package/static/assets/chunk-RYQCIY6F-DkDj-OR5.js +1 -0
  139. package/static/assets/chunk-V7JOEXUC-BlI1Mrah.js +206 -0
  140. package/static/assets/chunk-VR4S4FIN-Agf__duj.js +1 -0
  141. package/static/assets/chunk-XXDRQBXY-B5CfAh85.js +1 -0
  142. package/static/assets/classDiagram-OUVF2IWQ-DXJIljsR.js +1 -0
  143. package/static/assets/classDiagram-v2-EOCWNBFH-DXJIljsR.js +1 -0
  144. package/static/assets/cose-bilkent-JH36ORCC-DO0cQkcy.js +1 -0
  145. package/static/assets/cynefin-VYW2F7L2-CfOnsnXc.js +178 -0
  146. package/static/assets/cynefinDiagram-TSTJHNR4-BJ8tULAv.js +62 -0
  147. package/static/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
  148. package/static/assets/dagre-VKFMJZFB-DfjwzHqP.js +4 -0
  149. package/static/assets/defaultLocale-DX6XiGOO.js +1 -0
  150. package/static/assets/diagram-FQU43EPY-CndSOIIM.js +3 -0
  151. package/static/assets/diagram-G47NLZAW-Czf5hN0Z.js +24 -0
  152. package/static/assets/diagram-NH7WQ7WH-COAyojGh.js +24 -0
  153. package/static/assets/diagram-OA4YK3LP-H8PoiN2m.js +30 -0
  154. package/static/assets/diagram-WEI45ONY-DzcaVzjp.js +41 -0
  155. package/static/assets/ebnfDiagram-CCIWWBDH-DPazGYLJ.js +1 -0
  156. package/static/assets/erDiagram-Q63AITRT-Bo-fubgF.js +85 -0
  157. package/static/assets/flowDiagram-23GEKE2U-CSvxUDwh.js +156 -0
  158. package/static/assets/ganttDiagram-NO4QXBWP-DnBveWX6.js +292 -0
  159. package/static/assets/gitGraphDiagram-IHSO6WYX-B3rVL8c-.js +106 -0
  160. package/static/assets/graph-C9eacEi8.js +1 -0
  161. package/static/assets/index-LZSQkLE2.css +41 -0
  162. package/static/assets/index-y7XDSgMG.js +495 -0
  163. package/static/assets/infoDiagram-FWYZ7A6U-CvWV9yy2.js +2 -0
  164. package/static/assets/init-Gi6I4Gst.js +1 -0
  165. package/static/assets/ishikawaDiagram-FXEZZL3T-DF28a1N6.js +70 -0
  166. package/static/assets/journeyDiagram-5HDEW3XC-CB6CugYk.js +139 -0
  167. package/static/assets/kanban-definition-HUTT4EX6-ResSLF6d.js +89 -0
  168. package/static/assets/katex-C5jXJg4s.js +257 -0
  169. package/static/assets/layout-DEXfKzaS.js +1 -0
  170. package/static/assets/linear-CLO--ryk.js +1 -0
  171. package/static/assets/map-Czzmt4hB.js +1 -0
  172. package/static/assets/mindmap-definition-LN4V7U3C-BvW3EbxX.js +96 -0
  173. package/static/assets/ordinal-Cboi1Yqb.js +1 -0
  174. package/static/assets/pegDiagram-2B236MQR-BHCgqrrM.js +1 -0
  175. package/static/assets/pieDiagram-ENE6RG2P-DVmCQ5FA.js +39 -0
  176. package/static/assets/quadrantDiagram-ABIIQ3AL-B3e5KR52.js +7 -0
  177. package/static/assets/railroadDiagram-RFXS5EU6-Csv5ysLc.js +1 -0
  178. package/static/assets/requirementDiagram-TGXJPOKE-DOMVzvnB.js +84 -0
  179. package/static/assets/sankeyDiagram-HTMAVEWB-B1dcTOvy.js +40 -0
  180. package/static/assets/sequenceDiagram-DBY2YBRQ-BY414yeO.js +162 -0
  181. package/static/assets/sizeCapture-X5ZJPWSS-CuJfrfBN.js +1 -0
  182. package/static/assets/stateDiagram-2N3HPSRC-FX4rJkzV.js +1 -0
  183. package/static/assets/stateDiagram-v2-6OUMAXLB-BVufbO_H.js +1 -0
  184. package/static/assets/swimlanes-5IMT3BWC-Sfy58mmv.js +2 -0
  185. package/static/assets/swimlanesDiagram-G3AALYLV-wDCDw08H.js +8 -0
  186. package/static/assets/timeline-definition-FHXFAJF6-CmV7by8p.js +120 -0
  187. package/static/assets/vennDiagram-L72KCM5P-FqIyFZFt.js +34 -0
  188. package/static/assets/wardleyDiagram-EHGQE667-BZ06O45i.js +78 -0
  189. package/static/assets/xychartDiagram-FW5EYKEG-BOkffR06.js +7 -0
  190. package/static/index.html +24 -0
@@ -0,0 +1,498 @@
1
+ # Mockup Hub — Single Node Server for ALL Application Mockups
2
+
3
+ The Mockup Hub is ONE zero-dependency Node.js application at `<root>/mockup/` that serves
4
+ the mockups of EVERY application in the project. Per-application mockup folders
5
+ (`<app_folder>/context/mockup/`) contain ONLY assets (HTML/CSS/JS/images + a manifest) —
6
+ they contain NO server of their own.
7
+
8
+ - **Tailwind mockups** (`mockgen-tailwind`): the hub assembles shell + header + sidebar +
9
+ content + footer server-side at `/{app_slug}/{role}/{page}`.
10
+ - **shadcn mockups** (`mockgen-shadcn`): the hub serves the app's built `dist/` folder at
11
+ `/{app_slug}/...` with SPA fallback to `dist/index.html`.
12
+ - **Landing page** at `/`: dynamically lists ALL applications and their roles. Roles are
13
+ clickable only when the app's mockup is ready; otherwise they render as disabled
14
+ (grayed, no `href`) chips with a status hint ("Mockups not generated yet" /
15
+ "Build required").
16
+ - **Port** is configurable: `PORT` env var → `mockup.config.json` `port` → default `3000`.
17
+
18
+ The hub has **zero npm dependencies** — `npm start` (or `node server.js`) works with no
19
+ `npm install`.
20
+
21
+ ## File Structure
22
+
23
+ ```
24
+ <root>/
25
+ mockup/ # ← the hub (this template)
26
+ server.js # zero-dependency Node HTTP server
27
+ package.json # no dependencies; "start": "node server.js"
28
+ mockup.config.json # { "port": 3000 } — user-editable
29
+ .gitignore
30
+ 1_first_app/
31
+ context/mockup/ # assets only (+ mockup-manifest.json)
32
+ 2_second_app/
33
+ context/mockup/
34
+ ```
35
+
36
+ ## Ensure-Hub Rules (run by every mockgen-* execution, including module-filtered runs)
37
+
38
+ 1. If `<root>/mockup/server.js` does NOT exist: create ALL four hub files from the
39
+ templates below.
40
+ 2. If it exists: read the `HUB_VERSION` constant in the existing `server.js`.
41
+ - Existing version **< template version** → overwrite `server.js` and `package.json`
42
+ with the templates (upgrade).
43
+ - Existing version **>= template version**, or no marker found (user-customized) →
44
+ leave `server.js` and `package.json` untouched.
45
+ 3. `mockup.config.json`: create ONLY if missing. NEVER overwrite an existing one — it
46
+ holds the user's port choice. It is per-machine and gitignored.
47
+ 4. `.gitignore`: create ONLY if missing.
48
+ 5. Never place per-application assets inside `<root>/mockup/`. Conversely, everything the
49
+ hub generates or needs (config, logs, any `node_modules/`) stays inside `<root>/mockup/`
50
+ and is covered by its `.gitignore`.
51
+
52
+ ## mockup-manifest.json Schema (written to `<app_folder>/context/mockup/`)
53
+
54
+ Every mockgen-* run writes/updates this file. The hub uses it for discovery, routing and
55
+ the landing page.
56
+
57
+ | Field | Type | Description |
58
+ |-------|------|-------------|
59
+ | `app` | string | App slug used in URLs = app folder name minus leading `<number>_` (e.g. `hub_middleware`) |
60
+ | `appName` | string | Display name (e.g. "Hub Middleware") |
61
+ | `description` | string | Short app description from PRD.md |
62
+ | `stack` | string | `"tailwind"` or `"shadcn"` |
63
+ | `version` | string | Target version the mockups were generated for (e.g. `v1.0.3`) |
64
+ | `generatedAt` | string | ISO date of the last generation |
65
+ | `roles` | array | One entry per role: `{ "name", "slug", "screens" }`. `slug` uses the stack's convention — snake_case (tailwind) or kebab-case (shadcn). `screens` = number of screens generated for the role |
66
+ | `reports` | array (optional) | Report layout file names, if any |
67
+
68
+ Example:
69
+
70
+ ```json
71
+ {
72
+ "app": "hub_middleware",
73
+ "appName": "Hub Middleware",
74
+ "description": "Central middleware for labour mobility data exchange.",
75
+ "stack": "tailwind",
76
+ "version": "v1.0.3",
77
+ "generatedAt": "2026-07-12",
78
+ "roles": [
79
+ { "name": "Hub Administrator", "slug": "hub_administrator", "screens": 12 },
80
+ { "name": "Hub Operation Support", "slug": "hub_operation_support", "screens": 9 }
81
+ ],
82
+ "reports": ["staff_allocation_summary.html"]
83
+ }
84
+ ```
85
+
86
+ ## server.js Template
87
+
88
+ ```javascript
89
+ #!/usr/bin/env node
90
+ /*
91
+ * CO2 Mockup Hub — one server for ALL application mockups.
92
+ * HUB_VERSION: 1 (marker used by mockgen-* skills to decide upgrades — do not remove)
93
+ *
94
+ * Discovers every root-level application folder containing context/PRD.md.
95
+ * Apps with a context/mockup/mockup-manifest.json are routable:
96
+ * - stack "tailwind": pages assembled from partials + content fragments
97
+ * - stack "shadcn": served from the built dist/ folder (SPA fallback)
98
+ * Port: PORT env var > mockup.config.json "port" > 3000.
99
+ */
100
+ 'use strict';
101
+
102
+ const http = require('http');
103
+ const fs = require('fs');
104
+ const path = require('path');
105
+
106
+ const HUB_VERSION = 1;
107
+ const HUB_DIR = __dirname;
108
+ const ROOT_DIR = path.resolve(HUB_DIR, '..');
109
+ const SAFE_SEG = /^[A-Za-z0-9._-]+$/;
110
+
111
+ function resolvePort() {
112
+ const envPort = parseInt(process.env.PORT || '', 10);
113
+ if (Number.isInteger(envPort) && envPort > 0) return envPort;
114
+ try {
115
+ const cfg = JSON.parse(fs.readFileSync(path.join(HUB_DIR, 'mockup.config.json'), 'utf8'));
116
+ const cfgPort = parseInt(cfg.port, 10);
117
+ if (Number.isInteger(cfgPort) && cfgPort > 0) return cfgPort;
118
+ } catch (err) { /* missing/invalid config — use default */ }
119
+ return 3000;
120
+ }
121
+
122
+ const MIME = {
123
+ '.html': 'text/html; charset=utf-8',
124
+ '.css': 'text/css; charset=utf-8',
125
+ '.js': 'text/javascript; charset=utf-8',
126
+ '.mjs': 'text/javascript; charset=utf-8',
127
+ '.json': 'application/json; charset=utf-8',
128
+ '.svg': 'image/svg+xml',
129
+ '.png': 'image/png',
130
+ '.jpg': 'image/jpeg',
131
+ '.jpeg': 'image/jpeg',
132
+ '.gif': 'image/gif',
133
+ '.webp': 'image/webp',
134
+ '.ico': 'image/x-icon',
135
+ '.woff': 'font/woff',
136
+ '.woff2': 'font/woff2',
137
+ '.ttf': 'font/ttf',
138
+ '.map': 'application/json',
139
+ '.txt': 'text/plain; charset=utf-8',
140
+ '.pdf': 'application/pdf',
141
+ };
142
+
143
+ // ── Discovery ────────────────────────────────────────────────────────────────
144
+
145
+ function titleCase(slug) {
146
+ return slug.replace(/[_-]+/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
147
+ }
148
+
149
+ function rolesFromPrd(appDir) {
150
+ try {
151
+ const prd = fs.readFileSync(path.join(appDir, 'context', 'PRD.md'), 'utf8');
152
+ const names = new Set();
153
+ for (const line of prd.split('\n')) {
154
+ if (line.includes('~~')) continue; // skip strikethrough (deprecated) items
155
+ const m = line.match(/As an? (.+?) user/);
156
+ if (m) names.add(m[1].trim());
157
+ }
158
+ return Array.from(names).map((name) => ({ name, slug: null, screens: 0 }));
159
+ } catch (err) {
160
+ return [];
161
+ }
162
+ }
163
+
164
+ function discoverApps() {
165
+ let entries;
166
+ try { entries = fs.readdirSync(ROOT_DIR, { withFileTypes: true }); } catch (err) { return []; }
167
+ const apps = [];
168
+ for (const entry of entries) {
169
+ if (!entry.isDirectory()) continue;
170
+ const appDir = path.join(ROOT_DIR, entry.name);
171
+ if (!fs.existsSync(path.join(appDir, 'context', 'PRD.md'))) continue;
172
+ const slug = entry.name.replace(/^\d+_/, '');
173
+ const mockupDir = path.join(appDir, 'context', 'mockup');
174
+ let manifest = null;
175
+ try {
176
+ manifest = JSON.parse(fs.readFileSync(path.join(mockupDir, 'mockup-manifest.json'), 'utf8'));
177
+ } catch (err) { /* mockups not generated yet */ }
178
+ let status = 'not-generated';
179
+ if (manifest && manifest.stack === 'shadcn') {
180
+ status = fs.existsSync(path.join(mockupDir, 'dist', 'index.html')) ? 'ready' : 'build-required';
181
+ } else if (manifest) {
182
+ status = fs.existsSync(path.join(mockupDir, 'partials', 'shell.html')) ? 'ready' : 'not-generated';
183
+ }
184
+ apps.push({
185
+ slug,
186
+ folder: entry.name,
187
+ appName: (manifest && manifest.appName) || titleCase(slug),
188
+ description: (manifest && manifest.description) || '',
189
+ version: (manifest && manifest.version) || '',
190
+ stack: manifest ? manifest.stack : null,
191
+ status,
192
+ mockupDir,
193
+ roles: manifest ? (manifest.roles || []) : rolesFromPrd(appDir),
194
+ });
195
+ }
196
+ return apps.sort((a, b) => a.folder.localeCompare(b.folder));
197
+ }
198
+
199
+ function findApp(slug) {
200
+ return discoverApps().find((a) => a.slug === slug) || null;
201
+ }
202
+
203
+ // ── Response helpers ─────────────────────────────────────────────────────────
204
+
205
+ function send(res, code, body, type) {
206
+ res.writeHead(code, { 'Content-Type': type || 'text/html; charset=utf-8' });
207
+ res.end(body);
208
+ }
209
+
210
+ function redirect(res, location) {
211
+ res.writeHead(302, { Location: location });
212
+ res.end();
213
+ }
214
+
215
+ function escapeHtml(s) {
216
+ return String(s)
217
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;')
218
+ .replace(/>/g, '&gt;').replace(/"/g, '&quot;');
219
+ }
220
+
221
+ function safeJoin(baseDir, segments) {
222
+ if (!segments.every((s) => SAFE_SEG.test(s))) return null;
223
+ const target = path.normalize(path.join(baseDir, segments.join(path.sep)));
224
+ if (target !== baseDir && !target.startsWith(baseDir + path.sep)) return null;
225
+ return target;
226
+ }
227
+
228
+ function serveFile(res, filePath) {
229
+ if (!filePath) return false;
230
+ let stat;
231
+ try { stat = fs.statSync(filePath); } catch (err) { return false; }
232
+ if (!stat.isFile()) return false;
233
+ const type = MIME[path.extname(filePath).toLowerCase()] || 'application/octet-stream';
234
+ send(res, 200, fs.readFileSync(filePath), type);
235
+ return true;
236
+ }
237
+
238
+ function readText(filePath) {
239
+ try { return fs.readFileSync(filePath, 'utf8'); } catch (err) { return null; }
240
+ }
241
+
242
+ // ── Landing page ─────────────────────────────────────────────────────────────
243
+
244
+ const LANDING_CSS = `
245
+ * { box-sizing: border-box; margin: 0; }
246
+ body { font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
247
+ background: #f1f5f9; color: #0f172a; padding: 40px 20px; }
248
+ .wrap { max-width: 960px; margin: 0 auto; }
249
+ h1 { font-size: 26px; margin-bottom: 4px; }
250
+ .sub { color: #64748b; font-size: 14px; margin-bottom: 28px; }
251
+ .card { background: #fff; border: 1px solid #e2e8f0; border-radius: 12px;
252
+ padding: 20px; margin-bottom: 16px; }
253
+ .card h2 { font-size: 18px; display: inline; }
254
+ .meta { color: #94a3b8; font-size: 12px; margin-left: 8px; }
255
+ .badge { display: inline-block; font-size: 11px; padding: 2px 8px; border-radius: 999px;
256
+ margin-left: 8px; vertical-align: 2px; }
257
+ .badge.ready { background: #dcfce7; color: #166534; }
258
+ .badge.pending { background: #fef3c7; color: #92400e; }
259
+ .hint { color: #94a3b8; font-size: 12px; margin-top: 10px; }
260
+ .hint code { background: #f1f5f9; padding: 1px 5px; border-radius: 4px; }
261
+ .roles { margin-top: 12px; display: flex; flex-wrap: wrap; gap: 8px; }
262
+ .role { display: inline-block; font-size: 13px; padding: 6px 14px; border-radius: 8px;
263
+ border: 1px solid #cbd5e1; text-decoration: none; color: #0f172a; }
264
+ a.role:hover { border-color: #2563eb; color: #2563eb; }
265
+ .role.disabled { color: #cbd5e1; border-color: #e2e8f0; cursor: not-allowed; }
266
+ .index-link { font-size: 12px; margin-left: 8px; }
267
+ .foot { color: #94a3b8; font-size: 12px; text-align: center; margin-top: 28px; }
268
+ `;
269
+
270
+ function renderRoleChips(app) {
271
+ if (!app.roles.length) {
272
+ return '<span class="role disabled">No roles found</span>';
273
+ }
274
+ return app.roles.map((role) => {
275
+ if (app.status === 'ready' && role.slug) {
276
+ return '<a class="role" href="/' + app.slug + '/' + role.slug + '/home">'
277
+ + escapeHtml(role.name) + ' &rarr;</a>';
278
+ }
279
+ return '<span class="role disabled" aria-disabled="true">' + escapeHtml(role.name) + '</span>';
280
+ }).join('\n ');
281
+ }
282
+
283
+ function renderAppCard(app) {
284
+ const badge = app.status === 'ready'
285
+ ? '<span class="badge ready">Ready' + (app.stack ? ' · ' + escapeHtml(app.stack) : '') + '</span>'
286
+ : (app.status === 'build-required'
287
+ ? '<span class="badge pending">Build required</span>'
288
+ : '<span class="badge pending">Not generated yet</span>');
289
+ const indexLink = app.status === 'ready'
290
+ ? '<a class="index-link" href="/' + app.slug + '">screen index</a>' : '';
291
+ let hint = '';
292
+ if (app.status === 'build-required') {
293
+ hint = '<p class="hint">Run <code>cd ' + escapeHtml(app.folder)
294
+ + '/context/mockup &amp;&amp; npm install &amp;&amp; npm run build</code> to make this app viewable.</p>';
295
+ } else if (app.status === 'not-generated') {
296
+ hint = '<p class="hint">Run a <code>mockgen-*</code> skill for this application to generate its mockups.</p>';
297
+ }
298
+ return '<div class="card">\n'
299
+ + ' <h2>' + escapeHtml(app.appName) + '</h2>'
300
+ + (app.version ? '<span class="meta">' + escapeHtml(app.version) + '</span>' : '')
301
+ + badge + indexLink + '\n'
302
+ + (app.description ? ' <p class="sub" style="margin:6px 0 0">' + escapeHtml(app.description) + '</p>\n' : '')
303
+ + ' <div class="roles">\n ' + renderRoleChips(app) + '\n </div>\n'
304
+ + hint + '\n</div>';
305
+ }
306
+
307
+ function renderLanding(apps) {
308
+ return '<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8">\n'
309
+ + '<meta name="viewport" content="width=device-width, initial-scale=1.0">\n'
310
+ + '<title>Mockup Hub</title>\n<style>' + LANDING_CSS + '</style>\n</head>\n<body>\n'
311
+ + '<div class="wrap">\n<h1>Mockup Hub</h1>\n'
312
+ + '<p class="sub">All application mockups, served from one place. '
313
+ + apps.length + ' application(s) discovered. Click a role to open its dashboard.</p>\n'
314
+ + apps.map(renderAppCard).join('\n')
315
+ + '\n<p class="foot">CO2 Mockup Hub v' + HUB_VERSION
316
+ + ' &middot; port via PORT env or mockup.config.json</p>\n</div>\n</body>\n</html>';
317
+ }
318
+
319
+ function notReadyPage(app) {
320
+ return renderLanding([app]);
321
+ }
322
+
323
+ function notFoundPage(what) {
324
+ return '<!DOCTYPE html><html><body style="font-family:sans-serif;padding:40px">'
325
+ + '<h1>404 — Not found' + (what ? ': ' + escapeHtml(what) : '') + '</h1>'
326
+ + '<p><a href="/">Back to Mockup Hub</a></p></body></html>';
327
+ }
328
+
329
+ // ── Tailwind assembly ────────────────────────────────────────────────────────
330
+
331
+ function assemblePage(app, role, page) {
332
+ const dir = app.mockupDir;
333
+ const shell = readText(path.join(dir, 'partials', 'shell.html'));
334
+ let header = readText(path.join(dir, 'partials', 'header.html'));
335
+ const sidebar = readText(path.join(dir, 'partials', 'sidebar-' + role + '.html'));
336
+ const footer = readText(path.join(dir, 'partials', 'footer.html'));
337
+ const content = readText(path.join(dir, role, 'content', page + '.html'));
338
+ if (!shell || !header || !sidebar || !footer) return null;
339
+ header = header.replace(/\{\{ROLE\}\}/g, role).replace(/\{\{APP_SLUG\}\}/g, app.slug);
340
+ const body = content
341
+ || '<div class="p-6 text-red-500">Page not found: ' + escapeHtml(page) + '</div>';
342
+ return shell
343
+ .replace('{{HEADER}}', header)
344
+ .replace('{{SIDEBAR}}', sidebar.replace(/\{\{APP_SLUG\}\}/g, app.slug))
345
+ .replace('{{CONTENT}}', body)
346
+ .replace('{{FOOTER}}', footer);
347
+ }
348
+
349
+ // ── Routing ──────────────────────────────────────────────────────────────────
350
+
351
+ function routeTailwind(app, segs, res) {
352
+ if (segs.length === 1) {
353
+ if (serveFile(res, path.join(app.mockupDir, 'MOCKUP.html'))) return;
354
+ const first = app.roles.find((r) => r.slug);
355
+ if (first) return redirect(res, '/' + app.slug + '/' + first.slug + '/home');
356
+ return send(res, 404, notFoundPage(app.slug));
357
+ }
358
+ if (segs[1] === 'static' || segs[1] === 'reports') {
359
+ const file = safeJoin(path.join(app.mockupDir, segs[1]), segs.slice(2));
360
+ if (serveFile(res, file)) return;
361
+ return send(res, 404, notFoundPage());
362
+ }
363
+ if (!SAFE_SEG.test(segs[1])) return send(res, 400, notFoundPage());
364
+ if (segs.length === 2) return redirect(res, '/' + app.slug + '/' + segs[1] + '/home');
365
+ if (segs.length === 3 && SAFE_SEG.test(segs[2])) {
366
+ const html = assemblePage(app, segs[1], segs[2]);
367
+ if (html === null) {
368
+ return send(res, 500, '<h1>Missing partials. Re-run the mockup generator.</h1>');
369
+ }
370
+ return send(res, 200, html);
371
+ }
372
+ return send(res, 404, notFoundPage());
373
+ }
374
+
375
+ function routeShadcn(app, segs, res) {
376
+ if (segs.length === 1 && serveFile(res, path.join(app.mockupDir, 'MOCKUP.html'))) return;
377
+ if (segs[1] === 'reports') {
378
+ const file = safeJoin(path.join(app.mockupDir, 'public', 'reports'), segs.slice(2));
379
+ if (serveFile(res, file)) return;
380
+ return send(res, 404, notFoundPage());
381
+ }
382
+ const distDir = path.join(app.mockupDir, 'dist');
383
+ if (segs.length > 1) {
384
+ const file = safeJoin(distDir, segs.slice(1));
385
+ if (serveFile(res, file)) return;
386
+ }
387
+ // SPA fallback — React Router (basename /{app_slug}) handles the path client-side
388
+ if (serveFile(res, path.join(distDir, 'index.html'))) return;
389
+ return send(res, 404, notFoundPage(app.slug));
390
+ }
391
+
392
+ function route(req, res) {
393
+ const urlPath = decodeURIComponent((req.url || '/').split('?')[0]);
394
+ const segs = urlPath.split('/').filter(Boolean);
395
+
396
+ if (segs.length === 0) return send(res, 200, renderLanding(discoverApps()));
397
+ if (urlPath === '/favicon.ico') { res.writeHead(204); return res.end(); }
398
+
399
+ // HTMX fragment endpoint (tailwind): /api/content/:app/:role/:page
400
+ if (segs[0] === 'api' && segs[1] === 'content') {
401
+ if (segs.length !== 5) return send(res, 404, notFoundPage());
402
+ const app = findApp(segs[2]);
403
+ if (!app || app.status !== 'ready' || app.stack !== 'tailwind') {
404
+ return send(res, 404, notFoundPage(segs[2]));
405
+ }
406
+ if (!SAFE_SEG.test(segs[3]) || !SAFE_SEG.test(segs[4])) return send(res, 400, notFoundPage());
407
+ const content = readText(path.join(app.mockupDir, segs[3], 'content', segs[4] + '.html'));
408
+ if (content === null) {
409
+ return send(res, 404, '<div class="p-6 text-red-500">Content not found.</div>');
410
+ }
411
+ return send(res, 200, content);
412
+ }
413
+
414
+ if (!SAFE_SEG.test(segs[0])) return send(res, 400, notFoundPage());
415
+ const app = findApp(segs[0]);
416
+ if (!app) return send(res, 404, notFoundPage(segs[0]));
417
+ if (app.status !== 'ready') return send(res, 200, notReadyPage(app));
418
+
419
+ if (app.stack === 'shadcn') return routeShadcn(app, segs, res);
420
+ return routeTailwind(app, segs, res);
421
+ }
422
+
423
+ // ── Start ────────────────────────────────────────────────────────────────────
424
+
425
+ const PORT = resolvePort();
426
+ const server = http.createServer((req, res) => {
427
+ try {
428
+ route(req, res);
429
+ } catch (err) {
430
+ send(res, 500, '<h1>500 — Hub error</h1><pre>' + escapeHtml(err && err.stack || err) + '</pre>');
431
+ }
432
+ });
433
+
434
+ server.listen(PORT, () => {
435
+ console.log('');
436
+ console.log(' CO2 Mockup Hub v' + HUB_VERSION + ' running at:');
437
+ console.log(' -> http://localhost:' + PORT);
438
+ console.log('');
439
+ console.log(' Serving mockups for all applications under: ' + ROOT_DIR);
440
+ console.log(' Change port: PORT env var, or "port" in mockup.config.json');
441
+ console.log('');
442
+ });
443
+ ```
444
+
445
+ ## package.json Template
446
+
447
+ ```json
448
+ {
449
+ "name": "co2-mockup-hub",
450
+ "private": true,
451
+ "version": "1.0.0",
452
+ "description": "CO2 Mockup Hub — single server for all application mockups",
453
+ "main": "server.js",
454
+ "scripts": {
455
+ "start": "node server.js"
456
+ }
457
+ }
458
+ ```
459
+
460
+ ## mockup.config.json Template
461
+
462
+ Create only if missing — never overwrite (it stores the user's port choice).
463
+
464
+ ```json
465
+ {
466
+ "port": 3000
467
+ }
468
+ ```
469
+
470
+ Port precedence at startup: `PORT` env var (e.g. `PORT=4000 npm start`) →
471
+ `mockup.config.json` `port` → `3000`.
472
+
473
+ ## .gitignore Template
474
+
475
+ Everything npm- or machine-generated inside `<root>/mockup/` stays inside `<root>/mockup/`
476
+ and is gitignored there. The hub has zero dependencies, so `node_modules/` normally never
477
+ exists — the rule is a safety net in case a user installs packages. `mockup.config.json`
478
+ is per-machine (each machine keeps its own port choice; the skills recreate it if missing),
479
+ so it is gitignored too.
480
+
481
+ ```
482
+ node_modules/
483
+ package-lock.json
484
+ mockup.config.json
485
+ .DS_Store
486
+ *.log
487
+ ```
488
+
489
+ ## Quick Start (for output summaries)
490
+
491
+ ```
492
+ Mockup Hub
493
+ ==========
494
+ 1. cd <root>/mockup
495
+ 2. npm start (zero dependencies — no npm install required)
496
+ 3. Open http://localhost:3000 in your browser
497
+ (change port: PORT=4000 npm start, or edit mockup.config.json)
498
+ ```