thevoidforge 21.0.0

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 (328) hide show
  1. package/dist/scripts/vault-read.d.ts +11 -0
  2. package/dist/scripts/vault-read.js +89 -0
  3. package/dist/scripts/voidforge.d.ts +20 -0
  4. package/dist/scripts/voidforge.js +404 -0
  5. package/dist/tsconfig.tsbuildinfo +1 -0
  6. package/dist/wizard/api/auth.d.ts +5 -0
  7. package/dist/wizard/api/auth.js +133 -0
  8. package/dist/wizard/api/blueprint.d.ts +45 -0
  9. package/dist/wizard/api/blueprint.js +184 -0
  10. package/dist/wizard/api/cloud-providers.d.ts +16 -0
  11. package/dist/wizard/api/cloud-providers.js +363 -0
  12. package/dist/wizard/api/credentials.d.ts +1 -0
  13. package/dist/wizard/api/credentials.js +258 -0
  14. package/dist/wizard/api/danger-room.d.ts +18 -0
  15. package/dist/wizard/api/danger-room.js +401 -0
  16. package/dist/wizard/api/deploy.d.ts +4 -0
  17. package/dist/wizard/api/deploy.js +164 -0
  18. package/dist/wizard/api/prd.d.ts +1 -0
  19. package/dist/wizard/api/prd.js +363 -0
  20. package/dist/wizard/api/project.d.ts +1 -0
  21. package/dist/wizard/api/project.js +239 -0
  22. package/dist/wizard/api/projects.d.ts +6 -0
  23. package/dist/wizard/api/projects.js +648 -0
  24. package/dist/wizard/api/provision.d.ts +4 -0
  25. package/dist/wizard/api/provision.js +535 -0
  26. package/dist/wizard/api/terminal.d.ts +25 -0
  27. package/dist/wizard/api/terminal.js +241 -0
  28. package/dist/wizard/api/users.d.ts +6 -0
  29. package/dist/wizard/api/users.js +244 -0
  30. package/dist/wizard/api/war-room.d.ts +14 -0
  31. package/dist/wizard/api/war-room.js +45 -0
  32. package/dist/wizard/lib/ad-platform-core.d.ts +6 -0
  33. package/dist/wizard/lib/ad-platform-core.js +1 -0
  34. package/dist/wizard/lib/adapters/index.d.ts +52 -0
  35. package/dist/wizard/lib/adapters/index.js +38 -0
  36. package/dist/wizard/lib/adapters/sandbox-bank.d.ts +17 -0
  37. package/dist/wizard/lib/adapters/sandbox-bank.js +77 -0
  38. package/dist/wizard/lib/adapters/sandbox.d.ts +39 -0
  39. package/dist/wizard/lib/adapters/sandbox.js +174 -0
  40. package/dist/wizard/lib/adapters/stripe.d.ts +19 -0
  41. package/dist/wizard/lib/adapters/stripe.js +143 -0
  42. package/dist/wizard/lib/adapters/types.d.ts +9 -0
  43. package/dist/wizard/lib/adapters/types.js +10 -0
  44. package/dist/wizard/lib/agent-memory.d.ts +36 -0
  45. package/dist/wizard/lib/agent-memory.js +114 -0
  46. package/dist/wizard/lib/anomaly-detection.d.ts +59 -0
  47. package/dist/wizard/lib/anomaly-detection.js +122 -0
  48. package/dist/wizard/lib/anthropic.d.ts +21 -0
  49. package/dist/wizard/lib/anthropic.js +105 -0
  50. package/dist/wizard/lib/asset-scanner.d.ts +23 -0
  51. package/dist/wizard/lib/asset-scanner.js +107 -0
  52. package/dist/wizard/lib/audit-log.d.ts +23 -0
  53. package/dist/wizard/lib/audit-log.js +70 -0
  54. package/dist/wizard/lib/autonomy-controller.d.ts +76 -0
  55. package/dist/wizard/lib/autonomy-controller.js +183 -0
  56. package/dist/wizard/lib/body-parser.d.ts +2 -0
  57. package/dist/wizard/lib/body-parser.js +36 -0
  58. package/dist/wizard/lib/build-analytics.d.ts +39 -0
  59. package/dist/wizard/lib/build-analytics.js +91 -0
  60. package/dist/wizard/lib/build-step.d.ts +21 -0
  61. package/dist/wizard/lib/build-step.js +104 -0
  62. package/dist/wizard/lib/campaign-proposer.d.ts +39 -0
  63. package/dist/wizard/lib/campaign-proposer.js +180 -0
  64. package/dist/wizard/lib/campaign-state-machine.d.ts +63 -0
  65. package/dist/wizard/lib/campaign-state-machine.js +114 -0
  66. package/dist/wizard/lib/ci-generator.d.ts +14 -0
  67. package/dist/wizard/lib/ci-generator.js +187 -0
  68. package/dist/wizard/lib/claude-merge.d.ts +38 -0
  69. package/dist/wizard/lib/claude-merge.js +115 -0
  70. package/dist/wizard/lib/codegen/erd-gen.d.ts +16 -0
  71. package/dist/wizard/lib/codegen/erd-gen.js +98 -0
  72. package/dist/wizard/lib/codegen/integrations.d.ts +18 -0
  73. package/dist/wizard/lib/codegen/integrations.js +189 -0
  74. package/dist/wizard/lib/codegen/openapi-gen.d.ts +15 -0
  75. package/dist/wizard/lib/codegen/openapi-gen.js +79 -0
  76. package/dist/wizard/lib/codegen/prisma-types.d.ts +15 -0
  77. package/dist/wizard/lib/codegen/prisma-types.js +44 -0
  78. package/dist/wizard/lib/codegen/seed-gen.d.ts +16 -0
  79. package/dist/wizard/lib/codegen/seed-gen.js +128 -0
  80. package/dist/wizard/lib/compliance.d.ts +51 -0
  81. package/dist/wizard/lib/compliance.js +112 -0
  82. package/dist/wizard/lib/correlation-engine.d.ts +59 -0
  83. package/dist/wizard/lib/correlation-engine.js +151 -0
  84. package/dist/wizard/lib/cost-estimator.d.ts +22 -0
  85. package/dist/wizard/lib/cost-estimator.js +72 -0
  86. package/dist/wizard/lib/cost-tracker.d.ts +27 -0
  87. package/dist/wizard/lib/cost-tracker.js +37 -0
  88. package/dist/wizard/lib/daemon-aggregator.d.ts +71 -0
  89. package/dist/wizard/lib/daemon-aggregator.js +204 -0
  90. package/dist/wizard/lib/daemon-core.d.ts +6 -0
  91. package/dist/wizard/lib/daemon-core.js +5 -0
  92. package/dist/wizard/lib/dashboard-data.d.ts +132 -0
  93. package/dist/wizard/lib/dashboard-data.js +336 -0
  94. package/dist/wizard/lib/dashboard-ws.d.ts +25 -0
  95. package/dist/wizard/lib/dashboard-ws.js +91 -0
  96. package/dist/wizard/lib/deep-current.d.ts +77 -0
  97. package/dist/wizard/lib/deep-current.js +234 -0
  98. package/dist/wizard/lib/deploy-coordinator.d.ts +40 -0
  99. package/dist/wizard/lib/deploy-coordinator.js +86 -0
  100. package/dist/wizard/lib/deploy-log.d.ts +28 -0
  101. package/dist/wizard/lib/deploy-log.js +52 -0
  102. package/dist/wizard/lib/desktop-notify.d.ts +27 -0
  103. package/dist/wizard/lib/desktop-notify.js +98 -0
  104. package/dist/wizard/lib/dns/cloudflare-dns.d.ts +35 -0
  105. package/dist/wizard/lib/dns/cloudflare-dns.js +216 -0
  106. package/dist/wizard/lib/dns/cloudflare-registrar.d.ts +31 -0
  107. package/dist/wizard/lib/dns/cloudflare-registrar.js +148 -0
  108. package/dist/wizard/lib/dns/types.d.ts +22 -0
  109. package/dist/wizard/lib/dns/types.js +4 -0
  110. package/dist/wizard/lib/document-discovery.d.ts +33 -0
  111. package/dist/wizard/lib/document-discovery.js +145 -0
  112. package/dist/wizard/lib/env-validator.d.ts +14 -0
  113. package/dist/wizard/lib/env-validator.js +205 -0
  114. package/dist/wizard/lib/env-writer.d.ts +13 -0
  115. package/dist/wizard/lib/env-writer.js +26 -0
  116. package/dist/wizard/lib/exec.d.ts +30 -0
  117. package/dist/wizard/lib/exec.js +52 -0
  118. package/dist/wizard/lib/experiment.d.ts +70 -0
  119. package/dist/wizard/lib/experiment.js +169 -0
  120. package/dist/wizard/lib/extensions.d.ts +20 -0
  121. package/dist/wizard/lib/extensions.js +183 -0
  122. package/dist/wizard/lib/financial/adapter-factory.d.ts +47 -0
  123. package/dist/wizard/lib/financial/adapter-factory.js +225 -0
  124. package/dist/wizard/lib/financial/billing/base.d.ts +6 -0
  125. package/dist/wizard/lib/financial/billing/base.js +1 -0
  126. package/dist/wizard/lib/financial/billing/google-billing.d.ts +56 -0
  127. package/dist/wizard/lib/financial/billing/google-billing.js +298 -0
  128. package/dist/wizard/lib/financial/billing/meta-billing.d.ts +54 -0
  129. package/dist/wizard/lib/financial/billing/meta-billing.js +243 -0
  130. package/dist/wizard/lib/financial/billing/tiktok-billing.d.ts +54 -0
  131. package/dist/wizard/lib/financial/billing/tiktok-billing.js +260 -0
  132. package/dist/wizard/lib/financial/campaign/base.d.ts +13 -0
  133. package/dist/wizard/lib/financial/campaign/base.js +1 -0
  134. package/dist/wizard/lib/financial/campaign/google-campaign.d.ts +42 -0
  135. package/dist/wizard/lib/financial/campaign/google-campaign.js +388 -0
  136. package/dist/wizard/lib/financial/campaign/meta-campaign.d.ts +41 -0
  137. package/dist/wizard/lib/financial/campaign/meta-campaign.js +311 -0
  138. package/dist/wizard/lib/financial/campaign/sandbox-campaign.d.ts +45 -0
  139. package/dist/wizard/lib/financial/campaign/sandbox-campaign.js +261 -0
  140. package/dist/wizard/lib/financial/campaign/tiktok-campaign.d.ts +40 -0
  141. package/dist/wizard/lib/financial/campaign/tiktok-campaign.js +350 -0
  142. package/dist/wizard/lib/financial/funding-auto.d.ts +44 -0
  143. package/dist/wizard/lib/financial/funding-auto.js +52 -0
  144. package/dist/wizard/lib/financial/funding-policy.d.ts +60 -0
  145. package/dist/wizard/lib/financial/funding-policy.js +179 -0
  146. package/dist/wizard/lib/financial/platform-planner.d.ts +47 -0
  147. package/dist/wizard/lib/financial/platform-planner.js +134 -0
  148. package/dist/wizard/lib/financial/reconciliation-engine.d.ts +78 -0
  149. package/dist/wizard/lib/financial/reconciliation-engine.js +193 -0
  150. package/dist/wizard/lib/financial/registry.d.ts +22 -0
  151. package/dist/wizard/lib/financial/registry.js +26 -0
  152. package/dist/wizard/lib/financial/reporting.d.ts +96 -0
  153. package/dist/wizard/lib/financial/reporting.js +198 -0
  154. package/dist/wizard/lib/financial/stablecoin/base.d.ts +6 -0
  155. package/dist/wizard/lib/financial/stablecoin/base.js +1 -0
  156. package/dist/wizard/lib/financial/stablecoin/circle.d.ts +54 -0
  157. package/dist/wizard/lib/financial/stablecoin/circle.js +367 -0
  158. package/dist/wizard/lib/financial/stablecoin/mercury.d.ts +24 -0
  159. package/dist/wizard/lib/financial/stablecoin/mercury.js +171 -0
  160. package/dist/wizard/lib/financial/stablecoin/sandbox-stablecoin.d.ts +47 -0
  161. package/dist/wizard/lib/financial/stablecoin/sandbox-stablecoin.js +202 -0
  162. package/dist/wizard/lib/financial/treasury-planner.d.ts +52 -0
  163. package/dist/wizard/lib/financial/treasury-planner.js +128 -0
  164. package/dist/wizard/lib/financial-core.d.ts +6 -0
  165. package/dist/wizard/lib/financial-core.js +5 -0
  166. package/dist/wizard/lib/financial-vault.d.ts +34 -0
  167. package/dist/wizard/lib/financial-vault.js +199 -0
  168. package/dist/wizard/lib/frontmatter.d.ts +30 -0
  169. package/dist/wizard/lib/frontmatter.js +96 -0
  170. package/dist/wizard/lib/gap-analysis.d.ts +37 -0
  171. package/dist/wizard/lib/gap-analysis.js +218 -0
  172. package/dist/wizard/lib/github.d.ts +22 -0
  173. package/dist/wizard/lib/github.js +261 -0
  174. package/dist/wizard/lib/headless-deploy.d.ts +14 -0
  175. package/dist/wizard/lib/headless-deploy.js +452 -0
  176. package/dist/wizard/lib/health-monitor.d.ts +15 -0
  177. package/dist/wizard/lib/health-monitor.js +91 -0
  178. package/dist/wizard/lib/health-poller.d.ts +9 -0
  179. package/dist/wizard/lib/health-poller.js +123 -0
  180. package/dist/wizard/lib/heartbeat.d.ts +15 -0
  181. package/dist/wizard/lib/heartbeat.js +827 -0
  182. package/dist/wizard/lib/http-helpers.d.ts +9 -0
  183. package/dist/wizard/lib/http-helpers.js +24 -0
  184. package/dist/wizard/lib/image-gen.d.ts +56 -0
  185. package/dist/wizard/lib/image-gen.js +159 -0
  186. package/dist/wizard/lib/instance-sizing.d.ts +26 -0
  187. package/dist/wizard/lib/instance-sizing.js +51 -0
  188. package/dist/wizard/lib/kongo/analytics.d.ts +29 -0
  189. package/dist/wizard/lib/kongo/analytics.js +179 -0
  190. package/dist/wizard/lib/kongo/campaigns.d.ts +52 -0
  191. package/dist/wizard/lib/kongo/campaigns.js +91 -0
  192. package/dist/wizard/lib/kongo/client.d.ts +58 -0
  193. package/dist/wizard/lib/kongo/client.js +221 -0
  194. package/dist/wizard/lib/kongo/jobs.d.ts +57 -0
  195. package/dist/wizard/lib/kongo/jobs.js +122 -0
  196. package/dist/wizard/lib/kongo/pages.d.ts +60 -0
  197. package/dist/wizard/lib/kongo/pages.js +150 -0
  198. package/dist/wizard/lib/kongo/provisioner.d.ts +64 -0
  199. package/dist/wizard/lib/kongo/provisioner.js +116 -0
  200. package/dist/wizard/lib/kongo/seed.d.ts +49 -0
  201. package/dist/wizard/lib/kongo/seed.js +237 -0
  202. package/dist/wizard/lib/kongo/types.d.ts +323 -0
  203. package/dist/wizard/lib/kongo/types.js +11 -0
  204. package/dist/wizard/lib/kongo/variants.d.ts +57 -0
  205. package/dist/wizard/lib/kongo/variants.js +88 -0
  206. package/dist/wizard/lib/kongo/webhooks.d.ts +41 -0
  207. package/dist/wizard/lib/kongo/webhooks.js +112 -0
  208. package/dist/wizard/lib/marker.d.ts +28 -0
  209. package/dist/wizard/lib/marker.js +79 -0
  210. package/dist/wizard/lib/migrator.d.ts +35 -0
  211. package/dist/wizard/lib/migrator.js +190 -0
  212. package/dist/wizard/lib/natural-language-deploy.d.ts +30 -0
  213. package/dist/wizard/lib/natural-language-deploy.js +186 -0
  214. package/dist/wizard/lib/network.d.ts +22 -0
  215. package/dist/wizard/lib/network.js +72 -0
  216. package/dist/wizard/lib/oauth-core.d.ts +6 -0
  217. package/dist/wizard/lib/oauth-core.js +5 -0
  218. package/dist/wizard/lib/open-browser.d.ts +1 -0
  219. package/dist/wizard/lib/open-browser.js +26 -0
  220. package/dist/wizard/lib/patterns/ad-billing-adapter.d.ts +209 -0
  221. package/dist/wizard/lib/patterns/ad-billing-adapter.js +269 -0
  222. package/dist/wizard/lib/patterns/ad-platform-adapter.d.ts +200 -0
  223. package/dist/wizard/lib/patterns/ad-platform-adapter.js +212 -0
  224. package/dist/wizard/lib/patterns/daemon-process.d.ts +88 -0
  225. package/dist/wizard/lib/patterns/daemon-process.js +271 -0
  226. package/dist/wizard/lib/patterns/financial-transaction.d.ts +161 -0
  227. package/dist/wizard/lib/patterns/financial-transaction.js +132 -0
  228. package/dist/wizard/lib/patterns/funding-plan.d.ts +136 -0
  229. package/dist/wizard/lib/patterns/funding-plan.js +200 -0
  230. package/dist/wizard/lib/patterns/oauth-token-lifecycle.d.ts +94 -0
  231. package/dist/wizard/lib/patterns/oauth-token-lifecycle.js +139 -0
  232. package/dist/wizard/lib/patterns/outbound-rate-limiter.d.ts +67 -0
  233. package/dist/wizard/lib/patterns/outbound-rate-limiter.js +216 -0
  234. package/dist/wizard/lib/patterns/revenue-source-adapter.d.ts +96 -0
  235. package/dist/wizard/lib/patterns/revenue-source-adapter.js +182 -0
  236. package/dist/wizard/lib/patterns/stablecoin-adapter.d.ts +218 -0
  237. package/dist/wizard/lib/patterns/stablecoin-adapter.js +264 -0
  238. package/dist/wizard/lib/prd-validator.d.ts +39 -0
  239. package/dist/wizard/lib/prd-validator.js +137 -0
  240. package/dist/wizard/lib/project-init.d.ts +24 -0
  241. package/dist/wizard/lib/project-init.js +193 -0
  242. package/dist/wizard/lib/project-registry.d.ts +86 -0
  243. package/dist/wizard/lib/project-registry.js +359 -0
  244. package/dist/wizard/lib/provision-manifest.d.ts +44 -0
  245. package/dist/wizard/lib/provision-manifest.js +164 -0
  246. package/dist/wizard/lib/provisioner-registry.d.ts +15 -0
  247. package/dist/wizard/lib/provisioner-registry.js +34 -0
  248. package/dist/wizard/lib/provisioners/aws-vps.d.ts +6 -0
  249. package/dist/wizard/lib/provisioners/aws-vps.js +643 -0
  250. package/dist/wizard/lib/provisioners/cloudflare.d.ts +6 -0
  251. package/dist/wizard/lib/provisioners/cloudflare.js +300 -0
  252. package/dist/wizard/lib/provisioners/docker.d.ts +6 -0
  253. package/dist/wizard/lib/provisioners/docker.js +75 -0
  254. package/dist/wizard/lib/provisioners/http-client.d.ts +20 -0
  255. package/dist/wizard/lib/provisioners/http-client.js +79 -0
  256. package/dist/wizard/lib/provisioners/railway.d.ts +6 -0
  257. package/dist/wizard/lib/provisioners/railway.js +413 -0
  258. package/dist/wizard/lib/provisioners/scripts/caddyfile.d.ts +10 -0
  259. package/dist/wizard/lib/provisioners/scripts/caddyfile.js +54 -0
  260. package/dist/wizard/lib/provisioners/scripts/deploy-vps.d.ts +10 -0
  261. package/dist/wizard/lib/provisioners/scripts/deploy-vps.js +112 -0
  262. package/dist/wizard/lib/provisioners/scripts/docker-compose.d.ts +11 -0
  263. package/dist/wizard/lib/provisioners/scripts/docker-compose.js +91 -0
  264. package/dist/wizard/lib/provisioners/scripts/dockerfile.d.ts +5 -0
  265. package/dist/wizard/lib/provisioners/scripts/dockerfile.js +185 -0
  266. package/dist/wizard/lib/provisioners/scripts/ecosystem-config.d.ts +10 -0
  267. package/dist/wizard/lib/provisioners/scripts/ecosystem-config.js +36 -0
  268. package/dist/wizard/lib/provisioners/scripts/provision-vps.d.ts +14 -0
  269. package/dist/wizard/lib/provisioners/scripts/provision-vps.js +202 -0
  270. package/dist/wizard/lib/provisioners/scripts/rollback-vps.d.ts +10 -0
  271. package/dist/wizard/lib/provisioners/scripts/rollback-vps.js +67 -0
  272. package/dist/wizard/lib/provisioners/self-deploy.d.ts +41 -0
  273. package/dist/wizard/lib/provisioners/self-deploy.js +185 -0
  274. package/dist/wizard/lib/provisioners/static-s3.d.ts +6 -0
  275. package/dist/wizard/lib/provisioners/static-s3.js +235 -0
  276. package/dist/wizard/lib/provisioners/types.d.ts +40 -0
  277. package/dist/wizard/lib/provisioners/types.js +4 -0
  278. package/dist/wizard/lib/provisioners/vercel.d.ts +6 -0
  279. package/dist/wizard/lib/provisioners/vercel.js +287 -0
  280. package/dist/wizard/lib/pty-manager.d.ts +42 -0
  281. package/dist/wizard/lib/pty-manager.js +231 -0
  282. package/dist/wizard/lib/rate-limiter-core.d.ts +5 -0
  283. package/dist/wizard/lib/rate-limiter-core.js +5 -0
  284. package/dist/wizard/lib/reconciliation.d.ts +43 -0
  285. package/dist/wizard/lib/reconciliation.js +173 -0
  286. package/dist/wizard/lib/revenue-types.d.ts +5 -0
  287. package/dist/wizard/lib/revenue-types.js +1 -0
  288. package/dist/wizard/lib/route-optimizer.d.ts +28 -0
  289. package/dist/wizard/lib/route-optimizer.js +93 -0
  290. package/dist/wizard/lib/s3-deploy.d.ts +19 -0
  291. package/dist/wizard/lib/s3-deploy.js +156 -0
  292. package/dist/wizard/lib/safety-tiers.d.ts +76 -0
  293. package/dist/wizard/lib/safety-tiers.js +134 -0
  294. package/dist/wizard/lib/sentry-generator.d.ts +15 -0
  295. package/dist/wizard/lib/sentry-generator.js +116 -0
  296. package/dist/wizard/lib/server-config.d.ts +13 -0
  297. package/dist/wizard/lib/server-config.js +23 -0
  298. package/dist/wizard/lib/service-install.d.ts +18 -0
  299. package/dist/wizard/lib/service-install.js +182 -0
  300. package/dist/wizard/lib/site-scanner.d.ts +80 -0
  301. package/dist/wizard/lib/site-scanner.js +262 -0
  302. package/dist/wizard/lib/ssh-deploy.d.ts +25 -0
  303. package/dist/wizard/lib/ssh-deploy.js +225 -0
  304. package/dist/wizard/lib/templates.d.ts +24 -0
  305. package/dist/wizard/lib/templates.js +219 -0
  306. package/dist/wizard/lib/totp.d.ts +35 -0
  307. package/dist/wizard/lib/totp.js +276 -0
  308. package/dist/wizard/lib/tower-auth.d.ts +43 -0
  309. package/dist/wizard/lib/tower-auth.js +352 -0
  310. package/dist/wizard/lib/tower-rate-limit.d.ts +14 -0
  311. package/dist/wizard/lib/tower-rate-limit.js +61 -0
  312. package/dist/wizard/lib/tower-session.d.ts +28 -0
  313. package/dist/wizard/lib/tower-session.js +119 -0
  314. package/dist/wizard/lib/treasury-backup.d.ts +23 -0
  315. package/dist/wizard/lib/treasury-backup.js +126 -0
  316. package/dist/wizard/lib/treasury-heartbeat.d.ts +82 -0
  317. package/dist/wizard/lib/treasury-heartbeat.js +1104 -0
  318. package/dist/wizard/lib/updater.d.ts +29 -0
  319. package/dist/wizard/lib/updater.js +190 -0
  320. package/dist/wizard/lib/user-manager.d.ts +39 -0
  321. package/dist/wizard/lib/user-manager.js +182 -0
  322. package/dist/wizard/lib/vault.d.ts +26 -0
  323. package/dist/wizard/lib/vault.js +161 -0
  324. package/dist/wizard/router.d.ts +5 -0
  325. package/dist/wizard/router.js +15 -0
  326. package/dist/wizard/server.d.ts +18 -0
  327. package/dist/wizard/server.js +436 -0
  328. package/package.json +59 -0
