mcpmake 0.1.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 (344) hide show
  1. package/README.md +691 -0
  2. package/bin/mcpmake.mjs +2 -0
  3. package/dist/analyzer/auth-detector.d.ts +12 -0
  4. package/dist/analyzer/auth-detector.js +142 -0
  5. package/dist/analyzer/dom-parser.d.ts +10 -0
  6. package/dist/analyzer/dom-parser.js +259 -0
  7. package/dist/analyzer/goal-crawler.d.ts +25 -0
  8. package/dist/analyzer/goal-crawler.js +177 -0
  9. package/dist/analyzer/hybrid-detector.d.ts +28 -0
  10. package/dist/analyzer/hybrid-detector.js +96 -0
  11. package/dist/analyzer/index.d.ts +12 -0
  12. package/dist/analyzer/index.js +8 -0
  13. package/dist/analyzer/screenshot-capture.d.ts +29 -0
  14. package/dist/analyzer/screenshot-capture.js +42 -0
  15. package/dist/analyzer/selector-builder.d.ts +19 -0
  16. package/dist/analyzer/selector-builder.js +199 -0
  17. package/dist/analyzer/semantic-analyzer.d.ts +13 -0
  18. package/dist/analyzer/semantic-analyzer.js +145 -0
  19. package/dist/analyzer/site-crawler.d.ts +38 -0
  20. package/dist/analyzer/site-crawler.js +235 -0
  21. package/dist/cloud/billing/billing-engine.d.ts +44 -0
  22. package/dist/cloud/billing/billing-engine.js +81 -0
  23. package/dist/cloud/billing/credit-store.d.ts +64 -0
  24. package/dist/cloud/billing/credit-store.js +168 -0
  25. package/dist/cloud/billing/index.d.ts +4 -0
  26. package/dist/cloud/billing/index.js +2 -0
  27. package/dist/cloud/billing/usage-store.d.ts +42 -0
  28. package/dist/cloud/billing/usage-store.js +85 -0
  29. package/dist/cloud/billing/usage-tracker.d.ts +38 -0
  30. package/dist/cloud/billing/usage-tracker.js +95 -0
  31. package/dist/cloud/build-pipeline.d.ts +39 -0
  32. package/dist/cloud/build-pipeline.js +310 -0
  33. package/dist/cloud/build-queue.d.ts +30 -0
  34. package/dist/cloud/build-queue.js +70 -0
  35. package/dist/cloud/caddy-manager.d.ts +18 -0
  36. package/dist/cloud/caddy-manager.js +97 -0
  37. package/dist/cloud/container-backend.d.ts +62 -0
  38. package/dist/cloud/container-backend.js +59 -0
  39. package/dist/cloud/container-manager.d.ts +64 -0
  40. package/dist/cloud/container-manager.js +301 -0
  41. package/dist/cloud/crypto.d.ts +27 -0
  42. package/dist/cloud/crypto.js +63 -0
  43. package/dist/cloud/db/index.d.ts +27 -0
  44. package/dist/cloud/db/index.js +53 -0
  45. package/dist/cloud/db/migrations.d.ts +12 -0
  46. package/dist/cloud/db/migrations.js +329 -0
  47. package/dist/cloud/db/pg-store.d.ts +45 -0
  48. package/dist/cloud/db/pg-store.js +336 -0
  49. package/dist/cloud/failure-tracker.d.ts +51 -0
  50. package/dist/cloud/failure-tracker.js +102 -0
  51. package/dist/cloud/idle-monitor.d.ts +30 -0
  52. package/dist/cloud/idle-monitor.js +70 -0
  53. package/dist/cloud/mailer.d.ts +21 -0
  54. package/dist/cloud/mailer.js +193 -0
  55. package/dist/cloud/mcp-proxy.d.ts +58 -0
  56. package/dist/cloud/mcp-proxy.js +203 -0
  57. package/dist/cloud/metric-samples.d.ts +43 -0
  58. package/dist/cloud/metric-samples.js +85 -0
  59. package/dist/cloud/metrics.d.ts +26 -0
  60. package/dist/cloud/metrics.js +59 -0
  61. package/dist/cloud/multipart.d.ts +26 -0
  62. package/dist/cloud/multipart.js +132 -0
  63. package/dist/cloud/observability.d.ts +27 -0
  64. package/dist/cloud/observability.js +98 -0
  65. package/dist/cloud/rate-limiter.d.ts +31 -0
  66. package/dist/cloud/rate-limiter.js +58 -0
  67. package/dist/cloud/request-security.d.ts +5 -0
  68. package/dist/cloud/request-security.js +74 -0
  69. package/dist/cloud/resource-monitor.d.ts +69 -0
  70. package/dist/cloud/resource-monitor.js +130 -0
  71. package/dist/cloud/secret-store.d.ts +38 -0
  72. package/dist/cloud/secret-store.js +103 -0
  73. package/dist/cloud/security.d.ts +26 -0
  74. package/dist/cloud/security.js +142 -0
  75. package/dist/cloud/server.d.ts +21 -0
  76. package/dist/cloud/server.js +1079 -0
  77. package/dist/cloud/shared-state.d.ts +72 -0
  78. package/dist/cloud/shared-state.js +159 -0
  79. package/dist/cloud/ssrf.d.ts +43 -0
  80. package/dist/cloud/ssrf.js +150 -0
  81. package/dist/cloud/store.d.ts +41 -0
  82. package/dist/cloud/store.js +75 -0
  83. package/dist/cloud/stripe.d.ts +78 -0
  84. package/dist/cloud/stripe.js +317 -0
  85. package/dist/cloud/telemetry-store.d.ts +53 -0
  86. package/dist/cloud/telemetry-store.js +108 -0
  87. package/dist/cloud/web/auth.d.ts +225 -0
  88. package/dist/cloud/web/auth.js +555 -0
  89. package/dist/cloud/web/charts.d.ts +70 -0
  90. package/dist/cloud/web/charts.js +178 -0
  91. package/dist/cloud/web/csrf.d.ts +14 -0
  92. package/dist/cloud/web/csrf.js +22 -0
  93. package/dist/cloud/web/docs.d.ts +40 -0
  94. package/dist/cloud/web/docs.js +174 -0
  95. package/dist/cloud/web/router.d.ts +25 -0
  96. package/dist/cloud/web/router.js +1921 -0
  97. package/dist/cloud/web/static/alpine.min.js +5 -0
  98. package/dist/cloud/web/static/favicon.svg +4 -0
  99. package/dist/cloud/web/static/htmx-sse.js +290 -0
  100. package/dist/cloud/web/static/htmx.min.js +1 -0
  101. package/dist/cloud/web/static/style.css +2683 -0
  102. package/dist/cloud/web/static-server.d.ts +13 -0
  103. package/dist/cloud/web/static-server.js +73 -0
  104. package/dist/cloud/web/template-engine.d.ts +27 -0
  105. package/dist/cloud/web/template-engine.js +146 -0
  106. package/dist/cloud/web/templates/layouts/admin.hbs +57 -0
  107. package/dist/cloud/web/templates/layouts/auth.hbs +138 -0
  108. package/dist/cloud/web/templates/layouts/base.hbs +16 -0
  109. package/dist/cloud/web/templates/layouts/dashboard.hbs +39 -0
  110. package/dist/cloud/web/templates/layouts/landing.hbs +82 -0
  111. package/dist/cloud/web/templates/pages/admin/overview.hbs +123 -0
  112. package/dist/cloud/web/templates/pages/admin/servers.hbs +129 -0
  113. package/dist/cloud/web/templates/pages/admin/telemetry.hbs +39 -0
  114. package/dist/cloud/web/templates/pages/admin/user-edit.hbs +91 -0
  115. package/dist/cloud/web/templates/pages/admin/users.hbs +179 -0
  116. package/dist/cloud/web/templates/pages/auth/forgot-password.hbs +25 -0
  117. package/dist/cloud/web/templates/pages/auth/login.hbs +33 -0
  118. package/dist/cloud/web/templates/pages/auth/register.hbs +32 -0
  119. package/dist/cloud/web/templates/pages/auth/reset-password.hbs +34 -0
  120. package/dist/cloud/web/templates/pages/dashboard/billing.hbs +140 -0
  121. package/dist/cloud/web/templates/pages/dashboard/create.hbs +173 -0
  122. package/dist/cloud/web/templates/pages/dashboard/index.hbs +8 -0
  123. package/dist/cloud/web/templates/pages/dashboard/server-detail.hbs +280 -0
  124. package/dist/cloud/web/templates/pages/dashboard/server-logs.hbs +35 -0
  125. package/dist/cloud/web/templates/pages/dashboard/server-metrics.hbs +63 -0
  126. package/dist/cloud/web/templates/pages/dashboard/servers-partial.hbs +21 -0
  127. package/dist/cloud/web/templates/pages/dashboard/servers.hbs +44 -0
  128. package/dist/cloud/web/templates/pages/docs/show.hbs +16 -0
  129. package/dist/cloud/web/templates/pages/errors/404.hbs +9 -0
  130. package/dist/cloud/web/templates/pages/errors/500.hbs +8 -0
  131. package/dist/cloud/web/templates/pages/landing/index.hbs +223 -0
  132. package/dist/cloud/web/templates/pages/legal/privacy.hbs +71 -0
  133. package/dist/cloud/web/templates/pages/legal/terms.hbs +73 -0
  134. package/dist/cloud/web/templates/partials/admin-stats.hbs +52 -0
  135. package/dist/cloud/web/templates/partials/flash-message.hbs +6 -0
  136. package/dist/cloud/web/templates/partials/pricing-table.hbs +103 -0
  137. package/dist/cloud/web/templates/partials/server-card.hbs +19 -0
  138. package/dist/cloud/web/templates/partials/status-badge.hbs +1 -0
  139. package/dist/commands/bundle.d.ts +18 -0
  140. package/dist/commands/bundle.js +82 -0
  141. package/dist/commands/ci.d.ts +25 -0
  142. package/dist/commands/ci.js +149 -0
  143. package/dist/commands/deploy.d.ts +24 -0
  144. package/dist/commands/deploy.js +145 -0
  145. package/dist/commands/diff.d.ts +18 -0
  146. package/dist/commands/diff.js +185 -0
  147. package/dist/commands/from/describe.d.ts +65 -0
  148. package/dist/commands/from/describe.js +173 -0
  149. package/dist/commands/from/har.d.ts +81 -0
  150. package/dist/commands/from/har.js +255 -0
  151. package/dist/commands/from/openapi.d.ts +105 -0
  152. package/dist/commands/from/openapi.js +302 -0
  153. package/dist/commands/from/postman.d.ts +51 -0
  154. package/dist/commands/from/postman.js +146 -0
  155. package/dist/commands/from/target-support.d.ts +11 -0
  156. package/dist/commands/from/target-support.js +33 -0
  157. package/dist/commands/from/url.d.ts +75 -0
  158. package/dist/commands/from/url.js +244 -0
  159. package/dist/commands/from/website.d.ts +75 -0
  160. package/dist/commands/from/website.js +284 -0
  161. package/dist/commands/lint.d.ts +24 -0
  162. package/dist/commands/lint.js +184 -0
  163. package/dist/commands/merge.d.ts +18 -0
  164. package/dist/commands/merge.js +161 -0
  165. package/dist/commands/publish.d.ts +27 -0
  166. package/dist/commands/publish.js +334 -0
  167. package/dist/commands/rescan.d.ts +40 -0
  168. package/dist/commands/rescan.js +255 -0
  169. package/dist/commands/update.d.ts +14 -0
  170. package/dist/commands/update.js +87 -0
  171. package/dist/commands/verify.d.ts +14 -0
  172. package/dist/commands/verify.js +71 -0
  173. package/dist/config/configurable-command.d.ts +13 -0
  174. package/dist/config/configurable-command.js +70 -0
  175. package/dist/config/mcpmake-config.d.ts +68 -0
  176. package/dist/config/mcpmake-config.js +207 -0
  177. package/dist/docs/cli.md +400 -0
  178. package/dist/docs/mcp-2026-07-28-migration.md +78 -0
  179. package/dist/docs/migrate-from-stainless.md +94 -0
  180. package/dist/docs/quickstart.md +166 -0
  181. package/dist/docs/show-hn.md +26 -0
  182. package/dist/docs/website-servers.md +169 -0
  183. package/dist/emitter/code-writer.d.ts +8 -0
  184. package/dist/emitter/code-writer.js +25 -0
  185. package/dist/emitter/index.d.ts +32 -0
  186. package/dist/emitter/index.js +280 -0
  187. package/dist/emitter/mcpb-bundler.d.ts +31 -0
  188. package/dist/emitter/mcpb-bundler.js +172 -0
  189. package/dist/emitter/project-scaffolder.d.ts +4 -0
  190. package/dist/emitter/project-scaffolder.js +89 -0
  191. package/dist/emitter/python-template-loader.d.ts +4 -0
  192. package/dist/emitter/python-template-loader.js +30 -0
  193. package/dist/emitter/python-templates/dockerfile.hbs +14 -0
  194. package/dist/emitter/python-templates/env.example.hbs +6 -0
  195. package/dist/emitter/python-templates/requirements.txt.hbs +4 -0
  196. package/dist/emitter/python-templates/server.py.hbs +77 -0
  197. package/dist/emitter/site-scaffolder.d.ts +13 -0
  198. package/dist/emitter/site-scaffolder.js +70 -0
  199. package/dist/emitter/site-template-loader.d.ts +5 -0
  200. package/dist/emitter/site-template-loader.js +47 -0
  201. package/dist/emitter/site-templates/browser-manager.ts.hbs +233 -0
  202. package/dist/emitter/site-templates/config.ts.hbs +28 -0
  203. package/dist/emitter/site-templates/dockerfile.hbs +31 -0
  204. package/dist/emitter/site-templates/env.example.hbs +19 -0
  205. package/dist/emitter/site-templates/package.json.hbs +26 -0
  206. package/dist/emitter/site-templates/server-main-http.ts.hbs +108 -0
  207. package/dist/emitter/site-templates/server-main.ts.hbs +23 -0
  208. package/dist/emitter/site-templates/tool-handler-action.ts.hbs +86 -0
  209. package/dist/emitter/site-templates/tool-handler-form.ts.hbs +116 -0
  210. package/dist/emitter/site-templates/tool-handler-lifecycle.ts.hbs +146 -0
  211. package/dist/emitter/site-templates/tool-index.ts.hbs +11 -0
  212. package/dist/emitter/template-loader.d.ts +1 -0
  213. package/dist/emitter/template-loader.js +27 -0
  214. package/dist/emitter/templates/auth-provider.ts.hbs +57 -0
  215. package/dist/emitter/templates/config.ts.hbs +63 -0
  216. package/dist/emitter/templates/discovery.ts.hbs +301 -0
  217. package/dist/emitter/templates/dockerfile.hbs +34 -0
  218. package/dist/emitter/templates/env.example.hbs +28 -0
  219. package/dist/emitter/templates/gitignore.hbs +5 -0
  220. package/dist/emitter/templates/http-executor.ts.hbs +117 -0
  221. package/dist/emitter/templates/oauth.ts.hbs +188 -0
  222. package/dist/emitter/templates/package.json.hbs +25 -0
  223. package/dist/emitter/templates/prompts.ts.hbs +22 -0
  224. package/dist/emitter/templates/readme.md.hbs +123 -0
  225. package/dist/emitter/templates/resources.ts.hbs +63 -0
  226. package/dist/emitter/templates/server-main-http.ts.hbs +407 -0
  227. package/dist/emitter/templates/server-main.ts.hbs +40 -0
  228. package/dist/emitter/templates/task-handlers.ts.hbs +189 -0
  229. package/dist/emitter/templates/task-manager.ts.hbs +139 -0
  230. package/dist/emitter/templates/task-sse.ts.hbs +105 -0
  231. package/dist/emitter/templates/tool-handler.ts.hbs +124 -0
  232. package/dist/emitter/templates/tool-index.ts.hbs +11 -0
  233. package/dist/emitter/templates/tool-test.ts.hbs +57 -0
  234. package/dist/emitter/templates/trace.ts.hbs +79 -0
  235. package/dist/emitter/templates/tsconfig.json.hbs +16 -0
  236. package/dist/emitter/templates/types.ts.hbs +5 -0
  237. package/dist/emitter/worker-template-loader.d.ts +5 -0
  238. package/dist/emitter/worker-template-loader.js +33 -0
  239. package/dist/emitter/worker-templates/config.ts.hbs +54 -0
  240. package/dist/emitter/worker-templates/dev-vars.example.hbs +10 -0
  241. package/dist/emitter/worker-templates/gitignore.hbs +6 -0
  242. package/dist/emitter/worker-templates/package.json.hbs +24 -0
  243. package/dist/emitter/worker-templates/readme.md.hbs +53 -0
  244. package/dist/emitter/worker-templates/server.test.ts.hbs +20 -0
  245. package/dist/emitter/worker-templates/tool-handler.ts.hbs +85 -0
  246. package/dist/emitter/worker-templates/tool-index.ts.hbs +28 -0
  247. package/dist/emitter/worker-templates/tsconfig.json.hbs +17 -0
  248. package/dist/emitter/worker-templates/worker.ts.hbs +242 -0
  249. package/dist/emitter/worker-templates/wrangler.toml.hbs +19 -0
  250. package/dist/generator/spec-generator.d.ts +6 -0
  251. package/dist/generator/spec-generator.js +50 -0
  252. package/dist/index.d.ts +1 -0
  253. package/dist/index.js +64 -0
  254. package/dist/parser/har-filter.d.ts +8 -0
  255. package/dist/parser/har-filter.js +71 -0
  256. package/dist/parser/har-loader.d.ts +2 -0
  257. package/dist/parser/har-loader.js +14 -0
  258. package/dist/parser/har-normalizer.d.ts +20 -0
  259. package/dist/parser/har-normalizer.js +78 -0
  260. package/dist/parser/index.d.ts +10 -0
  261. package/dist/parser/index.js +6 -0
  262. package/dist/parser/openapi-loader.d.ts +6 -0
  263. package/dist/parser/openapi-loader.js +308 -0
  264. package/dist/parser/operation-extractor.d.ts +13 -0
  265. package/dist/parser/operation-extractor.js +155 -0
  266. package/dist/parser/overlay-loader.d.ts +10 -0
  267. package/dist/parser/overlay-loader.js +184 -0
  268. package/dist/parser/postman-loader.d.ts +9 -0
  269. package/dist/parser/postman-loader.js +106 -0
  270. package/dist/parser/schema-converter.d.ts +12 -0
  271. package/dist/parser/schema-converter.js +117 -0
  272. package/dist/plugins/adapter.d.ts +40 -0
  273. package/dist/plugins/adapter.js +15 -0
  274. package/dist/plugins/loader.d.ts +25 -0
  275. package/dist/plugins/loader.js +58 -0
  276. package/dist/pricing.d.ts +55 -0
  277. package/dist/pricing.js +133 -0
  278. package/dist/providers/index.d.ts +15 -0
  279. package/dist/providers/index.js +56 -0
  280. package/dist/recorder/browser-recorder.d.ts +22 -0
  281. package/dist/recorder/browser-recorder.js +205 -0
  282. package/dist/registry/official-registry.d.ts +90 -0
  283. package/dist/registry/official-registry.js +129 -0
  284. package/dist/rescan/diff-engine.d.ts +5 -0
  285. package/dist/rescan/diff-engine.js +312 -0
  286. package/dist/rescan/index.d.ts +3 -0
  287. package/dist/rescan/index.js +2 -0
  288. package/dist/rescan/rescan-runner.d.ts +42 -0
  289. package/dist/rescan/rescan-runner.js +69 -0
  290. package/dist/rescan/rescan-scheduler.d.ts +41 -0
  291. package/dist/rescan/rescan-scheduler.js +179 -0
  292. package/dist/site-transformer/browser-tools.d.ts +10 -0
  293. package/dist/site-transformer/browser-tools.js +59 -0
  294. package/dist/site-transformer/index.d.ts +2 -0
  295. package/dist/site-transformer/index.js +2 -0
  296. package/dist/site-transformer/selector-healer.d.ts +8 -0
  297. package/dist/site-transformer/selector-healer.js +106 -0
  298. package/dist/site-transformer/tool-generator.d.ts +13 -0
  299. package/dist/site-transformer/tool-generator.js +245 -0
  300. package/dist/transformer/auth-detector.d.ts +13 -0
  301. package/dist/transformer/auth-detector.js +90 -0
  302. package/dist/transformer/catalog-builder.d.ts +18 -0
  303. package/dist/transformer/catalog-builder.js +56 -0
  304. package/dist/transformer/client-compat.d.ts +6 -0
  305. package/dist/transformer/client-compat.js +44 -0
  306. package/dist/transformer/har-clusterer.d.ts +9 -0
  307. package/dist/transformer/har-clusterer.js +27 -0
  308. package/dist/transformer/har-dedup.d.ts +10 -0
  309. package/dist/transformer/har-dedup.js +81 -0
  310. package/dist/transformer/har-schema-inferrer.d.ts +15 -0
  311. package/dist/transformer/har-schema-inferrer.js +90 -0
  312. package/dist/transformer/har-to-operations.d.ts +13 -0
  313. package/dist/transformer/har-to-operations.js +192 -0
  314. package/dist/transformer/index.d.ts +8 -0
  315. package/dist/transformer/index.js +6 -0
  316. package/dist/transformer/llm-namer.d.ts +6 -0
  317. package/dist/transformer/llm-namer.js +59 -0
  318. package/dist/transformer/naming.d.ts +4 -0
  319. package/dist/transformer/naming.js +30 -0
  320. package/dist/transformer/operation-filter.d.ts +13 -0
  321. package/dist/transformer/operation-filter.js +52 -0
  322. package/dist/transformer/resource-builder.d.ts +12 -0
  323. package/dist/transformer/resource-builder.js +80 -0
  324. package/dist/transformer/schema-merger.d.ts +14 -0
  325. package/dist/transformer/schema-merger.js +65 -0
  326. package/dist/transformer/tool-builder.d.ts +3 -0
  327. package/dist/transformer/tool-builder.js +114 -0
  328. package/dist/types/index.d.ts +131 -0
  329. package/dist/types/index.js +1 -0
  330. package/dist/types/site.d.ts +284 -0
  331. package/dist/types/site.js +8 -0
  332. package/dist/utils/fail.d.ts +48 -0
  333. package/dist/utils/fail.js +204 -0
  334. package/dist/utils/fs.d.ts +5 -0
  335. package/dist/utils/fs.js +28 -0
  336. package/dist/utils/interactive.d.ts +6 -0
  337. package/dist/utils/interactive.js +30 -0
  338. package/dist/utils/logger.d.ts +1 -0
  339. package/dist/utils/logger.js +2 -0
  340. package/dist/utils/sanitize.d.ts +28 -0
  341. package/dist/utils/sanitize.js +44 -0
  342. package/dist/utils/watcher.d.ts +11 -0
  343. package/dist/utils/watcher.js +36 -0
  344. package/package.json +65 -0
