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,317 @@
1
+ /**
2
+ * Stripe payment integration.
3
+ *
4
+ * Optional module — if STRIPE_SECRET_KEY is not set, all functions return
5
+ * null or throw descriptive errors. Uses dynamic import for the `stripe`
6
+ * package to avoid a hard dependency (same pattern as `pg`).
7
+ *
8
+ * Environment variables:
9
+ * STRIPE_SECRET_KEY — Stripe secret API key
10
+ * STRIPE_WEBHOOK_SECRET — Webhook endpoint signing secret
11
+ * STRIPE_PRICE_HOBBYIST — Price ID for the Hobbyist plan
12
+ * STRIPE_PRICE_PRO — Price ID for the Pro plan
13
+ * STRIPE_PRICE_TEAM — Price ID for the Team plan
14
+ */
15
+ import { logger } from '../utils/logger.js';
16
+ import { getUserById, getUserByStripeCustomerId, updateUser } from './web/auth.js';
17
+ import { getDb } from './shared-state.js';
18
+ // ---------------------------------------------------------------------------
19
+ // Price ID mapping
20
+ // ---------------------------------------------------------------------------
21
+ export const PLAN_PRICE_IDS = {
22
+ hobbyist: process.env.STRIPE_PRICE_HOBBYIST || '',
23
+ pro: process.env.STRIPE_PRICE_PRO || '',
24
+ team: process.env.STRIPE_PRICE_TEAM || '',
25
+ };
26
+ // ---------------------------------------------------------------------------
27
+ // Client singleton
28
+ // ---------------------------------------------------------------------------
29
+ let _stripeClient;
30
+ /**
31
+ * Get a configured Stripe client. Returns `null` when STRIPE_SECRET_KEY
32
+ * is not set. Caches the instance after first successful creation.
33
+ */
34
+ export async function getStripeClient() {
35
+ if (_stripeClient !== undefined)
36
+ return _stripeClient;
37
+ const secretKey = process.env.STRIPE_SECRET_KEY;
38
+ if (!secretKey) {
39
+ _stripeClient = null;
40
+ return null;
41
+ }
42
+ try {
43
+ // Dynamic import — same indirection trick used for `pg` so TypeScript
44
+ // doesn't try to resolve the module at compile time.
45
+ let stripeMod;
46
+ const mod = 'stripe';
47
+ stripeMod = await Function('m', 'return import(m)')(mod);
48
+ const Stripe = stripeMod.default ?? stripeMod.Stripe ?? stripeMod;
49
+ _stripeClient = new Stripe(secretKey, { apiVersion: '2024-12-18.acacia' });
50
+ return _stripeClient;
51
+ }
52
+ catch (err) {
53
+ logger.warn(`Failed to load stripe package: ${err}`);
54
+ _stripeClient = null;
55
+ return null;
56
+ }
57
+ }
58
+ /**
59
+ * Returns true when Stripe is configured (secret key is present).
60
+ * Does NOT load the Stripe SDK — just checks the env var.
61
+ */
62
+ export function isStripeConfigured() {
63
+ return !!process.env.STRIPE_SECRET_KEY;
64
+ }
65
+ // ---------------------------------------------------------------------------
66
+ // Checkout
67
+ // ---------------------------------------------------------------------------
68
+ /**
69
+ * Create a Stripe Checkout session for a plan upgrade.
70
+ *
71
+ * @returns The checkout session URL to redirect the user to.
72
+ * @throws If Stripe is not configured or the plan has no price ID.
73
+ */
74
+ export async function createCheckoutSession(opts) {
75
+ const stripe = await getStripeClient();
76
+ if (!stripe) {
77
+ throw new Error('Stripe is not configured');
78
+ }
79
+ const priceId = PLAN_PRICE_IDS[opts.plan];
80
+ if (!priceId) {
81
+ throw new Error(`No Stripe price ID configured for plan "${opts.plan}"`);
82
+ }
83
+ const session = await stripe.checkout.sessions.create({
84
+ mode: 'subscription',
85
+ customer_email: opts.email,
86
+ line_items: [{ price: priceId, quantity: 1 }],
87
+ success_url: opts.successUrl,
88
+ cancel_url: opts.cancelUrl,
89
+ client_reference_id: opts.userId,
90
+ metadata: {
91
+ userId: opts.userId,
92
+ plan: opts.plan,
93
+ },
94
+ // Propagate metadata onto the Subscription so that subscription.updated /
95
+ // subscription.deleted webhooks can resolve the user (they read
96
+ // subscription.metadata, not the checkout session's metadata).
97
+ subscription_data: {
98
+ metadata: {
99
+ userId: opts.userId,
100
+ plan: opts.plan,
101
+ },
102
+ },
103
+ });
104
+ if (!session.url) {
105
+ throw new Error('Stripe did not return a checkout URL');
106
+ }
107
+ return session.url;
108
+ }
109
+ // ---------------------------------------------------------------------------
110
+ // Billing portal
111
+ // ---------------------------------------------------------------------------
112
+ /**
113
+ * Create a Stripe billing portal session so the customer can manage
114
+ * their subscription (cancel, change payment method, view invoices).
115
+ *
116
+ * @returns The portal session URL to redirect the user to.
117
+ */
118
+ export async function createPortalSession(customerId, returnUrl) {
119
+ const stripe = await getStripeClient();
120
+ if (!stripe) {
121
+ throw new Error('Stripe is not configured');
122
+ }
123
+ const session = await stripe.billingPortal.sessions.create({
124
+ customer: customerId,
125
+ return_url: returnUrl,
126
+ });
127
+ return session.url;
128
+ }
129
+ // ---------------------------------------------------------------------------
130
+ // Webhook handler
131
+ // ---------------------------------------------------------------------------
132
+ // ---------------------------------------------------------------------------
133
+ // Webhook idempotency
134
+ // ---------------------------------------------------------------------------
135
+ /** In-memory fallback ledger of processed event IDs (used when no DB). */
136
+ const recentEventIds = new Set();
137
+ const MAX_RECENT_EVENTS = 5_000;
138
+ /** Whether this Stripe event has already been processed (replay protection). */
139
+ async function alreadyProcessed(eventId) {
140
+ const db = getDb();
141
+ if (db) {
142
+ const { rows } = await db.query('SELECT id FROM processed_stripe_events WHERE id = $1', [eventId]);
143
+ return rows.length > 0;
144
+ }
145
+ return recentEventIds.has(eventId);
146
+ }
147
+ /** Record an event as processed. Called only after successful handling so a
148
+ * failed delivery is re-tried by Stripe rather than silently dropped. */
149
+ async function markProcessed(eventId) {
150
+ const db = getDb();
151
+ if (db) {
152
+ await db.query('INSERT INTO processed_stripe_events (id) VALUES ($1) ON CONFLICT (id) DO NOTHING', [eventId]);
153
+ return;
154
+ }
155
+ recentEventIds.add(eventId);
156
+ if (recentEventIds.size > MAX_RECENT_EVENTS) {
157
+ const keep = [...recentEventIds].slice(-Math.floor(MAX_RECENT_EVENTS / 2));
158
+ recentEventIds.clear();
159
+ for (const id of keep)
160
+ recentEventIds.add(id);
161
+ }
162
+ }
163
+ /** Map Stripe price IDs back to plan names. */
164
+ function planFromPriceId(priceId) {
165
+ for (const [plan, id] of Object.entries(PLAN_PRICE_IDS)) {
166
+ if (id && id === priceId)
167
+ return plan;
168
+ }
169
+ return undefined;
170
+ }
171
+ /**
172
+ * Verify and handle an incoming Stripe webhook event.
173
+ *
174
+ * Handles:
175
+ * - checkout.session.completed — activate subscription, store customer ID
176
+ * - customer.subscription.updated — plan change / renewal
177
+ * - customer.subscription.deleted — cancellation, downgrade to free
178
+ *
179
+ * @param payload - The raw request body as a Buffer (required for signature verification)
180
+ * @param signature - The `stripe-signature` header value
181
+ */
182
+ export async function handleWebhookEvent(payload, signature) {
183
+ const stripe = await getStripeClient();
184
+ if (!stripe) {
185
+ throw new Error('Stripe is not configured');
186
+ }
187
+ const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
188
+ if (!webhookSecret) {
189
+ throw new Error('STRIPE_WEBHOOK_SECRET is not set');
190
+ }
191
+ // Verify signature — throws on invalid signature
192
+ const event = stripe.webhooks.constructEvent(payload, signature, webhookSecret);
193
+ // Idempotency: Stripe may deliver the same event more than once. Skip events
194
+ // we've already applied so a replay can't double-apply a plan change.
195
+ if (event.id && (await alreadyProcessed(event.id))) {
196
+ logger.info(`[stripe] Duplicate webhook ${event.id} (${event.type}) ignored`);
197
+ return;
198
+ }
199
+ switch (event.type) {
200
+ case 'checkout.session.completed': {
201
+ await handleCheckoutCompleted(event.data.object);
202
+ break;
203
+ }
204
+ case 'customer.subscription.updated': {
205
+ await handleSubscriptionUpdated(event.data.object);
206
+ break;
207
+ }
208
+ case 'customer.subscription.deleted': {
209
+ await handleSubscriptionDeleted(event.data.object);
210
+ break;
211
+ }
212
+ default:
213
+ logger.info(`[stripe] Unhandled webhook event type: ${event.type}`);
214
+ }
215
+ // Mark processed only after successful handling (above throws propagate to the
216
+ // caller → 400 → Stripe retries, which is what we want on a transient failure).
217
+ if (event.id)
218
+ await markProcessed(event.id);
219
+ }
220
+ /**
221
+ * Handle checkout.session.completed — the user just completed payment.
222
+ * Save the Stripe customer ID on the user and upgrade their plan.
223
+ */
224
+ async function handleCheckoutCompleted(session) {
225
+ const userId = session.client_reference_id ?? session.metadata?.userId;
226
+ const plan = session.metadata?.plan;
227
+ const customerId = session.customer;
228
+ if (!userId) {
229
+ logger.warn('[stripe] checkout.session.completed missing client_reference_id');
230
+ return;
231
+ }
232
+ const user = await getUserById(userId);
233
+ if (!user) {
234
+ logger.warn(`[stripe] checkout.session.completed: user ${userId} not found`);
235
+ return;
236
+ }
237
+ const updates = {
238
+ stripeCustomerId: customerId,
239
+ };
240
+ if (plan && ['hobbyist', 'pro', 'team', 'enterprise'].includes(plan)) {
241
+ updates.plan = plan;
242
+ }
243
+ await updateUser(userId, updates);
244
+ logger.info(`[stripe] User ${userId} upgraded to ${plan ?? 'unknown'}, customer=${customerId}`);
245
+ }
246
+ /**
247
+ * Resolve the local user for a Stripe subscription. Prefers the userId stored
248
+ * in subscription metadata (set via subscription_data.metadata at checkout),
249
+ * and falls back to looking up by the Stripe customer ID so that subscriptions
250
+ * created without metadata (or before this fix) are still attributed.
251
+ */
252
+ async function resolveSubscriptionUserId(subscription) {
253
+ const fromMetadata = subscription.metadata?.userId;
254
+ if (fromMetadata)
255
+ return fromMetadata;
256
+ const customerId = subscription.customer;
257
+ if (customerId && typeof customerId === 'string') {
258
+ const user = await getUserByStripeCustomerId(customerId);
259
+ if (user)
260
+ return user.id;
261
+ }
262
+ return undefined;
263
+ }
264
+ /** Extract the current period end (epoch ms) from a subscription, if present. */
265
+ function periodEndMs(subscription) {
266
+ const seconds = subscription.current_period_end;
267
+ return typeof seconds === 'number' ? seconds * 1000 : undefined;
268
+ }
269
+ /**
270
+ * Handle customer.subscription.updated — plan change or renewal.
271
+ * Look up the user by metadata or customer ID and update their plan + period.
272
+ */
273
+ async function handleSubscriptionUpdated(subscription) {
274
+ const userId = await resolveSubscriptionUserId(subscription);
275
+ if (!userId) {
276
+ logger.warn('[stripe] subscription.updated: could not resolve user — skipping');
277
+ return;
278
+ }
279
+ const user = await getUserById(userId);
280
+ if (!user) {
281
+ logger.warn(`[stripe] subscription.updated: user ${userId} not found`);
282
+ return;
283
+ }
284
+ const updates = {};
285
+ // Determine the new plan from the first subscription item's price.
286
+ const items = subscription.items?.data;
287
+ const priceId = items?.[0]?.price?.id;
288
+ const plan = priceId ? planFromPriceId(priceId) : undefined;
289
+ if (plan)
290
+ updates.plan = plan;
291
+ // Capture the billing period anchor so usage quotas align to the subscription.
292
+ const end = periodEndMs(subscription);
293
+ if (end)
294
+ updates.subscriptionPeriodEnd = end;
295
+ if (Object.keys(updates).length === 0)
296
+ return;
297
+ await updateUser(userId, updates);
298
+ logger.info(`[stripe] User ${userId} subscription updated (plan: ${plan ?? 'unchanged'})`);
299
+ }
300
+ /**
301
+ * Handle customer.subscription.deleted — cancellation.
302
+ * Downgrade the user to the free plan.
303
+ */
304
+ async function handleSubscriptionDeleted(subscription) {
305
+ const userId = await resolveSubscriptionUserId(subscription);
306
+ if (!userId) {
307
+ logger.warn('[stripe] subscription.deleted: could not resolve user — skipping');
308
+ return;
309
+ }
310
+ const user = await getUserById(userId);
311
+ if (!user) {
312
+ logger.warn(`[stripe] subscription.deleted: user ${userId} not found`);
313
+ return;
314
+ }
315
+ await updateUser(userId, { plan: 'free' });
316
+ logger.info(`[stripe] User ${userId} subscription canceled — downgraded to free`);
317
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Storage for CLI crash/error reports received at POST /api/telemetry/report.
3
+ *
4
+ * Reports are already validated, whitelisted, truncated, and stripped of PII
5
+ * (home-dir paths / tokens) by the time they reach here — the endpoint and the
6
+ * CLI's `fail()` helper own that. This layer only persists and groups them.
7
+ *
8
+ * Backed by the `telemetry_reports` Postgres table when a database is
9
+ * configured; otherwise a bounded in-memory ring (dev / single-process).
10
+ */
11
+ import type { Database } from './db/index.js';
12
+ export interface TelemetryReport {
13
+ receivedAt: string;
14
+ /** Hashed client IP (never the raw address). */
15
+ clientIp: string;
16
+ cliVersion: string;
17
+ platform: string;
18
+ nodeVersion: string;
19
+ command: string;
20
+ errorMessage: string;
21
+ stack: string;
22
+ /** Hash of (command + normalized message) — groups duplicate failures. */
23
+ fingerprint: string;
24
+ }
25
+ /** A group of reports sharing a fingerprint, for the admin view. */
26
+ export interface TelemetryGroup {
27
+ fingerprint: string;
28
+ count: number;
29
+ lastSeen: string;
30
+ command: string;
31
+ errorMessage: string;
32
+ cliVersion: string;
33
+ platform: string;
34
+ }
35
+ export interface TelemetryStore {
36
+ save(report: TelemetryReport): Promise<void>;
37
+ /** Most-recent reports grouped by fingerprint (most recent first). */
38
+ recentGrouped(limit: number): Promise<TelemetryGroup[]>;
39
+ prune(olderThanMs: number): Promise<void>;
40
+ }
41
+ export declare class InMemoryTelemetryStore implements TelemetryStore {
42
+ private reports;
43
+ save(report: TelemetryReport): Promise<void>;
44
+ recentGrouped(limit: number): Promise<TelemetryGroup[]>;
45
+ prune(olderThanMs: number): Promise<void>;
46
+ }
47
+ export declare class PgTelemetryStore implements TelemetryStore {
48
+ private db;
49
+ constructor(db: Database);
50
+ save(report: TelemetryReport): Promise<void>;
51
+ recentGrouped(limit: number): Promise<TelemetryGroup[]>;
52
+ prune(olderThanMs: number): Promise<void>;
53
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Storage for CLI crash/error reports received at POST /api/telemetry/report.
3
+ *
4
+ * Reports are already validated, whitelisted, truncated, and stripped of PII
5
+ * (home-dir paths / tokens) by the time they reach here — the endpoint and the
6
+ * CLI's `fail()` helper own that. This layer only persists and groups them.
7
+ *
8
+ * Backed by the `telemetry_reports` Postgres table when a database is
9
+ * configured; otherwise a bounded in-memory ring (dev / single-process).
10
+ */
11
+ import { randomBytes } from 'node:crypto';
12
+ // ---------------------------------------------------------------------------
13
+ // In-memory (dev / no database) — bounded ring
14
+ // ---------------------------------------------------------------------------
15
+ const MAX_INMEMORY_REPORTS = 1_000;
16
+ export class InMemoryTelemetryStore {
17
+ reports = [];
18
+ async save(report) {
19
+ this.reports.push(report);
20
+ if (this.reports.length > MAX_INMEMORY_REPORTS) {
21
+ this.reports.splice(0, this.reports.length - MAX_INMEMORY_REPORTS);
22
+ }
23
+ }
24
+ async recentGrouped(limit) {
25
+ const groups = new Map();
26
+ // Iterate newest-first so each group keeps the latest sample fields.
27
+ for (let i = this.reports.length - 1; i >= 0; i--) {
28
+ const r = this.reports[i];
29
+ const existing = groups.get(r.fingerprint);
30
+ if (existing) {
31
+ existing.count++;
32
+ if (r.receivedAt > existing.lastSeen)
33
+ existing.lastSeen = r.receivedAt;
34
+ }
35
+ else {
36
+ groups.set(r.fingerprint, {
37
+ fingerprint: r.fingerprint,
38
+ count: 1,
39
+ lastSeen: r.receivedAt,
40
+ command: r.command,
41
+ errorMessage: r.errorMessage,
42
+ cliVersion: r.cliVersion,
43
+ platform: r.platform,
44
+ });
45
+ }
46
+ }
47
+ return [...groups.values()].sort((a, b) => (a.lastSeen < b.lastSeen ? 1 : -1)).slice(0, limit);
48
+ }
49
+ async prune(olderThanMs) {
50
+ const cutoff = Date.now() - olderThanMs;
51
+ this.reports = this.reports.filter((r) => Date.parse(r.receivedAt) >= cutoff);
52
+ }
53
+ }
54
+ // ---------------------------------------------------------------------------
55
+ // Postgres
56
+ // ---------------------------------------------------------------------------
57
+ export class PgTelemetryStore {
58
+ db;
59
+ constructor(db) {
60
+ this.db = db;
61
+ }
62
+ async save(report) {
63
+ await this.db.query(`INSERT INTO telemetry_reports
64
+ (id, received_at, client_ip, cli_version, platform, node_version,
65
+ command, error_message, stack, fingerprint)
66
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, [
67
+ randomBytes(16).toString('hex'),
68
+ report.receivedAt,
69
+ report.clientIp,
70
+ report.cliVersion,
71
+ report.platform,
72
+ report.nodeVersion,
73
+ report.command,
74
+ report.errorMessage,
75
+ report.stack,
76
+ report.fingerprint,
77
+ ]);
78
+ }
79
+ async recentGrouped(limit) {
80
+ const { rows } = await this.db.query(`SELECT fingerprint,
81
+ count(*) AS count,
82
+ max(received_at) AS last_seen,
83
+ (array_agg(command ORDER BY received_at DESC))[1] AS command,
84
+ (array_agg(error_message ORDER BY received_at DESC))[1] AS error_message,
85
+ (array_agg(cli_version ORDER BY received_at DESC))[1] AS cli_version,
86
+ (array_agg(platform ORDER BY received_at DESC))[1] AS platform
87
+ FROM telemetry_reports
88
+ GROUP BY fingerprint
89
+ ORDER BY last_seen DESC
90
+ LIMIT $1`, [limit]);
91
+ return rows.map((row) => {
92
+ const lastSeen = row.last_seen;
93
+ return {
94
+ fingerprint: String(row.fingerprint ?? ''),
95
+ count: typeof row.count === 'number' ? row.count : Number(row.count ?? 0),
96
+ lastSeen: typeof lastSeen === 'string' ? lastSeen : new Date(lastSeen).toISOString(),
97
+ command: String(row.command ?? ''),
98
+ errorMessage: String(row.error_message ?? ''),
99
+ cliVersion: String(row.cli_version ?? ''),
100
+ platform: String(row.platform ?? ''),
101
+ };
102
+ });
103
+ }
104
+ async prune(olderThanMs) {
105
+ const cutoff = new Date(Date.now() - olderThanMs).toISOString();
106
+ await this.db.query('DELETE FROM telemetry_reports WHERE received_at < $1', [cutoff]);
107
+ }
108
+ }