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,363 @@
1
+ import { request as httpsRequest } from 'node:https';
2
+ import { addRoute } from '../router.js';
3
+ import { vaultSet, vaultKeys } from '../lib/vault.js';
4
+ import { getSessionPassword } from './credentials.js';
5
+ import { parseJsonBody } from '../lib/body-parser.js';
6
+ import { sendJson } from '../lib/http-helpers.js';
7
+ function requirePassword(res) {
8
+ const password = getSessionPassword();
9
+ if (!password) {
10
+ sendJson(res, 401, { error: 'Vault is locked.' });
11
+ return null;
12
+ }
13
+ return password;
14
+ }
15
+ export const PROVIDERS = [
16
+ {
17
+ id: 'aws',
18
+ name: 'Amazon Web Services',
19
+ fields: [
20
+ { key: 'aws-access-key-id', label: 'Access Key ID', placeholder: 'AKIA...', secret: false },
21
+ { key: 'aws-secret-access-key', label: 'Secret Access Key', placeholder: 'wJalr...', secret: true },
22
+ { key: 'aws-region', label: 'Default Region', placeholder: 'us-east-1', secret: false },
23
+ ],
24
+ deployTargets: ['vps', 'static'],
25
+ description: 'EC2 instances, S3, RDS, ElastiCache, Route53, SES',
26
+ credentialUrl: 'https://console.aws.amazon.com/iam/home#/security_credentials',
27
+ help: '<ol><li>Sign in to the <a href="https://console.aws.amazon.com" target="_blank" rel="noopener">AWS Console</a></li><li>Go to <strong>IAM &rarr; Security Credentials</strong></li><li>Under "Access keys", click <strong>Create access key</strong></li><li>Copy the <strong>Access Key ID</strong> and <strong>Secret Access Key</strong></li><li>For region, use the region closest to your users (e.g. <code>us-east-1</code>)</li></ol><p>For production, create a dedicated IAM user with limited permissions instead of using root credentials.</p>',
28
+ },
29
+ {
30
+ id: 'vercel',
31
+ name: 'Vercel',
32
+ fields: [
33
+ { key: 'vercel-token', label: 'API Token', placeholder: 'Bearer token', secret: true },
34
+ ],
35
+ deployTargets: ['vercel'],
36
+ description: 'Serverless deployment for Next.js and static sites',
37
+ credentialUrl: 'https://vercel.com/account/tokens',
38
+ help: '<ol><li>Sign in to <a href="https://vercel.com" target="_blank" rel="noopener">vercel.com</a></li><li>Go to <strong>Settings &rarr; Tokens</strong></li><li>Click <strong>Create Token</strong></li><li>Give it a name (e.g. "VoidForge") and set scope to your team or personal account</li><li>Copy the token — it won\'t be shown again</li></ol>',
39
+ },
40
+ {
41
+ id: 'railway',
42
+ name: 'Railway',
43
+ fields: [
44
+ { key: 'railway-token', label: 'API Token', placeholder: 'Token', secret: true },
45
+ ],
46
+ deployTargets: ['railway'],
47
+ description: 'Managed deployment with Postgres, Redis, and auto-scaling',
48
+ credentialUrl: 'https://railway.com/account/tokens',
49
+ help: '<ol><li>Sign in to <a href="https://railway.com" target="_blank" rel="noopener">railway.com</a></li><li>Go to <strong>Account Settings &rarr; Tokens</strong></li><li>Click <strong>Create Token</strong></li><li>Give it a name and copy the token</li></ol>',
50
+ },
51
+ {
52
+ id: 'cloudflare',
53
+ name: 'Cloudflare',
54
+ fields: [
55
+ { key: 'cloudflare-api-token', label: 'API Token', placeholder: 'Token', secret: true },
56
+ { key: 'cloudflare-account-id', label: 'Account ID', placeholder: 'e.g. 1a2b3c...', secret: false },
57
+ ],
58
+ deployTargets: ['cloudflare'],
59
+ description: 'Workers, Pages, D1, R2 — edge-first deployment',
60
+ credentialUrl: 'https://dash.cloudflare.com/profile/api-tokens',
61
+ help: '<ol><li>Sign in to the <a href="https://dash.cloudflare.com" target="_blank" rel="noopener">Cloudflare Dashboard</a></li><li>Go to <strong>My Profile &rarr; API Tokens</strong></li><li>Click <strong>Create Token</strong></li><li>Use a <strong>custom token</strong> with permissions: <strong>Zone:DNS:Edit</strong>, <strong>Account:Cloudflare Pages:Edit</strong>, and <strong>Account:Registrar:Edit</strong> (for DNS wiring, Pages deployment, and domain registration)</li><li>Your <strong>Account ID</strong> is on the right sidebar of any zone\'s overview page, or at <strong>dash.cloudflare.com</strong> in the URL</li><li>Copy the token — it won\'t be shown again</li></ol>',
62
+ },
63
+ {
64
+ id: 'github',
65
+ name: 'GitHub',
66
+ fields: [
67
+ { key: 'github-token', label: 'Personal Access Token', placeholder: 'ghp_...', secret: true },
68
+ { key: 'github-owner', label: 'Owner (optional)', placeholder: 'username or org — defaults to token owner', secret: false, optional: true },
69
+ ],
70
+ deployTargets: ['vercel', 'cloudflare', 'railway', 'vps', 'static'],
71
+ description: 'Push code to GitHub for auto-deploy. Required for Vercel, Cloudflare Pages, and Railway.',
72
+ credentialUrl: 'https://github.com/settings/tokens',
73
+ help: '<ol><li>Sign in to <a href="https://github.com" target="_blank" rel="noopener">GitHub</a></li><li>Go to <strong>Settings &rarr; Developer settings &rarr; Personal access tokens &rarr; Fine-grained tokens</strong></li><li>Click <strong>Generate new token</strong></li><li>Set repository access to <strong>All repositories</strong> (or select specific repos)</li><li>Under permissions, enable: <strong>Contents: Read and write</strong> and <strong>Administration: Read and write</strong></li><li>Copy the token — it won\'t be shown again</li></ol><p>Classic tokens also work — select the <code>repo</code> scope.</p>',
74
+ },
75
+ ];
76
+ // --- Validation functions ---
77
+ function httpsGet(hostname, path, headers) {
78
+ return new Promise((resolve, reject) => {
79
+ const req = httpsRequest({ hostname, path, method: 'GET', headers, timeout: 10000 }, (res) => {
80
+ let body = '';
81
+ res.on('data', (chunk) => { body += chunk.toString(); });
82
+ res.on('end', () => resolve({ status: res.statusCode ?? 0, body }));
83
+ });
84
+ req.on('error', reject);
85
+ req.on('timeout', () => { req.destroy(); reject(new Error('Timed out')); });
86
+ req.end();
87
+ });
88
+ }
89
+ async function validateAws(accessKeyId, secretKey, region) {
90
+ // Format validation first
91
+ if (!accessKeyId.startsWith('AKIA') || accessKeyId.length < 16) {
92
+ return { valid: false, error: 'Access Key ID should start with AKIA and be at least 16 characters' };
93
+ }
94
+ if (secretKey.length < 20) {
95
+ return { valid: false, error: 'Secret Access Key seems too short' };
96
+ }
97
+ const validRegions = ['us-east-1', 'us-east-2', 'us-west-1', 'us-west-2', 'eu-west-1', 'eu-west-2', 'eu-central-1', 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'ap-south-1', 'sa-east-1', 'ca-central-1'];
98
+ if (region && !validRegions.includes(region)) {
99
+ return { valid: false, error: `Unknown region "${region}". Common regions: us-east-1, us-west-2, eu-west-1` };
100
+ }
101
+ // v17.0: Real STS validation — call GetCallerIdentity (zero-permission call that validates credentials)
102
+ try {
103
+ const { STSClient, GetCallerIdentityCommand } = await import('@aws-sdk/client-sts');
104
+ const sts = new STSClient({
105
+ region: region || 'us-east-1',
106
+ credentials: { accessKeyId, secretAccessKey: secretKey },
107
+ });
108
+ const identity = await sts.send(new GetCallerIdentityCommand({}));
109
+ return { valid: true, identity: identity.Arn ?? identity.Account ?? 'Verified' };
110
+ }
111
+ catch (err) {
112
+ const message = err instanceof Error ? err.message : 'AWS credential validation failed';
113
+ if (message.includes('not authorized') || message.includes('InvalidClientTokenId') || message.includes('SignatureDoesNotMatch')) {
114
+ return { valid: false, error: `Invalid AWS credentials: ${message}` };
115
+ }
116
+ // Network errors — credentials might be valid but can't reach AWS
117
+ return { valid: false, error: `Could not reach AWS to validate: ${message}` };
118
+ }
119
+ }
120
+ async function validateVercel(token) {
121
+ try {
122
+ const { status, body } = await httpsGet('api.vercel.com', '/v2/user', {
123
+ 'Authorization': `Bearer ${token}`,
124
+ });
125
+ if (status === 200) {
126
+ const data = JSON.parse(body);
127
+ return { valid: true, identity: data.user?.username };
128
+ }
129
+ if (status === 401 || status === 403) {
130
+ return { valid: false, error: 'Invalid or expired token' };
131
+ }
132
+ return { valid: false, error: `Vercel API returned ${status}` };
133
+ }
134
+ catch (err) {
135
+ return { valid: false, error: `Connection failed: ${err.message}` };
136
+ }
137
+ }
138
+ async function validateRailway(token) {
139
+ try {
140
+ // Railway uses GraphQL — try multiple queries since schema evolves
141
+ // Team tokens may not have access to `me`, so fall back to listing projects
142
+ const queries = [
143
+ { query: '{ me { name email } }', extract: (d) => { const me = d.me; return me?.name || me?.email; } },
144
+ { query: '{ projects { edges { node { name } } } }', extract: () => 'authenticated' },
145
+ ];
146
+ for (const q of queries) {
147
+ const postData = JSON.stringify({ query: q.query });
148
+ const result = await new Promise((resolve, reject) => {
149
+ const req = httpsRequest({
150
+ hostname: 'backboard.railway.com',
151
+ path: '/graphql/v2',
152
+ method: 'POST',
153
+ headers: {
154
+ 'Authorization': `Bearer ${token}`,
155
+ 'Content-Type': 'application/json',
156
+ 'Content-Length': Buffer.byteLength(postData),
157
+ },
158
+ timeout: 10000,
159
+ }, (res) => {
160
+ let body = '';
161
+ res.on('data', (chunk) => { body += chunk.toString(); });
162
+ res.on('end', () => resolve({ status: res.statusCode ?? 0, body }));
163
+ });
164
+ req.on('error', reject);
165
+ req.on('timeout', () => { req.destroy(); reject(new Error('Timed out')); });
166
+ req.write(postData);
167
+ req.end();
168
+ });
169
+ if (result.status === 401) {
170
+ return { valid: false, error: 'Invalid or expired token' };
171
+ }
172
+ if (result.status === 200) {
173
+ const parsed = JSON.parse(result.body);
174
+ // If the query succeeded with data (no errors, or errors but still got data)
175
+ if (parsed.data && !parsed.errors) {
176
+ const identity = q.extract(parsed.data);
177
+ if (identity)
178
+ return { valid: true, identity };
179
+ }
180
+ // If this query errored, try the next one
181
+ continue;
182
+ }
183
+ return { valid: false, error: `Railway API returned ${result.status}` };
184
+ }
185
+ // If we got here, all queries returned 200 but none yielded identity — token is likely valid
186
+ return { valid: true, identity: 'connected' };
187
+ }
188
+ catch (err) {
189
+ return { valid: false, error: `Connection failed: ${err.message}` };
190
+ }
191
+ }
192
+ async function validateGithub(token) {
193
+ try {
194
+ const { status, body } = await httpsGet('api.github.com', '/user', {
195
+ 'Authorization': `Bearer ${token}`,
196
+ 'User-Agent': 'VoidForge',
197
+ 'X-GitHub-Api-Version': '2022-11-28',
198
+ });
199
+ if (status === 200) {
200
+ const data = JSON.parse(body);
201
+ return { valid: true, identity: data.login };
202
+ }
203
+ if (status === 401) {
204
+ return { valid: false, error: 'Invalid or expired token' };
205
+ }
206
+ if (status === 403) {
207
+ return { valid: false, error: 'Token lacks required permissions. Ensure "repo" scope (classic) or "Contents: Read and write" (fine-grained).' };
208
+ }
209
+ return { valid: false, error: `GitHub API returned ${status}` };
210
+ }
211
+ catch (err) {
212
+ return { valid: false, error: `Connection failed: ${err.message}` };
213
+ }
214
+ }
215
+ async function validateCloudflare(token) {
216
+ try {
217
+ const { status, body } = await httpsGet('api.cloudflare.com', '/client/v4/user/tokens/verify', {
218
+ 'Authorization': `Bearer ${token}`,
219
+ });
220
+ if (status === 200) {
221
+ const data = JSON.parse(body);
222
+ if (data.success && data.result?.status === 'active') {
223
+ return { valid: true, identity: 'active token' };
224
+ }
225
+ return { valid: false, error: 'Token is not active' };
226
+ }
227
+ if (status === 401) {
228
+ return { valid: false, error: 'Invalid or expired token' };
229
+ }
230
+ return { valid: false, error: `Cloudflare API returned ${status}` };
231
+ }
232
+ catch (err) {
233
+ return { valid: false, error: `Connection failed: ${err.message}` };
234
+ }
235
+ }
236
+ // --- API Routes ---
237
+ // GET /api/cloud/providers — list all providers with their fields
238
+ addRoute('GET', '/api/cloud/providers', async (_req, res) => {
239
+ sendJson(res, 200, { providers: PROVIDERS });
240
+ });
241
+ // GET /api/cloud/status — which providers have stored credentials
242
+ addRoute('GET', '/api/cloud/status', async (_req, res) => {
243
+ const password = requirePassword(res);
244
+ if (!password)
245
+ return;
246
+ const keys = await vaultKeys(password);
247
+ const status = {};
248
+ for (const provider of PROVIDERS) {
249
+ // IG-R2: Unified check — a provider is configured if all non-optional fields are stored
250
+ status[provider.id] = provider.fields.every((f) => f.optional || keys.includes(f.key));
251
+ }
252
+ sendJson(res, 200, { status });
253
+ });
254
+ // POST /api/cloud/validate — validate and store credentials for a provider
255
+ addRoute('POST', '/api/cloud/validate', async (req, res) => {
256
+ const password = requirePassword(res);
257
+ if (!password)
258
+ return;
259
+ const body = await parseJsonBody(req);
260
+ if (!body.provider || !body.credentials || typeof body.credentials !== 'object') {
261
+ sendJson(res, 400, { error: 'provider and credentials are required' });
262
+ return;
263
+ }
264
+ // IG-R2: Validate all credential values are strings (prevent non-string vault storage)
265
+ if (Object.values(body.credentials).some((v) => typeof v !== 'string')) {
266
+ sendJson(res, 400, { error: 'All credential values must be strings' });
267
+ return;
268
+ }
269
+ const provider = PROVIDERS.find((p) => p.id === body.provider);
270
+ if (!provider) {
271
+ sendJson(res, 400, { error: `Unknown provider: ${body.provider}` });
272
+ return;
273
+ }
274
+ const creds = body.credentials;
275
+ // Validate based on provider
276
+ let result;
277
+ switch (body.provider) {
278
+ case 'aws':
279
+ result = await validateAws(creds['aws-access-key-id'] ?? '', creds['aws-secret-access-key'] ?? '', creds['aws-region'] ?? 'us-east-1');
280
+ break;
281
+ case 'vercel':
282
+ result = await validateVercel(creds['vercel-token'] ?? '');
283
+ break;
284
+ case 'railway':
285
+ result = await validateRailway(creds['railway-token'] ?? '');
286
+ break;
287
+ case 'github':
288
+ result = await validateGithub(creds['github-token'] ?? '');
289
+ break;
290
+ case 'cloudflare': {
291
+ result = await validateCloudflare(creds['cloudflare-api-token'] ?? '');
292
+ // After token validation succeeds, validate account ID format if provided
293
+ if (result.valid) {
294
+ const accountId = creds['cloudflare-account-id'];
295
+ if (accountId && !/^[a-f0-9]{32}$/i.test(accountId)) {
296
+ result = { valid: false, error: 'Account ID should be a 32-character hex string (found on your Cloudflare dashboard)' };
297
+ }
298
+ }
299
+ break;
300
+ }
301
+ default:
302
+ result = { valid: false, error: 'No validator for this provider' };
303
+ }
304
+ if (!result.valid) {
305
+ sendJson(res, 400, { error: result.error ?? 'Validation failed' });
306
+ return;
307
+ }
308
+ // Store all fields in the vault
309
+ for (const field of provider.fields) {
310
+ const value = creds[field.key];
311
+ if (value) {
312
+ await vaultSet(password, field.key, value);
313
+ }
314
+ }
315
+ sendJson(res, 200, { stored: true, identity: result.identity });
316
+ });
317
+ // DELETE /api/cloud/provider — remove a provider's credentials
318
+ addRoute('POST', '/api/cloud/remove', async (req, res) => {
319
+ const password = requirePassword(res);
320
+ if (!password)
321
+ return;
322
+ const body = await parseJsonBody(req);
323
+ if (!body.provider) {
324
+ sendJson(res, 400, { error: 'provider is required' });
325
+ return;
326
+ }
327
+ const provider = PROVIDERS.find((p) => p.id === body.provider);
328
+ if (!provider) {
329
+ sendJson(res, 400, { error: `Unknown provider: ${body.provider}` });
330
+ return;
331
+ }
332
+ // Import vaultDelete
333
+ const { vaultDelete } = await import('../lib/vault.js');
334
+ for (const field of provider.fields) {
335
+ await vaultDelete(password, field.key);
336
+ }
337
+ sendJson(res, 200, { removed: true });
338
+ });
339
+ // GET /api/cloud/deploy-targets — available deploy targets based on stored credentials
340
+ addRoute('GET', '/api/cloud/deploy-targets', async (_req, res) => {
341
+ const password = requirePassword(res);
342
+ if (!password)
343
+ return;
344
+ const keys = await vaultKeys(password);
345
+ // Docker and static-local are always available (no credentials needed)
346
+ const targets = [
347
+ { id: 'vps', name: 'VPS (AWS EC2)', available: false, provider: 'aws', description: 'Full server with SSH access. Best for: full-stack apps, background workers, databases on the same box.' },
348
+ { id: 'vercel', name: 'Vercel', available: false, provider: 'vercel', description: 'Serverless + edge. Best for: Next.js, static sites, JAMstack.' },
349
+ { id: 'railway', name: 'Railway', available: false, provider: 'railway', description: 'Managed containers with add-ons. Best for: full-stack apps that need Postgres/Redis without managing servers.' },
350
+ { id: 'cloudflare', name: 'Cloudflare Workers/Pages', available: false, provider: 'cloudflare', description: 'Edge-first serverless. Best for: APIs, static sites, globally distributed apps.' },
351
+ { id: 'static', name: 'Static (S3 + CloudFront)', available: false, provider: 'aws', description: 'Static hosting with CDN. Best for: marketing sites, docs, SPAs with separate API.' },
352
+ { id: 'docker', name: 'Docker (local)', available: true, provider: null, description: 'Dockerfile + docker-compose.yml. No cloud credentials needed. Best for: local dev, self-hosted.' },
353
+ ];
354
+ for (const target of targets) {
355
+ if (target.provider) {
356
+ const provider = PROVIDERS.find((p) => p.id === target.provider);
357
+ if (provider) {
358
+ target.available = provider.fields.every((f) => f.optional || keys.includes(f.key));
359
+ }
360
+ }
361
+ }
362
+ sendJson(res, 200, { targets });
363
+ });
@@ -0,0 +1 @@
1
+ export declare function getSessionPassword(): string | null;
@@ -0,0 +1,258 @@
1
+ import { request as httpsRequest } from 'node:https';
2
+ import { addRoute } from '../router.js';
3
+ import { vaultSet, vaultExists, vaultUnlock, vaultKeys, vaultPath, vaultLock } from '../lib/vault.js';
4
+ import { parseJsonBody } from '../lib/body-parser.js';
5
+ import { clearModelCache } from '../lib/anthropic.js';
6
+ import { sendJson } from '../lib/http-helpers.js';
7
+ import { getClientIp } from '../lib/tower-auth.js';
8
+ /** Session password — held in memory only, never written to disk */
9
+ let sessionPassword = null;
10
+ export function getSessionPassword() {
11
+ if (sessionPassword)
12
+ touchVaultAccess(); // SEC-R2-004: Reset auto-lock on every access
13
+ return sessionPassword;
14
+ }
15
+ // ── Vault unlock rate limiting (SEC-R2-003) ──────────
16
+ // Separate from login rate limits to prevent cross-endpoint exhaustion (Deathstroke R4)
17
+ const VAULT_RATE_LIMIT_MAX = 5;
18
+ const VAULT_RATE_WINDOW_MS = 60_000;
19
+ const VAULT_LOCKOUT_THRESHOLD = 10;
20
+ const VAULT_LOCKOUT_DURATION_MS = 30 * 60_000;
21
+ const vaultRateLimits = new Map();
22
+ function checkVaultRateLimit(ip) {
23
+ const now = Date.now();
24
+ const entry = vaultRateLimits.get(ip);
25
+ if (!entry) {
26
+ vaultRateLimits.set(ip, { attempts: 1, firstAttempt: now, consecutiveFailures: 0, lockedUntil: 0 });
27
+ return { allowed: true, retryAfterMs: 0 };
28
+ }
29
+ if (entry.lockedUntil > now) {
30
+ return { allowed: false, retryAfterMs: entry.lockedUntil - now };
31
+ }
32
+ if (now - entry.firstAttempt > VAULT_RATE_WINDOW_MS) {
33
+ entry.attempts = 1;
34
+ entry.firstAttempt = now;
35
+ return { allowed: true, retryAfterMs: 0 };
36
+ }
37
+ entry.attempts++;
38
+ if (entry.attempts > VAULT_RATE_LIMIT_MAX) {
39
+ return { allowed: false, retryAfterMs: VAULT_RATE_WINDOW_MS - (now - entry.firstAttempt) };
40
+ }
41
+ return { allowed: true, retryAfterMs: 0 };
42
+ }
43
+ function recordVaultFailure(ip) {
44
+ const entry = vaultRateLimits.get(ip);
45
+ if (!entry)
46
+ return;
47
+ entry.consecutiveFailures++;
48
+ if (entry.consecutiveFailures >= VAULT_LOCKOUT_THRESHOLD) {
49
+ entry.lockedUntil = Date.now() + VAULT_LOCKOUT_DURATION_MS;
50
+ entry.consecutiveFailures = 0;
51
+ }
52
+ }
53
+ function clearVaultFailures(ip) {
54
+ const entry = vaultRateLimits.get(ip);
55
+ if (entry)
56
+ entry.consecutiveFailures = 0;
57
+ }
58
+ // ── Vault auto-lock (SEC-R2-004) ─────────────────────
59
+ const VAULT_AUTO_LOCK_MS = 15 * 60_000; // 15 minutes idle
60
+ let lastVaultAccess = 0;
61
+ let autoLockTimer = null;
62
+ function touchVaultAccess() {
63
+ lastVaultAccess = Date.now();
64
+ if (!autoLockTimer) {
65
+ autoLockTimer = setInterval(() => {
66
+ if (sessionPassword && Date.now() - lastVaultAccess > VAULT_AUTO_LOCK_MS) {
67
+ sessionPassword = null;
68
+ vaultLock();
69
+ if (autoLockTimer) {
70
+ clearInterval(autoLockTimer);
71
+ autoLockTimer = null;
72
+ }
73
+ }
74
+ }, 60_000); // Check every minute
75
+ autoLockTimer.unref(); // Don't prevent graceful shutdown
76
+ }
77
+ }
78
+ /** Validate an Anthropic API key by making a lightweight test call */
79
+ function validateAnthropicKey(apiKey) {
80
+ return new Promise((resolve) => {
81
+ const postData = JSON.stringify({
82
+ model: 'claude-haiku-4-5-20251001',
83
+ max_tokens: 1,
84
+ messages: [{ role: 'user', content: 'hi' }],
85
+ });
86
+ const req = httpsRequest({
87
+ hostname: 'api.anthropic.com',
88
+ path: '/v1/messages',
89
+ method: 'POST',
90
+ headers: {
91
+ 'Content-Type': 'application/json',
92
+ 'x-api-key': apiKey,
93
+ 'anthropic-version': '2023-06-01',
94
+ 'Content-Length': Buffer.byteLength(postData),
95
+ },
96
+ timeout: 15000,
97
+ }, (res) => {
98
+ let body = '';
99
+ res.on('data', (chunk) => { body += chunk.toString(); });
100
+ res.on('end', () => {
101
+ if (res.statusCode === 200) {
102
+ resolve({ valid: true });
103
+ }
104
+ else if (res.statusCode === 401) {
105
+ resolve({ valid: false, error: 'Invalid API key' });
106
+ }
107
+ else {
108
+ try {
109
+ const parsed = JSON.parse(body);
110
+ resolve({ valid: false, error: parsed.error?.message ?? `HTTP ${res.statusCode}` });
111
+ }
112
+ catch {
113
+ resolve({ valid: false, error: `HTTP ${res.statusCode}` });
114
+ }
115
+ }
116
+ });
117
+ });
118
+ req.on('error', (err) => {
119
+ resolve({ valid: false, error: `Connection failed: ${err.message}` });
120
+ });
121
+ req.on('timeout', () => {
122
+ req.destroy();
123
+ resolve({ valid: false, error: 'Request timed out' });
124
+ });
125
+ req.write(postData);
126
+ req.end();
127
+ });
128
+ }
129
+ // GET /api/credentials/status — check vault state and stored keys
130
+ addRoute('GET', '/api/credentials/status', async (_req, res) => {
131
+ const exists = vaultExists();
132
+ const unlocked = sessionPassword !== null;
133
+ let hasAnthropic = false;
134
+ if (unlocked && sessionPassword) {
135
+ try {
136
+ const keys = await vaultKeys(sessionPassword);
137
+ hasAnthropic = keys.includes('anthropic-api-key');
138
+ }
139
+ catch {
140
+ // Vault read failed — treat as locked
141
+ }
142
+ }
143
+ sendJson(res, 200, {
144
+ vaultExists: exists,
145
+ unlocked,
146
+ anthropic: hasAnthropic,
147
+ vaultPath: vaultPath(),
148
+ });
149
+ });
150
+ // POST /api/credentials/unlock — unlock vault with password (or create new vault)
151
+ addRoute('POST', '/api/credentials/unlock', async (req, res) => {
152
+ // SEC-R2-003: Rate limit vault unlock to prevent brute-force.
153
+ // Use getClientIp() for consistent IP extraction behind proxy (v17.0 fix).
154
+ const ip = getClientIp(req);
155
+ const rateCheck = checkVaultRateLimit(ip);
156
+ if (!rateCheck.allowed) {
157
+ sendJson(res, 429, { error: 'Too many unlock attempts. Try again later.', retryAfterMs: rateCheck.retryAfterMs });
158
+ return;
159
+ }
160
+ const body = await parseJsonBody(req);
161
+ if (!body.password || typeof body.password !== 'string') {
162
+ sendJson(res, 400, { error: 'password is required' });
163
+ return;
164
+ }
165
+ // QA-R3-017: Raise vault password minimum from 4 to 8 chars
166
+ if (body.password.length < 8) {
167
+ sendJson(res, 400, { error: 'Password must be at least 8 characters' });
168
+ return;
169
+ }
170
+ if (body.password.length > 256) {
171
+ sendJson(res, 400, { error: 'Password must be 256 characters or fewer' });
172
+ return;
173
+ }
174
+ // Capture vault existence BEFORE unlock — vaultUnlock creates the vault if new,
175
+ // so checking after would always return true (CODE-R1-001 / QA-009).
176
+ const wasNew = !vaultExists();
177
+ const valid = await vaultUnlock(body.password);
178
+ if (!valid) {
179
+ recordVaultFailure(ip);
180
+ sendJson(res, 401, { error: 'Wrong password' });
181
+ return;
182
+ }
183
+ clearVaultFailures(ip);
184
+ sessionPassword = body.password;
185
+ touchVaultAccess(); // SEC-R2-004: Start auto-lock timer
186
+ // Check what's already stored
187
+ let hasAnthropic = false;
188
+ try {
189
+ const keys = await vaultKeys(sessionPassword);
190
+ hasAnthropic = keys.includes('anthropic-api-key');
191
+ }
192
+ catch {
193
+ // Fresh vault
194
+ }
195
+ sendJson(res, 200, {
196
+ unlocked: true,
197
+ isNew: wasNew,
198
+ anthropic: hasAnthropic,
199
+ });
200
+ });
201
+ // POST /api/credentials/anthropic — validate and store Anthropic API key
202
+ addRoute('POST', '/api/credentials/anthropic', async (req, res) => {
203
+ if (!sessionPassword) {
204
+ sendJson(res, 401, { error: 'Vault is locked. Unlock with your password first.' });
205
+ return;
206
+ }
207
+ const body = await parseJsonBody(req);
208
+ if (!body.apiKey || typeof body.apiKey !== 'string') {
209
+ sendJson(res, 400, { error: 'apiKey is required' });
210
+ return;
211
+ }
212
+ const apiKey = body.apiKey.trim();
213
+ if (!apiKey.startsWith('sk-ant-')) {
214
+ sendJson(res, 400, { error: 'Invalid key format. Anthropic API keys start with sk-ant-' });
215
+ return;
216
+ }
217
+ const result = await validateAnthropicKey(apiKey);
218
+ if (!result.valid) {
219
+ sendJson(res, 400, { error: result.error ?? 'Invalid API key' });
220
+ return;
221
+ }
222
+ await vaultSet(sessionPassword, 'anthropic-api-key', apiKey);
223
+ clearModelCache();
224
+ sendJson(res, 200, { stored: true });
225
+ });
226
+ // POST /api/credentials/env-batch — store multiple project-specific credentials at once
227
+ addRoute('POST', '/api/credentials/env-batch', async (req, res) => {
228
+ if (!sessionPassword) {
229
+ sendJson(res, 401, { error: 'Vault is locked.' });
230
+ return;
231
+ }
232
+ const body = await parseJsonBody(req);
233
+ if (!body.credentials || typeof body.credentials !== 'object') {
234
+ sendJson(res, 400, { error: 'credentials object is required' });
235
+ return;
236
+ }
237
+ const entries = Object.entries(body.credentials).filter(([key, val]) => typeof key === 'string' && typeof val === 'string' && val.trim().length > 0);
238
+ if (entries.length === 0) {
239
+ sendJson(res, 400, { error: 'No non-empty credentials provided' });
240
+ return;
241
+ }
242
+ // Max 100 entries to prevent abuse
243
+ if (entries.length > 100) {
244
+ sendJson(res, 400, { error: 'Too many credentials (max 100)' });
245
+ return;
246
+ }
247
+ // Validate key format: only allow env-var-style keys
248
+ for (const [key] of entries) {
249
+ if (!/^[A-Z][A-Z0-9_]{1,100}$/.test(key)) {
250
+ sendJson(res, 400, { error: `Invalid credential key format: ${key}` });
251
+ return;
252
+ }
253
+ }
254
+ for (const [key, value] of entries) {
255
+ await vaultSet(sessionPassword, `env:${key}`, value.trim());
256
+ }
257
+ sendJson(res, 200, { stored: true, count: entries.length });
258
+ });
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Danger Room API — Real-time data feeds for the mission control dashboard.
3
+ *
4
+ * Shared parsers and WebSocket infra live in wizard/lib/dashboard-*.ts.
5
+ * This file registers Danger Room routes and any Danger Room-specific endpoints
6
+ * (heartbeat, freeze, Deep Current).
7
+ */
8
+ import type { IncomingMessage } from 'node:http';
9
+ import type { Duplex } from 'node:stream';
10
+ /** Broadcast a message to all connected Danger Room clients. */
11
+ export declare const broadcastDangerRoom: (data: {
12
+ type: string;
13
+ [key: string]: unknown;
14
+ }) => void;
15
+ /** Close all Danger Room WebSocket connections, activity watcher, and shut down. */
16
+ export declare function closeDangerRoom(): void;
17
+ /** Handle WebSocket upgrade for /ws/danger-room. */
18
+ export declare const handleDangerRoomUpgrade: (req: IncomingMessage, socket: Duplex, head: Buffer) => void;