@@ -0,0 +1,648 @@
1
+ /**
2
+ * Projects API — Multi-project CRUD for The Lobby.
3
+ * Endpoints: list, get, import, delete, access management.
4
+ * All queries filtered by per-project access control (v7.0).
5
+ */
6
+ import { access as fsAccess, readFile, realpath } from 'node:fs/promises';
7
+ import { join, resolve } from 'node:path';
8
+ import { addRoute } from '../router.js';
9
+ import { parseJsonBody } from '../lib/body-parser.js';
10
+ import { parseFrontmatter } from '../lib/frontmatter.js';
11
+ import { addProject, getProject, removeProject, findByDirectory, getProjectsForUser, grantAccess, revokeAccess, getProjectAccess, checkProjectAccess, linkProjects, unlinkProjects, getLinkedGroup, } from '../lib/project-registry.js';
12
+ import { getDeployPlan } from '../lib/deploy-coordinator.js';
13
+ import { getAggregateCosts } from '../lib/cost-tracker.js';
14
+ import { addLesson, getLessons, getLessonCount } from '../lib/agent-memory.js';
15
+ import { audit } from '../lib/audit-log.js';
16
+ import { validateSession, parseSessionCookie, getClientIp, isRemoteMode } from '../lib/tower-auth.js';
17
+ import { isValidRole, hasRole } from '../lib/user-manager.js';
18
+ import { sendJson } from '../lib/http-helpers.js';
19
+ /** Extract session from request. Returns null if not authenticated (local mode returns synthetic admin). */
20
+ function getSession(req) {
21
+ if (!isRemoteMode()) {
22
+ return { username: 'local', role: 'admin' };
23
+ }
24
+ const token = parseSessionCookie(req.headers.cookie);
25
+ const ip = getClientIp(req);
26
+ if (!token)
27
+ return null;
28
+ return validateSession(token, ip);
29
+ }
30
+ // GET /api/projects — list projects visible to the current user
31
+ addRoute('GET', '/api/projects', async (req, res) => {
32
+ const session = getSession(req);
33
+ if (!session) {
34
+ sendJson(res, 401, { success: false, error: 'Authentication required' });
35
+ return;
36
+ }
37
+ const projects = await getProjectsForUser(session.username, session.role);
38
+ // Annotate each project with the user's effective role for UI rendering
39
+ const annotated = projects.map((p) => {
40
+ let userRole = 'viewer';
41
+ if (session.role === 'admin' || p.owner === session.username) {
42
+ userRole = 'owner';
43
+ }
44
+ else {
45
+ const entry = p.access.find((a) => a.username === session.username);
46
+ if (entry)
47
+ userRole = entry.role;
48
+ }
49
+ return { ...p, userRole };
50
+ });
51
+ sendJson(res, 200, { success: true, data: annotated });
52
+ });
53
+ // GET /api/projects/get — get single project by id (filtered by access)
54
+ addRoute('GET', '/api/projects/get', async (req, res) => {
55
+ const session = getSession(req);
56
+ if (!session) {
57
+ sendJson(res, 401, { success: false, error: 'Authentication required' });
58
+ return;
59
+ }
60
+ const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
61
+ const id = url.searchParams.get('id');
62
+ if (!id) {
63
+ sendJson(res, 400, { success: false, error: 'id query parameter is required' });
64
+ return;
65
+ }
66
+ // Access check — returns null if project doesn't exist OR user has no access
67
+ const effectiveRole = await checkProjectAccess(id, session.username, session.role);
68
+ if (!effectiveRole) {
69
+ sendJson(res, 404, { success: false, error: 'Project not found' });
70
+ return;
71
+ }
72
+ const project = await getProject(id);
73
+ if (!project) {
74
+ sendJson(res, 404, { success: false, error: 'Project not found' });
75
+ return;
76
+ }
77
+ sendJson(res, 200, { success: true, data: { ...project, userRole: effectiveRole } });
78
+ });
79
+ /** Scan a project directory for metadata. Reuses patterns from wizard/api/deploy.ts. */
80
+ async function scanProjectMetadata(dir) {
81
+ let name = 'Unknown';
82
+ try {
83
+ const claudeMd = await readFile(join(dir, 'CLAUDE.md'), 'utf-8');
84
+ const nameMatch = claudeMd.match(/\*\*Name:\*\*\s*(.+)/);
85
+ if (nameMatch) {
86
+ const extracted = nameMatch[1].trim();
87
+ if (!extracted.startsWith('['))
88
+ name = extracted;
89
+ }
90
+ }
91
+ catch { /* use default */ }
92
+ let deployTarget = '';
93
+ let deployUrl = '';
94
+ let hostname = '';
95
+ let sshHost = '';
96
+ try {
97
+ const envContent = await readFile(join(dir, '.env'), 'utf-8');
98
+ const deployMatch = envContent.match(/DEPLOY_TARGET=(.+)/);
99
+ if (deployMatch) {
100
+ deployTarget = deployMatch[1].trim().replace(/^["']|["']$/g, '').split('#')[0].trim();
101
+ }
102
+ const hostnameMatch = envContent.match(/HOSTNAME=(.+)/);
103
+ if (hostnameMatch) {
104
+ hostname = hostnameMatch[1].trim().replace(/^["']|["']$/g, '').split('#')[0].trim();
105
+ if (hostname)
106
+ deployUrl = `https://${hostname}`;
107
+ }
108
+ if (deployTarget === 'vps') {
109
+ const sshMatch = envContent.match(/SSH_HOST=(.+)/);
110
+ if (sshMatch) {
111
+ sshHost = sshMatch[1].trim().replace(/^["']|["']$/g, '').split('#')[0].trim();
112
+ }
113
+ }
114
+ }
115
+ catch { /* no .env */ }
116
+ let framework = '';
117
+ let database = 'none';
118
+ try {
119
+ const prd = await readFile(join(dir, 'docs', 'PRD.md'), 'utf-8');
120
+ const { frontmatter } = parseFrontmatter(prd);
121
+ if (frontmatter.framework)
122
+ framework = frontmatter.framework;
123
+ if (frontmatter.database)
124
+ database = frontmatter.database;
125
+ if (frontmatter.deploy && !deployTarget)
126
+ deployTarget = frontmatter.deploy;
127
+ if (frontmatter.hostname && !hostname) {
128
+ hostname = frontmatter.hostname;
129
+ if (!deployUrl && hostname)
130
+ deployUrl = `https://${hostname}`;
131
+ }
132
+ }
133
+ catch { /* no PRD or no frontmatter */ }
134
+ if (!framework) {
135
+ try {
136
+ const pkg = await readFile(join(dir, 'package.json'), 'utf-8');
137
+ const pkgData = JSON.parse(pkg);
138
+ const deps = pkgData.dependencies ?? {};
139
+ if (deps['next'])
140
+ framework = 'next.js';
141
+ else if (deps['express'])
142
+ framework = 'express';
143
+ }
144
+ catch { /* not a Node project */ }
145
+ if (!framework) {
146
+ try {
147
+ const reqs = await readFile(join(dir, 'requirements.txt'), 'utf-8');
148
+ if (reqs.toLowerCase().includes('django'))
149
+ framework = 'django';
150
+ else
151
+ framework = 'python';
152
+ }
153
+ catch { /* not Python */ }
154
+ }
155
+ if (!framework) {
156
+ try {
157
+ await fsAccess(join(dir, 'Gemfile'));
158
+ framework = 'rails';
159
+ }
160
+ catch { /* not Ruby */ }
161
+ }
162
+ }
163
+ let lastBuildPhase = 0;
164
+ try {
165
+ const buildState = await readFile(join(dir, 'logs', 'build-state.md'), 'utf-8');
166
+ const phaseMatch = buildState.match(/\*\*Current Phase:\*\*\s*(\d+)/);
167
+ if (phaseMatch) {
168
+ lastBuildPhase = parseInt(phaseMatch[1], 10);
169
+ }
170
+ }
171
+ catch { /* no build state */ }
172
+ let healthCheckUrl = '';
173
+ if (deployUrl) {
174
+ healthCheckUrl = `${deployUrl}/api/health`;
175
+ }
176
+ return {
177
+ name,
178
+ directory: dir,
179
+ deployTarget: deployTarget || 'unknown',
180
+ deployUrl,
181
+ sshHost,
182
+ framework: framework || 'unknown',
183
+ database,
184
+ createdAt: new Date().toISOString(),
185
+ lastBuildPhase,
186
+ lastDeployAt: '',
187
+ healthCheckUrl,
188
+ monthlyCost: 0,
189
+ owner: '',
190
+ access: [],
191
+ linkedProjects: [],
192
+ };
193
+ }
194
+ // POST /api/projects/import — import an existing project (sets owner to current user)
195
+ addRoute('POST', '/api/projects/import', async (req, res) => {
196
+ const session = getSession(req);
197
+ if (!session) {
198
+ sendJson(res, 401, { success: false, error: 'Authentication required' });
199
+ return;
200
+ }
201
+ const body = await parseJsonBody(req);
202
+ if (typeof body !== 'object' || body === null) {
203
+ sendJson(res, 400, { success: false, error: 'Request body must be a JSON object' });
204
+ return;
205
+ }
206
+ const { directory } = body;
207
+ if (typeof directory !== 'string' || directory.trim().length === 0) {
208
+ sendJson(res, 400, { success: false, error: 'directory must be a non-empty string' });
209
+ return;
210
+ }
211
+ if (directory.includes('..')) {
212
+ sendJson(res, 400, { success: false, error: 'directory must not contain ".." segments' });
213
+ return;
214
+ }
215
+ let dir = resolve(directory);
216
+ if (!dir.startsWith('/')) {
217
+ sendJson(res, 400, { success: false, error: 'directory must be an absolute path' });
218
+ return;
219
+ }
220
+ try {
221
+ await fsAccess(dir);
222
+ }
223
+ catch {
224
+ sendJson(res, 400, { success: false, error: 'Directory does not exist' });
225
+ return;
226
+ }
227
+ // CROSS-R4-005 + IG-R4: Resolve symlinks and use the real path for all subsequent operations.
228
+ // This handles macOS /tmp → /private/tmp while ensuring we operate on the true filesystem location.
229
+ try {
230
+ dir = await realpath(dir);
231
+ }
232
+ catch {
233
+ sendJson(res, 400, { success: false, error: 'Could not resolve directory path' });
234
+ return;
235
+ }
236
+ try {
237
+ await fsAccess(join(dir, 'CLAUDE.md'));
238
+ }
239
+ catch {
240
+ sendJson(res, 400, { success: false, error: 'Not a VoidForge project — no CLAUDE.md found' });
241
+ return;
242
+ }
243
+ const existing = await findByDirectory(dir);
244
+ if (existing) {
245
+ sendJson(res, 409, { success: false, error: 'Project already registered', data: existing });
246
+ return;
247
+ }
248
+ try {
249
+ const input = await scanProjectMetadata(dir);
250
+ input.owner = session.username; // Set owner to importing user
251
+ const project = await addProject(input);
252
+ const ip = getClientIp(req);
253
+ await audit('project_create', ip, session.username, { action: 'import', directory: dir, name: project.name });
254
+ sendJson(res, 201, { success: true, data: { ...project, userRole: 'owner' } });
255
+ }
256
+ catch (err) {
257
+ const message = err instanceof Error ? err.message : 'Import failed';
258
+ if (message.includes('already registered')) {
259
+ sendJson(res, 409, { success: false, error: 'Project already registered' });
260
+ }
261
+ else {
262
+ sendJson(res, 500, { success: false, error: 'Failed to import project' });
263
+ }
264
+ }
265
+ });
266
+ // POST /api/projects/delete — remove a project (owner or admin only)
267
+ addRoute('POST', '/api/projects/delete', async (req, res) => {
268
+ const session = getSession(req);
269
+ if (!session) {
270
+ sendJson(res, 401, { success: false, error: 'Authentication required' });
271
+ return;
272
+ }
273
+ const body = await parseJsonBody(req);
274
+ if (typeof body !== 'object' || body === null) {
275
+ sendJson(res, 400, { success: false, error: 'Request body must be a JSON object' });
276
+ return;
277
+ }
278
+ const { id } = body;
279
+ if (typeof id !== 'string' || id.trim().length === 0) {
280
+ sendJson(res, 400, { success: false, error: 'id must be a non-empty string' });
281
+ return;
282
+ }
283
+ // Check access — only owner or admin can delete
284
+ const effectiveRole = await checkProjectAccess(id, session.username, session.role);
285
+ if (effectiveRole !== 'admin') {
286
+ sendJson(res, 404, { success: false, error: 'Project not found' });
287
+ return;
288
+ }
289
+ const removed = await removeProject(id);
290
+ if (!removed) {
291
+ sendJson(res, 404, { success: false, error: 'Project not found' });
292
+ return;
293
+ }
294
+ const ip = getClientIp(req);
295
+ await audit('project_delete', ip, session.username, { projectId: id });
296
+ sendJson(res, 200, { success: true });
297
+ });
298
+ // ── Access management endpoints ─────────────────────
299
+ // GET /api/projects/access — get access list for a project (owner or admin)
300
+ addRoute('GET', '/api/projects/access', async (req, res) => {
301
+ const session = getSession(req);
302
+ if (!session) {
303
+ sendJson(res, 401, { success: false, error: 'Authentication required' });
304
+ return;
305
+ }
306
+ const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
307
+ const id = url.searchParams.get('id');
308
+ if (!id) {
309
+ sendJson(res, 400, { success: false, error: 'id query parameter is required' });
310
+ return;
311
+ }
312
+ // Only owner or admin can view access list
313
+ const effectiveRole = await checkProjectAccess(id, session.username, session.role);
314
+ if (effectiveRole !== 'admin') {
315
+ sendJson(res, 404, { success: false, error: 'Project not found' });
316
+ return;
317
+ }
318
+ const accessInfo = await getProjectAccess(id);
319
+ if (!accessInfo) {
320
+ sendJson(res, 404, { success: false, error: 'Project not found' });
321
+ return;
322
+ }
323
+ sendJson(res, 200, { success: true, data: accessInfo });
324
+ });
325
+ // POST /api/projects/access/grant — grant access (owner or admin only)
326
+ addRoute('POST', '/api/projects/access/grant', async (req, res) => {
327
+ const session = getSession(req);
328
+ if (!session) {
329
+ sendJson(res, 401, { success: false, error: 'Authentication required' });
330
+ return;
331
+ }
332
+ const body = await parseJsonBody(req);
333
+ if (typeof body !== 'object' || body === null) {
334
+ sendJson(res, 400, { success: false, error: 'Request body must be a JSON object' });
335
+ return;
336
+ }
337
+ const { projectId, username, role } = body;
338
+ if (typeof projectId !== 'string' || projectId.trim().length === 0) {
339
+ sendJson(res, 400, { success: false, error: 'projectId is required' });
340
+ return;
341
+ }
342
+ if (typeof username !== 'string' || username.trim().length === 0) {
343
+ sendJson(res, 400, { success: false, error: 'username is required' });
344
+ return;
345
+ }
346
+ if (typeof role !== 'string' || !isValidRole(role) || role === 'admin') {
347
+ sendJson(res, 400, { success: false, error: 'role must be one of: deployer, viewer' });
348
+ return;
349
+ }
350
+ // Only owner or admin can grant access
351
+ const effectiveRole = await checkProjectAccess(projectId, session.username, session.role);
352
+ if (effectiveRole !== 'admin') {
353
+ sendJson(res, 404, { success: false, error: 'Project not found' });
354
+ return;
355
+ }
356
+ const ip = getClientIp(req);
357
+ try {
358
+ await grantAccess(projectId, username.trim(), role);
359
+ await audit('access_grant', ip, session.username, {
360
+ projectId,
361
+ target: username.trim(),
362
+ grantedRole: role,
363
+ });
364
+ sendJson(res, 200, { success: true });
365
+ }
366
+ catch (err) {
367
+ const message = err instanceof Error ? err.message : 'Failed to grant access';
368
+ if (message === 'Project not found') {
369
+ sendJson(res, 404, { success: false, error: 'Project not found' });
370
+ }
371
+ else {
372
+ sendJson(res, 400, { success: false, error: 'Failed to grant access' });
373
+ }
374
+ }
375
+ });
376
+ // POST /api/projects/access/revoke — revoke access (owner or admin only)
377
+ addRoute('POST', '/api/projects/access/revoke', async (req, res) => {
378
+ const session = getSession(req);
379
+ if (!session) {
380
+ sendJson(res, 401, { success: false, error: 'Authentication required' });
381
+ return;
382
+ }
383
+ const body = await parseJsonBody(req);
384
+ if (typeof body !== 'object' || body === null) {
385
+ sendJson(res, 400, { success: false, error: 'Request body must be a JSON object' });
386
+ return;
387
+ }
388
+ const { projectId, username } = body;
389
+ if (typeof projectId !== 'string' || projectId.trim().length === 0) {
390
+ sendJson(res, 400, { success: false, error: 'projectId is required' });
391
+ return;
392
+ }
393
+ if (typeof username !== 'string' || username.trim().length === 0) {
394
+ sendJson(res, 400, { success: false, error: 'username is required' });
395
+ return;
396
+ }
397
+ // Only owner or admin can revoke access
398
+ const effectiveRole = await checkProjectAccess(projectId, session.username, session.role);
399
+ if (effectiveRole !== 'admin') {
400
+ sendJson(res, 404, { success: false, error: 'Project not found' });
401
+ return;
402
+ }
403
+ const ip = getClientIp(req);
404
+ try {
405
+ await revokeAccess(projectId, username.trim());
406
+ await audit('access_revoke', ip, session.username, {
407
+ projectId,
408
+ target: username.trim(),
409
+ });
410
+ sendJson(res, 200, { success: true });
411
+ }
412
+ catch (err) {
413
+ const message = err instanceof Error ? err.message : 'Failed to revoke access';
414
+ if (message === 'Project not found' || message === 'User has no access to revoke') {
415
+ sendJson(res, 400, { success: false, error: message });
416
+ }
417
+ else {
418
+ sendJson(res, 400, { success: false, error: 'Failed to revoke access' });
419
+ }
420
+ }
421
+ });
422
+ // ── Linked services endpoints ───────────────────────
423
+ // POST /api/projects/link — link two projects (owner/admin of both required)
424
+ addRoute('POST', '/api/projects/link', async (req, res) => {
425
+ const session = getSession(req);
426
+ if (!session) {
427
+ sendJson(res, 401, { success: false, error: 'Authentication required' });
428
+ return;
429
+ }
430
+ const body = await parseJsonBody(req);
431
+ if (typeof body !== 'object' || body === null) {
432
+ sendJson(res, 400, { success: false, error: 'Request body must be a JSON object' });
433
+ return;
434
+ }
435
+ const { projectIdA, projectIdB } = body;
436
+ if (typeof projectIdA !== 'string' || projectIdA.trim().length === 0) {
437
+ sendJson(res, 400, { success: false, error: 'projectIdA is required' });
438
+ return;
439
+ }
440
+ if (typeof projectIdB !== 'string' || projectIdB.trim().length === 0) {
441
+ sendJson(res, 400, { success: false, error: 'projectIdB is required' });
442
+ return;
443
+ }
444
+ // Must have admin access to BOTH projects
445
+ const roleA = await checkProjectAccess(projectIdA, session.username, session.role);
446
+ const roleB = await checkProjectAccess(projectIdB, session.username, session.role);
447
+ if (roleA !== 'admin' || roleB !== 'admin') {
448
+ sendJson(res, 404, { success: false, error: 'Project not found' });
449
+ return;
450
+ }
451
+ const ip = getClientIp(req);
452
+ try {
453
+ await linkProjects(projectIdA, projectIdB);
454
+ await audit('access_grant', ip, session.username, {
455
+ action: 'link',
456
+ projectIdA,
457
+ projectIdB,
458
+ });
459
+ sendJson(res, 200, { success: true });
460
+ }
461
+ catch (err) {
462
+ const message = err instanceof Error ? err.message : 'Failed to link';
463
+ if (message === 'Cannot link a project to itself' || message === 'Project not found') {
464
+ sendJson(res, 400, { success: false, error: message });
465
+ }
466
+ else {
467
+ sendJson(res, 400, { success: false, error: 'Failed to link projects' });
468
+ }
469
+ }
470
+ });
471
+ // POST /api/projects/unlink — unlink two projects (owner/admin of either)
472
+ addRoute('POST', '/api/projects/unlink', async (req, res) => {
473
+ const session = getSession(req);
474
+ if (!session) {
475
+ sendJson(res, 401, { success: false, error: 'Authentication required' });
476
+ return;
477
+ }
478
+ const body = await parseJsonBody(req);
479
+ if (typeof body !== 'object' || body === null) {
480
+ sendJson(res, 400, { success: false, error: 'Request body must be a JSON object' });
481
+ return;
482
+ }
483
+ const { projectIdA, projectIdB } = body;
484
+ if (typeof projectIdA !== 'string' || projectIdA.trim().length === 0) {
485
+ sendJson(res, 400, { success: false, error: 'projectIdA is required' });
486
+ return;
487
+ }
488
+ if (typeof projectIdB !== 'string' || projectIdB.trim().length === 0) {
489
+ sendJson(res, 400, { success: false, error: 'projectIdB is required' });
490
+ return;
491
+ }
492
+ // Must have admin access to EITHER project
493
+ const roleA = await checkProjectAccess(projectIdA, session.username, session.role);
494
+ const roleB = await checkProjectAccess(projectIdB, session.username, session.role);
495
+ if (roleA !== 'admin' && roleB !== 'admin') {
496
+ sendJson(res, 404, { success: false, error: 'Project not found' });
497
+ return;
498
+ }
499
+ const ip = getClientIp(req);
500
+ try {
501
+ await unlinkProjects(projectIdA, projectIdB);
502
+ await audit('access_revoke', ip, session.username, {
503
+ action: 'unlink',
504
+ projectIdA,
505
+ projectIdB,
506
+ });
507
+ sendJson(res, 200, { success: true });
508
+ }
509
+ catch (err) {
510
+ const message = err instanceof Error ? err.message : 'Failed to unlink';
511
+ if (message === 'Project not found') {
512
+ sendJson(res, 400, { success: false, error: message });
513
+ }
514
+ else {
515
+ sendJson(res, 400, { success: false, error: 'Failed to unlink projects' });
516
+ }
517
+ }
518
+ });
519
+ // GET /api/projects/linked — get linked projects for a project
520
+ addRoute('GET', '/api/projects/linked', async (req, res) => {
521
+ const session = getSession(req);
522
+ if (!session) {
523
+ sendJson(res, 401, { success: false, error: 'Authentication required' });
524
+ return;
525
+ }
526
+ const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
527
+ const id = url.searchParams.get('id');
528
+ if (!id) {
529
+ sendJson(res, 400, { success: false, error: 'id query parameter is required' });
530
+ return;
531
+ }
532
+ const effectiveRole = await checkProjectAccess(id, session.username, session.role);
533
+ if (!effectiveRole) {
534
+ sendJson(res, 404, { success: false, error: 'Project not found' });
535
+ return;
536
+ }
537
+ const group = await getLinkedGroup(id);
538
+ // Filter linked projects by user access — don't leak data about projects the user can't see
539
+ const accessChecks = await Promise.all(group.filter((p) => p.id !== id).map(async (p) => ({
540
+ project: p,
541
+ hasAccess: !!(await checkProjectAccess(p.id, session.username, session.role)),
542
+ })));
543
+ const linked = accessChecks.filter((c) => c.hasAccess).map((c) => ({
544
+ id: c.project.id,
545
+ name: c.project.name,
546
+ deployTarget: c.project.deployTarget,
547
+ healthStatus: c.project.healthStatus,
548
+ lastDeployAt: c.project.lastDeployAt,
549
+ }));
550
+ sendJson(res, 200, { success: true, data: linked });
551
+ });
552
+ // POST /api/projects/deploy-check — check which linked projects need redeployment
553
+ addRoute('POST', '/api/projects/deploy-check', async (req, res) => {
554
+ const session = getSession(req);
555
+ if (!session) {
556
+ sendJson(res, 401, { success: false, error: 'Authentication required' });
557
+ return;
558
+ }
559
+ const body = await parseJsonBody(req);
560
+ if (typeof body !== 'object' || body === null) {
561
+ sendJson(res, 400, { success: false, error: 'Request body must be a JSON object' });
562
+ return;
563
+ }
564
+ const { projectId } = body;
565
+ if (typeof projectId !== 'string' || projectId.trim().length === 0) {
566
+ sendJson(res, 400, { success: false, error: 'projectId is required' });
567
+ return;
568
+ }
569
+ const effectiveRole = await checkProjectAccess(projectId, session.username, session.role);
570
+ if (!effectiveRole) {
571
+ sendJson(res, 404, { success: false, error: 'Project not found' });
572
+ return;
573
+ }
574
+ const ip = getClientIp(req);
575
+ const plan = await getDeployPlan(projectId, session.username, ip);
576
+ if (!plan) {
577
+ sendJson(res, 404, { success: false, error: 'Project not found' });
578
+ return;
579
+ }
580
+ sendJson(res, 200, { success: true, data: plan });
581
+ });
582
+ // ── Cost + Lessons endpoints ────────────────────────
583
+ // GET /api/projects/costs — aggregate costs across accessible projects
584
+ addRoute('GET', '/api/projects/costs', async (req, res) => {
585
+ const session = getSession(req);
586
+ if (!session) {
587
+ sendJson(res, 401, { success: false, error: 'Authentication required' });
588
+ return;
589
+ }
590
+ const costs = await getAggregateCosts(session.username, session.role);
591
+ sendJson(res, 200, { success: true, data: costs });
592
+ });
593
+ // GET /api/projects/lessons — get lessons (optionally filtered by framework)
594
+ addRoute('GET', '/api/projects/lessons', async (req, res) => {
595
+ const session = getSession(req);
596
+ if (!session) {
597
+ sendJson(res, 401, { success: false, error: 'Authentication required' });
598
+ return;
599
+ }
600
+ const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
601
+ const framework = url.searchParams.get('framework') || undefined;
602
+ const category = url.searchParams.get('category') || undefined;
603
+ const lessons = await getLessons({ framework, category });
604
+ const count = await getLessonCount();
605
+ sendJson(res, 200, { success: true, data: { lessons, total: count } });
606
+ });
607
+ // POST /api/projects/lessons — add a lesson (deployer+)
608
+ addRoute('POST', '/api/projects/lessons', async (req, res) => {
609
+ const session = getSession(req);
610
+ if (!session) {
611
+ sendJson(res, 401, { success: false, error: 'Authentication required' });
612
+ return;
613
+ }
614
+ // Deployer minimum to write lessons
615
+ if (!hasRole(session, 'deployer')) {
616
+ sendJson(res, 404, { success: false, error: 'Not found' });
617
+ return;
618
+ }
619
+ const body = await parseJsonBody(req);
620
+ if (typeof body !== 'object' || body === null) {
621
+ sendJson(res, 400, { success: false, error: 'Request body must be a JSON object' });
622
+ return;
623
+ }
624
+ const { framework, category, lesson, action, project, agent } = body;
625
+ if (typeof framework !== 'string' || typeof category !== 'string' ||
626
+ typeof lesson !== 'string' || typeof action !== 'string' ||
627
+ typeof project !== 'string' || typeof agent !== 'string') {
628
+ sendJson(res, 400, { success: false, error: 'framework, category, lesson, action, project, and agent are required strings' });
629
+ return;
630
+ }
631
+ // Cap field lengths
632
+ if (lesson.length > 1000 || action.length > 500) {
633
+ sendJson(res, 400, { success: false, error: 'lesson max 1000 chars, action max 500 chars' });
634
+ return;
635
+ }
636
+ const input = {
637
+ framework: framework.slice(0, 50),
638
+ category: category.slice(0, 50),
639
+ lesson: lesson.slice(0, 1000),
640
+ action: action.slice(0, 500),
641
+ project: project.slice(0, 100),
642
+ agent: agent.slice(0, 50),
643
+ };
644
+ const created = await addLesson(input);
645
+ const ip = getClientIp(req);
646
+ await audit('project_create', ip, session.username, { action: 'add_lesson', framework: input.framework, category: input.category });
647
+ sendJson(res, 201, { success: true, data: created });
648
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Provisioning API routes — SSE-streamed infrastructure provisioning.
3
+ */
4
+ export {};