@@ -0,0 +1,123 @@
1
+ <div class="admin-page">
2
+ <div class="admin-page-header">
3
+ <h1>System Overview</h1>
4
+ </div>
5
+
6
+ <div id="admin-stats" hx-get="/admin/_stats" hx-trigger="every 10s" hx-swap="innerHTML" aria-live="polite">
7
+ {{> admin-stats}}
8
+ </div>
9
+
10
+ <div class="health-panel">
11
+ <h2>Health</h2>
12
+ <div class="health-grid">
13
+ <div class="health-item">
14
+ <span class="health-badge {{#if health.db}}health-badge--ok{{else}}health-badge--bad{{/if}}">{{#if health.db}}OK{{else}}OFF{{/if}}</span>
15
+ <div class="health-meta">
16
+ <span class="health-name">Database</span>
17
+ <span class="health-sub">{{#if health.db}}reachable{{else}}in-memory mode{{/if}}</span>
18
+ </div>
19
+ </div>
20
+ <div class="health-item">
21
+ <span class="health-badge {{#if health.stripe}}health-badge--ok{{else}}health-badge--bad{{/if}}">{{#if health.stripe}}OK{{else}}OFF{{/if}}</span>
22
+ <div class="health-meta">
23
+ <span class="health-name">Stripe</span>
24
+ <span class="health-sub">{{#if health.stripe}}configured{{else}}not configured{{/if}}</span>
25
+ </div>
26
+ </div>
27
+ <div class="health-item">
28
+ <span class="health-badge health-badge--neutral">{{health.backend}}</span>
29
+ <div class="health-meta">
30
+ <span class="health-name">Container backend</span>
31
+ <span class="health-sub">driver</span>
32
+ </div>
33
+ </div>
34
+ <div class="health-item">
35
+ <span class="health-badge {{#if health.ramOk}}health-badge--ok{{else}}health-badge--bad{{/if}}">{{#if health.ramOk}}OK{{else}}ALERT{{/if}}</span>
36
+ <div class="health-meta">
37
+ <span class="health-name">RAM pressure</span>
38
+ <span class="health-sub">{{health.memUsedPct}}% used</span>
39
+ </div>
40
+ </div>
41
+ <div class="health-item">
42
+ <span class="health-badge {{#if health.diskOk}}health-badge--ok{{else}}health-badge--bad{{/if}}">{{#if health.diskOk}}OK{{else}}ALERT{{/if}}</span>
43
+ <div class="health-meta">
44
+ <span class="health-name">Disk pressure</span>
45
+ <span class="health-sub">{{health.diskUsedPct}}% used</span>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ {{#if health.alerts.length}}
50
+ <ul class="health-alerts">
51
+ {{#each health.alerts}}<li>{{this}}</li>{{/each}}
52
+ </ul>
53
+ {{/if}}
54
+ </div>
55
+
56
+ <div class="chart-panel">
57
+ <div class="chart-panel-header">
58
+ <h2>API failure rate</h2>
59
+ <span class="chart-panel-meta">last 60 minutes</span>
60
+ </div>
61
+ <div class="failure-rate-grid">
62
+ <div class="stat-card">
63
+ <span class="stat-card-value">{{failure.requests}}</span>
64
+ <span class="stat-card-label">Requests (last hour)</span>
65
+ </div>
66
+ <div class="stat-card">
67
+ <span class="stat-card-value {{#if (eq failure.serverErrorRatePct '0.0')}}status-green{{else}}status-red{{/if}}">{{failure.serverErrorRatePct}}%</span>
68
+ <span class="stat-card-label">Server error rate (5xx)</span>
69
+ </div>
70
+ <div class="stat-card">
71
+ <span class="stat-card-value">{{failure.clientErrorRatePct}}%</span>
72
+ <span class="stat-card-label">Client error rate (4xx)</span>
73
+ </div>
74
+ </div>
75
+ <div class="chart-box">
76
+ <span class="chart-box-label">Requests / minute (live)</span>
77
+ {{{lastHourChart}}}
78
+ </div>
79
+ </div>
80
+
81
+ <div class="chart-panel">
82
+ <div class="chart-panel-header">
83
+ <h2>History</h2>
84
+ <span class="chart-panel-meta">last 24 hours</span>
85
+ </div>
86
+ {{#if hasHistory}}
87
+ <div class="chart-grid">
88
+ <div class="chart-box">
89
+ <span class="chart-box-label">Requests vs 5xx errors</span>
90
+ {{{charts.requestsErrors}}}
91
+ <div class="chart-legend">
92
+ <span class="chart-legend-item"><span class="chart-swatch chart-swatch--a"></span>Requests</span>
93
+ <span class="chart-legend-item"><span class="chart-swatch chart-swatch--b"></span>5xx errors</span>
94
+ </div>
95
+ </div>
96
+ <div class="chart-box">
97
+ <span class="chart-box-label">Memory vs disk used (%)</span>
98
+ {{{charts.memDisk}}}
99
+ <div class="chart-legend">
100
+ <span class="chart-legend-item"><span class="chart-swatch chart-swatch--a"></span>Memory</span>
101
+ <span class="chart-legend-item"><span class="chart-swatch chart-swatch--b"></span>Disk</span>
102
+ </div>
103
+ </div>
104
+ <div class="chart-box">
105
+ <span class="chart-box-label">Total users</span>
106
+ {{{charts.users}}}
107
+ </div>
108
+ <div class="chart-box">
109
+ <span class="chart-box-label">Total servers</span>
110
+ {{{charts.servers}}}
111
+ </div>
112
+ </div>
113
+ {{else}}
114
+ <div class="chart-empty">
115
+ {{#if historyEmpty}}
116
+ <p>History requires <code>DATABASE_URL</code>. Configure a database to retain metric samples across restarts.</p>
117
+ {{else}}
118
+ <p>No metric samples recorded yet. Charts will populate as the sampler runs.</p>
119
+ {{/if}}
120
+ </div>
121
+ {{/if}}
122
+ </div>
123
+ </div>
@@ -0,0 +1,129 @@
1
+ <div class="admin-page" x-data="adminServers()">
2
+ <div class="admin-page-header">
3
+ <h1>All Servers</h1>
4
+ <span class="admin-page-count">{{servers.length}} total</span>
5
+ </div>
6
+
7
+ <input
8
+ type="text"
9
+ class="search-input"
10
+ placeholder="Filter by slug, user, or status..."
11
+ x-model="search"
12
+ >
13
+
14
+ <div class="admin-table-wrap">
15
+ <table class="admin-table">
16
+ <thead>
17
+ <tr>
18
+ <th @click="sortBy('slug')" :class="sortClass('slug')">Slug</th>
19
+ <th @click="sortBy('status')" :class="sortClass('status')">Status</th>
20
+ <th @click="sortBy('userId')" :class="sortClass('userId')">User</th>
21
+ <th @click="sortBy('toolCount')" :class="sortClass('toolCount')">Tools</th>
22
+ <th @click="sortBy('port')" :class="sortClass('port')">Port</th>
23
+ <th @click="sortBy('createdAt')" :class="sortClass('createdAt')">Created</th>
24
+ <th @click="sortBy('lastActiveAt')" :class="sortClass('lastActiveAt')">Last Active</th>
25
+ <th>Actions</th>
26
+ </tr>
27
+ </thead>
28
+ <tbody>
29
+ <template x-for="server in filteredServers()" :key="server.slug">
30
+ <tr>
31
+ <td><code x-text="server.slug"></code></td>
32
+ <td>
33
+ <span
34
+ class="status-dot"
35
+ :class="{
36
+ 'status-green': server.status === 'running',
37
+ 'status-gray': server.status === 'stopped',
38
+ 'status-red': server.status === 'error',
39
+ 'status-yellow': server.status === 'building'
40
+ }"
41
+ x-text="server.status"
42
+ ></span>
43
+ </td>
44
+ <td x-text="server.userEmail || server.userId || '(anonymous)'"></td>
45
+ <td x-text="server.toolCount"></td>
46
+ <td><code x-text="server.port"></code></td>
47
+ <td x-text="formatDate(server.createdAt)"></td>
48
+ <td x-text="formatDate(server.lastActiveAt)"></td>
49
+ <td class="admin-actions">
50
+ <form method="POST" :action="'/admin/servers/' + server.slug + '/delete'" style="display:inline">
51
+ <input type="hidden" name="_csrf" value="{{csrfToken}}">
52
+ <button
53
+ type="submit"
54
+ class="btn btn-sm btn-danger"
55
+ @click.prevent="confirmDelete($event, server.slug)"
56
+ >Delete</button>
57
+ </form>
58
+ </td>
59
+ </tr>
60
+ </template>
61
+ <template x-if="filteredServers().length === 0">
62
+ <tr>
63
+ <td colspan="8" class="admin-table-empty">No servers match your filter.</td>
64
+ </tr>
65
+ </template>
66
+ </tbody>
67
+ </table>
68
+ </div>
69
+ </div>
70
+
71
+ <script>
72
+ function adminServers() {
73
+ return {
74
+ search: '',
75
+ sortCol: 'createdAt',
76
+ sortDir: 'desc',
77
+ servers: {{{json servers}}},
78
+
79
+ sortBy(col) {
80
+ if (this.sortCol === col) {
81
+ this.sortDir = this.sortDir === 'asc' ? 'desc' : 'asc';
82
+ } else {
83
+ this.sortCol = col;
84
+ this.sortDir = 'asc';
85
+ }
86
+ },
87
+
88
+ sortClass(col) {
89
+ if (this.sortCol !== col) return 'sortable';
90
+ return 'sortable sorted-' + this.sortDir;
91
+ },
92
+
93
+ filteredServers() {
94
+ let result = this.servers;
95
+ const q = this.search.toLowerCase().trim();
96
+ if (q) {
97
+ result = result.filter(s =>
98
+ s.slug.toLowerCase().includes(q) ||
99
+ (s.userEmail || '').toLowerCase().includes(q) ||
100
+ (s.userId || '').toLowerCase().includes(q) ||
101
+ s.status.toLowerCase().includes(q)
102
+ );
103
+ }
104
+ const col = this.sortCol;
105
+ const dir = this.sortDir === 'asc' ? 1 : -1;
106
+ result = [...result].sort((a, b) => {
107
+ const va = a[col] ?? '';
108
+ const vb = b[col] ?? '';
109
+ if (typeof va === 'number' && typeof vb === 'number') return (va - vb) * dir;
110
+ return String(va).localeCompare(String(vb)) * dir;
111
+ });
112
+ return result;
113
+ },
114
+
115
+ formatDate(iso) {
116
+ if (!iso) return '';
117
+ const d = new Date(iso);
118
+ if (isNaN(d.getTime())) return iso;
119
+ return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
120
+ },
121
+
122
+ confirmDelete(event, slug) {
123
+ if (confirm('Delete server "' + slug + '"? This will stop the container and remove all data.')) {
124
+ event.target.closest('form').submit();
125
+ }
126
+ }
127
+ };
128
+ }
129
+ </script>
@@ -0,0 +1,39 @@
1
+ <div class="admin-page">
2
+ <div class="admin-page-header">
3
+ <h1>Telemetry</h1>
4
+ <span class="admin-page-count">{{groups.length}} groups</span>
5
+ </div>
6
+
7
+ <p class="form-hint">CLI crash and error reports, grouped by fingerprint. PII is stripped at ingest.</p>
8
+
9
+ <div class="admin-table-wrap">
10
+ <table class="admin-table">
11
+ <thead>
12
+ <tr>
13
+ <th>Count</th>
14
+ <th>Last seen</th>
15
+ <th>Command</th>
16
+ <th>Error</th>
17
+ <th>CLI</th>
18
+ <th>Platform</th>
19
+ </tr>
20
+ </thead>
21
+ <tbody>
22
+ {{#each groups}}
23
+ <tr>
24
+ <td><span class="telemetry-count">{{this.count}}</span></td>
25
+ <td>{{formatDate this.lastSeen}}</td>
26
+ <td><code>{{this.command}}</code></td>
27
+ <td class="telemetry-error" title="{{this.errorMessage}}">{{truncate this.errorMessage 120}}</td>
28
+ <td><code>{{this.cliVersion}}</code></td>
29
+ <td>{{this.platform}}</td>
30
+ </tr>
31
+ {{else}}
32
+ <tr>
33
+ <td colspan="6" class="admin-table-empty">No telemetry reports yet.</td>
34
+ </tr>
35
+ {{/each}}
36
+ </tbody>
37
+ </table>
38
+ </div>
39
+ </div>
@@ -0,0 +1,91 @@
1
+ <div class="admin-page">
2
+ <div class="admin-page-header">
3
+ <a href="/admin/users" class="admin-back-link">&larr; Users</a>
4
+ <h1>{{targetUser.email}}</h1>
5
+ </div>
6
+
7
+ <div class="admin-stats">
8
+ <div class="stat-card">
9
+ <span class="stat-card-value">
10
+ <span class="plan-badge plan-badge--{{targetUser.plan}}">{{targetUser.plan}}</span>
11
+ </span>
12
+ <span class="stat-card-label">Plan</span>
13
+ </div>
14
+ <div class="stat-card">
15
+ <span class="stat-card-value">{{balance}}</span>
16
+ <span class="stat-card-label">Credit Balance</span>
17
+ </div>
18
+ <div class="stat-card">
19
+ <span class="stat-card-value">{{serverCount}}</span>
20
+ <span class="stat-card-label">Servers</span>
21
+ </div>
22
+ <div class="stat-card">
23
+ <span class="stat-card-value {{#if targetUser.isAdmin}}status-green{{/if}}">{{#if targetUser.isAdmin}}Yes{{else}}No{{/if}}</span>
24
+ <span class="stat-card-label">Admin</span>
25
+ </div>
26
+ </div>
27
+
28
+ <div class="system-info">
29
+ <h2>Account</h2>
30
+ <dl class="system-info-list">
31
+ <div class="system-info-row">
32
+ <dt>Email</dt>
33
+ <dd>{{targetUser.email}}</dd>
34
+ </div>
35
+ <div class="system-info-row">
36
+ <dt>Registered</dt>
37
+ <dd>{{formatDate targetUser.createdAt}}</dd>
38
+ </div>
39
+ <div class="system-info-row">
40
+ <dt>Last login</dt>
41
+ <dd>{{#if targetUser.lastLoginAt}}{{formatDate targetUser.lastLoginAt}}{{else}}—{{/if}}</dd>
42
+ </div>
43
+ </dl>
44
+ </div>
45
+
46
+ <div class="credit-panel">
47
+ <h2>Adjust credits</h2>
48
+ <p class="form-hint">Grant or revoke promotional tool-call credits. Recorded in the ledger below.</p>
49
+ <form method="POST" action="/admin/users/{{targetUser.id}}/credits" class="credit-form">
50
+ <input type="hidden" name="_csrf" value="{{csrfToken}}">
51
+ <div class="credit-form-row">
52
+ <label class="credit-form-field">
53
+ <span>Amount</span>
54
+ <input type="number" name="amount" min="1" max="100000000" step="1" required class="search-input" placeholder="e.g. 1000">
55
+ </label>
56
+ <div class="credit-form-actions">
57
+ <button type="submit" name="action" value="grant" class="btn btn-primary">Grant</button>
58
+ <button type="submit" name="action" value="revoke" class="btn btn-secondary">Revoke</button>
59
+ </div>
60
+ </div>
61
+ </form>
62
+ </div>
63
+
64
+ <div class="admin-table-wrap">
65
+ <h2 class="ledger-heading">Recent ledger</h2>
66
+ <table class="admin-table">
67
+ <thead>
68
+ <tr>
69
+ <th>When</th>
70
+ <th>Change</th>
71
+ <th>Reason</th>
72
+ <th>Balance after</th>
73
+ </tr>
74
+ </thead>
75
+ <tbody>
76
+ {{#each ledger}}
77
+ <tr>
78
+ <td>{{formatDate this.createdAt}}</td>
79
+ <td><span class="{{#if this.isCredit}}status-green{{else}}status-red{{/if}}">{{this.deltaLabel}}</span></td>
80
+ <td><code>{{this.reason}}</code></td>
81
+ <td>{{this.balanceAfter}}</td>
82
+ </tr>
83
+ {{else}}
84
+ <tr>
85
+ <td colspan="4" class="admin-table-empty">No credit activity yet.</td>
86
+ </tr>
87
+ {{/each}}
88
+ </tbody>
89
+ </table>
90
+ </div>
91
+ </div>
@@ -0,0 +1,179 @@
1
+ <div class="admin-page" x-data="adminUsers()">
2
+ <div class="admin-page-header">
3
+ <h1>User Management</h1>
4
+ <span class="admin-page-count">{{users.length}} total</span>
5
+ </div>
6
+
7
+ <input
8
+ type="text"
9
+ class="search-input"
10
+ placeholder="Filter by email or plan..."
11
+ x-model="search"
12
+ >
13
+
14
+ <div class="admin-table-wrap">
15
+ <table class="admin-table">
16
+ <thead>
17
+ <tr>
18
+ <th @click="sortBy('email')" :class="sortClass('email')">Email</th>
19
+ <th @click="sortBy('plan')" :class="sortClass('plan')">Plan</th>
20
+ <th @click="sortBy('isAdmin')" :class="sortClass('isAdmin')">Admin?</th>
21
+ <th @click="sortBy('serverCount')" :class="sortClass('serverCount')">Servers</th>
22
+ <th @click="sortBy('createdAt')" :class="sortClass('createdAt')">Registered</th>
23
+ <th @click="sortBy('lastLoginAt')" :class="sortClass('lastLoginAt')">Last Login</th>
24
+ <th>Actions</th>
25
+ </tr>
26
+ </thead>
27
+ <tbody>
28
+ <template x-for="user in filteredUsers()" :key="user.id">
29
+ <tr>
30
+ <td><a class="admin-row-link" :href="'/admin/users/' + user.id" x-text="user.email"></a></td>
31
+ <td>
32
+ <span
33
+ class="plan-badge"
34
+ :class="'plan-badge--' + user.plan"
35
+ x-text="user.plan"
36
+ ></span>
37
+ </td>
38
+ <td>
39
+ <span
40
+ x-show="user.isAdmin"
41
+ class="admin-badge"
42
+ >admin</span>
43
+ </td>
44
+ <td x-text="user.serverCount"></td>
45
+ <td x-text="formatDate(user.createdAt)"></td>
46
+ <td x-text="formatDate(user.lastLoginAt)"></td>
47
+ <td class="admin-actions">
48
+ <form
49
+ class="inline-action"
50
+ method="POST"
51
+ :action="'/admin/users/' + user.id + '/plan'"
52
+ hx-post
53
+ :hx-post="'/admin/users/' + user.id + '/plan'"
54
+ hx-swap="none"
55
+ @htmx:after-request="handlePlanChange($event, user)"
56
+ >
57
+ <input type="hidden" name="_csrf" value="{{csrfToken}}">
58
+ <select name="plan" :value="user.plan" class="admin-select" @change="$el.closest('form').requestSubmit()">
59
+ <option value="free" :selected="user.plan === 'free'">free</option>
60
+ <option value="hobbyist" :selected="user.plan === 'hobbyist'">hobbyist</option>
61
+ <option value="pro" :selected="user.plan === 'pro'">pro</option>
62
+ <option value="team" :selected="user.plan === 'team'">team</option>
63
+ <option value="enterprise" :selected="user.plan === 'enterprise'">enterprise</option>
64
+ </select>
65
+ </form>
66
+
67
+ <form
68
+ class="inline-action"
69
+ method="POST"
70
+ :action="'/admin/users/' + user.id + '/admin'"
71
+ hx-post
72
+ :hx-post="'/admin/users/' + user.id + '/admin'"
73
+ hx-swap="none"
74
+ @htmx:after-request="handleAdminToggle($event, user)"
75
+ >
76
+ <input type="hidden" name="_csrf" value="{{csrfToken}}">
77
+ <button type="submit" class="btn btn-sm btn-secondary" x-text="user.isAdmin ? 'Revoke admin' : 'Make admin'"></button>
78
+ </form>
79
+
80
+ <form
81
+ class="inline-action"
82
+ method="POST"
83
+ :action="'/admin/users/' + user.id + '/delete'"
84
+ >
85
+ <input type="hidden" name="_csrf" value="{{csrfToken}}">
86
+ <button
87
+ type="submit"
88
+ class="btn btn-sm btn-danger"
89
+ @click.prevent="confirmDelete($event, user)"
90
+ >Delete</button>
91
+ </form>
92
+ </td>
93
+ </tr>
94
+ </template>
95
+ <template x-if="filteredUsers().length === 0">
96
+ <tr>
97
+ <td colspan="7" class="admin-table-empty">No users match your filter.</td>
98
+ </tr>
99
+ </template>
100
+ </tbody>
101
+ </table>
102
+ </div>
103
+ </div>
104
+
105
+ <script>
106
+ function adminUsers() {
107
+ return {
108
+ search: '',
109
+ sortCol: 'createdAt',
110
+ sortDir: 'desc',
111
+ users: {{{json users}}},
112
+
113
+ sortBy(col) {
114
+ if (this.sortCol === col) {
115
+ this.sortDir = this.sortDir === 'asc' ? 'desc' : 'asc';
116
+ } else {
117
+ this.sortCol = col;
118
+ this.sortDir = 'asc';
119
+ }
120
+ },
121
+
122
+ sortClass(col) {
123
+ if (this.sortCol !== col) return 'sortable';
124
+ return 'sortable sorted-' + this.sortDir;
125
+ },
126
+
127
+ filteredUsers() {
128
+ let result = this.users;
129
+ const q = this.search.toLowerCase().trim();
130
+ if (q) {
131
+ result = result.filter(u =>
132
+ u.email.toLowerCase().includes(q) ||
133
+ u.plan.toLowerCase().includes(q)
134
+ );
135
+ }
136
+ const col = this.sortCol;
137
+ const dir = this.sortDir === 'asc' ? 1 : -1;
138
+ result = [...result].sort((a, b) => {
139
+ const va = a[col] ?? '';
140
+ const vb = b[col] ?? '';
141
+ if (typeof va === 'number' && typeof vb === 'number') return (va - vb) * dir;
142
+ if (typeof va === 'boolean' && typeof vb === 'boolean') return ((va ? 1 : 0) - (vb ? 1 : 0)) * dir;
143
+ return String(va).localeCompare(String(vb)) * dir;
144
+ });
145
+ return result;
146
+ },
147
+
148
+ formatDate(iso) {
149
+ if (!iso) return '';
150
+ const d = new Date(iso);
151
+ if (isNaN(d.getTime())) return iso;
152
+ return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
153
+ },
154
+
155
+ handlePlanChange(event, user) {
156
+ if (event.detail.successful) {
157
+ const form = event.target;
158
+ const select = form.querySelector('select[name="plan"]');
159
+ user.plan = select.value;
160
+ }
161
+ },
162
+
163
+ handleAdminToggle(event, user) {
164
+ if (event.detail.successful) {
165
+ user.isAdmin = !user.isAdmin;
166
+ }
167
+ },
168
+
169
+ confirmDelete(event, user) {
170
+ if (user.isAdmin) {
171
+ if (!confirm('This user is an admin. Are you sure you want to delete "' + user.email + '"?')) return;
172
+ } else {
173
+ if (!confirm('Delete user "' + user.email + '"? This cannot be undone.')) return;
174
+ }
175
+ event.target.closest('form').submit();
176
+ }
177
+ };
178
+ }
179
+ </script>
@@ -0,0 +1,25 @@
1
+ <h1>Reset password</h1>
2
+ <p class="auth-subtitle">Enter your email to receive a reset link</p>
3
+
4
+ {{#if error}}
5
+ <div class="auth-error" role="alert" aria-live="assertive">{{error}}</div>
6
+ {{/if}}
7
+
8
+ {{#if success}}
9
+ <div class="auth-success" role="status" aria-live="polite">{{success}}</div>
10
+ {{else}}
11
+ <form method="POST" action="/forgot-password">
12
+ <input type="hidden" name="_csrf" value="{{csrfToken}}">
13
+
14
+ <div class="form-group">
15
+ <label for="email">Email</label>
16
+ <input type="email" id="email" name="email" value="{{email}}" required autocomplete="email" autofocus>
17
+ </div>
18
+
19
+ <button type="submit" class="auth-submit">Send reset link</button>
20
+ </form>
21
+ {{/if}}
22
+
23
+ <div class="auth-footer">
24
+ <a href="/login">Back to sign in</a>
25
+ </div>
@@ -0,0 +1,33 @@
1
+ <h1>Sign in</h1>
2
+ <p class="auth-subtitle">Welcome back to mcpmake</p>
3
+
4
+ {{#if success}}
5
+ <div class="auth-success" role="status" aria-live="polite">{{success}}</div>
6
+ {{/if}}
7
+
8
+ {{#if error}}
9
+ <div class="auth-error" role="alert" aria-live="assertive">{{error}}</div>
10
+ {{/if}}
11
+
12
+ <form method="POST" action="/login">
13
+ <input type="hidden" name="_csrf" value="{{csrfToken}}">
14
+
15
+ <div class="form-group">
16
+ <label for="email">Email</label>
17
+ <input type="email" id="email" name="email" value="{{email}}" required autocomplete="email" autofocus>
18
+ </div>
19
+
20
+ <div class="form-group">
21
+ <label for="password">Password</label>
22
+ <input type="password" id="password" name="password" required autocomplete="current-password">
23
+ </div>
24
+
25
+ <button type="submit" class="auth-submit">Sign in</button>
26
+ </form>
27
+
28
+ <div class="auth-footer">
29
+ <a href="/forgot-password">Forgot your password?</a>
30
+ </div>
31
+ <div class="auth-footer">
32
+ Don't have an account? <a href="/register">Register</a>
33
+ </div>
@@ -0,0 +1,32 @@
1
+ <h1>Create account</h1>
2
+ <p class="auth-subtitle">Get started with mcpmake Cloud</p>
3
+
4
+ {{#if error}}
5
+ <div class="auth-error" role="alert" aria-live="assertive">{{error}}</div>
6
+ {{/if}}
7
+
8
+ <form method="POST" action="/register">
9
+ <input type="hidden" name="_csrf" value="{{csrfToken}}">
10
+
11
+ <div class="form-group">
12
+ <label for="email">Email</label>
13
+ <input type="email" id="email" name="email" value="{{email}}" required autocomplete="email" autofocus>
14
+ </div>
15
+
16
+ <div class="form-group">
17
+ <label for="password">Password</label>
18
+ <input type="password" id="password" name="password" required autocomplete="new-password" minlength="8">
19
+ <p class="form-hint">Minimum 8 characters</p>
20
+ </div>
21
+
22
+ <div class="form-group">
23
+ <label for="confirmPassword">Confirm password</label>
24
+ <input type="password" id="confirmPassword" name="confirmPassword" required autocomplete="new-password">
25
+ </div>
26
+
27
+ <button type="submit" class="auth-submit">Create account</button>
28
+ </form>
29
+
30
+ <div class="auth-footer">
31
+ Already have an account? <a href="/login">Sign in</a>
32
+ </div>