plum-e2e 2.8.1 → 2.8.3

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 (618) hide show
  1. package/backend/Dockerfile +1 -1
  2. package/backend/app.js +1 -0
  3. package/backend/mcp/server.js +444 -448
  4. package/backend/package.json +2 -3
  5. package/backend/routes/mcp.routes.js +46 -0
  6. package/bin/plum.js +0 -27
  7. package/bin/postinstall.js +5 -5
  8. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/BackLink.CvWWuO3h.css +5 -0
  9. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/Badge.D1SV0Gv7.css +5 -0
  10. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/ConfirmModal.Dd3uP-Sk.css +5 -0
  11. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/EmptyState.DUDM4P19.css +5 -0
  12. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/Modal.BuaCfdLj.css +5 -0
  13. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/Pagination.DZmi2Po-.css +5 -0
  14. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/Toast.DENwYn3g.css +5 -0
  15. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_layout.BuhMvMUj.css +5 -0
  16. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.0Eln51hV.css +5 -0
  17. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.5IJAEAjc.css +5 -0
  18. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.BR3wgXcZ.css +5 -0
  19. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.BWAH0Beh.css +5 -0
  20. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.BZfe6nWe.css +5 -0
  21. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.Bj_a_5lv.css +5 -0
  22. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.BwgOqeMo.css +5 -0
  23. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.CBHi6pzT.css +5 -0
  24. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.CFnbmY-D.css +5 -0
  25. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.DvMkmYA-.css +5 -0
  26. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page._h2o67kg.css +5 -0
  27. package/frontend/.svelte-kit/adapter-node/chunks/BackLink.js +5 -0
  28. package/frontend/.svelte-kit/adapter-node/chunks/Badge.js +5 -0
  29. package/frontend/.svelte-kit/adapter-node/chunks/ConfirmModal.js +5 -0
  30. package/frontend/.svelte-kit/adapter-node/chunks/EmptyState.js +5 -0
  31. package/frontend/.svelte-kit/adapter-node/chunks/Modal.js +5 -0
  32. package/frontend/.svelte-kit/adapter-node/chunks/Toast.js +5 -0
  33. package/frontend/.svelte-kit/adapter-node/chunks/attributes.js +5 -0
  34. package/frontend/.svelte-kit/adapter-node/chunks/auth.js +5 -0
  35. package/frontend/.svelte-kit/adapter-node/chunks/auth2.js +5 -0
  36. package/frontend/.svelte-kit/adapter-node/chunks/client.js +5 -0
  37. package/frontend/.svelte-kit/adapter-node/chunks/common.js +5 -0
  38. package/frontend/.svelte-kit/adapter-node/chunks/constants.js +5 -0
  39. package/frontend/.svelte-kit/adapter-node/chunks/escaping.js +5 -0
  40. package/frontend/.svelte-kit/adapter-node/chunks/exports.js +5 -0
  41. package/frontend/.svelte-kit/adapter-node/chunks/format.js +5 -0
  42. package/frontend/.svelte-kit/adapter-node/chunks/index-server.js +5 -0
  43. package/frontend/.svelte-kit/adapter-node/chunks/index.js +5 -0
  44. package/frontend/.svelte-kit/adapter-node/chunks/index2.js +5 -0
  45. package/frontend/.svelte-kit/adapter-node/chunks/internal.js +5 -0
  46. package/frontend/.svelte-kit/adapter-node/chunks/reports.js +5 -0
  47. package/frontend/.svelte-kit/adapter-node/chunks/reports2.js +5 -0
  48. package/frontend/.svelte-kit/adapter-node/chunks/repository.js +5 -0
  49. package/frontend/.svelte-kit/adapter-node/chunks/runner.js +5 -0
  50. package/frontend/.svelte-kit/adapter-node/chunks/stores.js +5 -0
  51. package/frontend/.svelte-kit/adapter-node/chunks/theme.js +5 -0
  52. package/frontend/.svelte-kit/adapter-node/entries/fallbacks/error.svelte.js +5 -0
  53. package/frontend/.svelte-kit/adapter-node/entries/pages/_layout.js +5 -0
  54. package/frontend/.svelte-kit/adapter-node/entries/pages/_layout.svelte.js +5 -0
  55. package/frontend/.svelte-kit/adapter-node/entries/pages/_page.svelte.js +5 -0
  56. package/frontend/.svelte-kit/adapter-node/entries/pages/login/_page.svelte.js +5 -0
  57. package/frontend/.svelte-kit/adapter-node/entries/pages/reports/_id_/_page.svelte.js +5 -0
  58. package/frontend/.svelte-kit/adapter-node/entries/pages/reports/_page.svelte.js +5 -0
  59. package/frontend/.svelte-kit/adapter-node/entries/pages/reports/live/_page.svelte.js +5 -0
  60. package/frontend/.svelte-kit/adapter-node/entries/pages/scheduled-tests/_page.svelte.js +5 -0
  61. package/frontend/.svelte-kit/adapter-node/entries/pages/settings/_page.svelte.js +5 -0
  62. package/frontend/.svelte-kit/adapter-node/entries/pages/setup/_page.svelte.js +5 -0
  63. package/frontend/.svelte-kit/adapter-node/entries/pages/test-repository/_page.svelte.js +5 -0
  64. package/frontend/.svelte-kit/adapter-node/entries/pages/test-repository/runs/_id_/_page.svelte.js +5 -0
  65. package/frontend/.svelte-kit/adapter-node/entries/pages/test-repository/suites/_id_/_page.svelte.js +5 -0
  66. package/frontend/.svelte-kit/adapter-node/index.js +5 -0
  67. package/frontend/.svelte-kit/adapter-node/internal.js +5 -0
  68. package/frontend/.svelte-kit/adapter-node/manifest-full.js +5 -0
  69. package/frontend/.svelte-kit/adapter-node/manifest.js +5 -0
  70. package/frontend/.svelte-kit/adapter-node/nodes/0.js +5 -0
  71. package/frontend/.svelte-kit/adapter-node/nodes/1.js +5 -0
  72. package/frontend/.svelte-kit/adapter-node/nodes/10.js +5 -0
  73. package/frontend/.svelte-kit/adapter-node/nodes/11.js +5 -0
  74. package/frontend/.svelte-kit/adapter-node/nodes/12.js +5 -0
  75. package/frontend/.svelte-kit/adapter-node/nodes/2.js +5 -0
  76. package/frontend/.svelte-kit/adapter-node/nodes/3.js +5 -0
  77. package/frontend/.svelte-kit/adapter-node/nodes/4.js +5 -0
  78. package/frontend/.svelte-kit/adapter-node/nodes/5.js +5 -0
  79. package/frontend/.svelte-kit/adapter-node/nodes/6.js +5 -0
  80. package/frontend/.svelte-kit/adapter-node/nodes/7.js +5 -0
  81. package/frontend/.svelte-kit/adapter-node/nodes/8.js +5 -0
  82. package/frontend/.svelte-kit/adapter-node/nodes/9.js +5 -0
  83. package/frontend/.svelte-kit/ambient.d.ts +5 -0
  84. package/frontend/.svelte-kit/generated/client/app.js +5 -0
  85. package/frontend/.svelte-kit/generated/client/matchers.js +5 -0
  86. package/frontend/.svelte-kit/generated/client/nodes/0.js +5 -0
  87. package/frontend/.svelte-kit/generated/client/nodes/1.js +5 -0
  88. package/frontend/.svelte-kit/generated/client/nodes/10.js +5 -0
  89. package/frontend/.svelte-kit/generated/client/nodes/11.js +5 -0
  90. package/frontend/.svelte-kit/generated/client/nodes/12.js +5 -0
  91. package/frontend/.svelte-kit/generated/client/nodes/2.js +5 -0
  92. package/frontend/.svelte-kit/generated/client/nodes/3.js +5 -0
  93. package/frontend/.svelte-kit/generated/client/nodes/4.js +5 -0
  94. package/frontend/.svelte-kit/generated/client/nodes/5.js +5 -0
  95. package/frontend/.svelte-kit/generated/client/nodes/6.js +5 -0
  96. package/frontend/.svelte-kit/generated/client/nodes/7.js +5 -0
  97. package/frontend/.svelte-kit/generated/client/nodes/8.js +5 -0
  98. package/frontend/.svelte-kit/generated/client/nodes/9.js +5 -0
  99. package/frontend/.svelte-kit/generated/client-optimized/app.js +5 -0
  100. package/frontend/.svelte-kit/generated/client-optimized/matchers.js +5 -0
  101. package/frontend/.svelte-kit/generated/client-optimized/nodes/0.js +5 -0
  102. package/frontend/.svelte-kit/generated/client-optimized/nodes/1.js +5 -0
  103. package/frontend/.svelte-kit/generated/client-optimized/nodes/10.js +5 -0
  104. package/frontend/.svelte-kit/generated/client-optimized/nodes/11.js +5 -0
  105. package/frontend/.svelte-kit/generated/client-optimized/nodes/12.js +5 -0
  106. package/frontend/.svelte-kit/generated/client-optimized/nodes/2.js +5 -0
  107. package/frontend/.svelte-kit/generated/client-optimized/nodes/3.js +5 -0
  108. package/frontend/.svelte-kit/generated/client-optimized/nodes/4.js +5 -0
  109. package/frontend/.svelte-kit/generated/client-optimized/nodes/5.js +5 -0
  110. package/frontend/.svelte-kit/generated/client-optimized/nodes/6.js +5 -0
  111. package/frontend/.svelte-kit/generated/client-optimized/nodes/7.js +5 -0
  112. package/frontend/.svelte-kit/generated/client-optimized/nodes/8.js +5 -0
  113. package/frontend/.svelte-kit/generated/client-optimized/nodes/9.js +5 -0
  114. package/frontend/.svelte-kit/generated/root.js +5 -0
  115. package/frontend/.svelte-kit/generated/root.svelte +5 -0
  116. package/frontend/.svelte-kit/generated/server/internal.js +5 -0
  117. package/frontend/.svelte-kit/non-ambient.d.ts +5 -0
  118. package/frontend/.svelte-kit/output/client/_app/immutable/assets/0.BhSG-jZH.css +5 -0
  119. package/frontend/.svelte-kit/output/client/_app/immutable/assets/10.CdaQUS6U.css +5 -0
  120. package/frontend/.svelte-kit/output/client/_app/immutable/assets/11.BWAH0Beh.css +5 -0
  121. package/frontend/.svelte-kit/output/client/_app/immutable/assets/12.Bj_a_5lv.css +5 -0
  122. package/frontend/.svelte-kit/output/client/_app/immutable/assets/2.DvMkmYA-.css +5 -0
  123. package/frontend/.svelte-kit/output/client/_app/immutable/assets/3.BR3wgXcZ.css +5 -0
  124. package/frontend/.svelte-kit/output/client/_app/immutable/assets/4.0Eln51hV.css +5 -0
  125. package/frontend/.svelte-kit/output/client/_app/immutable/assets/5.DFV9OgAu.css +5 -0
  126. package/frontend/.svelte-kit/output/client/_app/immutable/assets/6.CFnbmY-D.css +5 -0
  127. package/frontend/.svelte-kit/output/client/_app/immutable/assets/7.CBHi6pzT.css +5 -0
  128. package/frontend/.svelte-kit/output/client/_app/immutable/assets/8.BwgOqeMo.css +5 -0
  129. package/frontend/.svelte-kit/output/client/_app/immutable/assets/9._h2o67kg.css +5 -0
  130. package/frontend/.svelte-kit/output/client/_app/immutable/assets/BackLink.CvWWuO3h.css +5 -0
  131. package/frontend/.svelte-kit/output/client/_app/immutable/assets/Badge.D1SV0Gv7.css +5 -0
  132. package/frontend/.svelte-kit/output/client/_app/immutable/assets/Button.FOWXxvc7.css +5 -0
  133. package/frontend/.svelte-kit/output/client/_app/immutable/assets/ConfirmModal.Dd3uP-Sk.css +5 -0
  134. package/frontend/.svelte-kit/output/client/_app/immutable/assets/EmptyState.DUDM4P19.css +5 -0
  135. package/frontend/.svelte-kit/output/client/_app/immutable/assets/Modal.BZpm0HP8.css +5 -0
  136. package/frontend/.svelte-kit/output/client/_app/immutable/assets/Modal.BuaCfdLj.css +5 -0
  137. package/frontend/.svelte-kit/output/client/_app/immutable/assets/Pagination.DZmi2Po-.css +5 -0
  138. package/frontend/.svelte-kit/output/client/_app/immutable/assets/Toast.DENwYn3g.css +5 -0
  139. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_layout.BuhMvMUj.css +5 -0
  140. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.0Eln51hV.css +5 -0
  141. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.5IJAEAjc.css +5 -0
  142. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.BR3wgXcZ.css +5 -0
  143. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.BWAH0Beh.css +5 -0
  144. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.BZfe6nWe.css +5 -0
  145. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.Bj_a_5lv.css +5 -0
  146. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.BwgOqeMo.css +5 -0
  147. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.CBHi6pzT.css +5 -0
  148. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.CFnbmY-D.css +5 -0
  149. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.DvMkmYA-.css +5 -0
  150. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page._h2o67kg.css +5 -0
  151. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/2zMvi6fR.js +5 -0
  152. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/3InFPLvE.js +5 -0
  153. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/BAACMRH5.js +5 -0
  154. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/BAMUGSh7.js +5 -0
  155. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/BKuh57WU.js +5 -0
  156. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/BUwxRmHf.js +5 -0
  157. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/Bfc47y5P.js +5 -0
  158. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/BmZ9upwL.js +5 -0
  159. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/C1Vid6wz.js +5 -0
  160. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/C8OURRSs.js +5 -0
  161. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/CGeZlCaI.js +5 -0
  162. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/CKDtmZUQ.js +5 -0
  163. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/CMBQQ9RZ.js +5 -0
  164. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/CMEtqUfQ.js +5 -0
  165. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/CNdcuGR0.js +5 -0
  166. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/CUC98kMZ.js +5 -0
  167. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/CgnwuKKO.js +5 -0
  168. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/Co3xKhEp.js +5 -0
  169. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/CuqnZUex.js +5 -0
  170. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/CzlPaHY9.js +5 -0
  171. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/DCgHuuvs.js +5 -0
  172. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/DEKAA-Fm.js +5 -0
  173. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/DHM6H-es.js +5 -0
  174. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/DMZwI8Cj.js +5 -0
  175. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/DSLBMEpr.js +5 -0
  176. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/DbgD7JLa.js +5 -0
  177. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/Dhf4xQKo.js +5 -0
  178. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/Dlnxgs49.js +5 -0
  179. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/TOCyHP97.js +5 -0
  180. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/t-La0hgu.js +5 -0
  181. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/w0D4Uzlq.js +5 -0
  182. package/frontend/.svelte-kit/output/client/_app/immutable/entry/app.B0GH5ONo.js +5 -0
  183. package/frontend/.svelte-kit/output/client/_app/immutable/entry/start.Bn25CW9X.js +5 -0
  184. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/0.-imXt0f5.js +5 -0
  185. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/1.Dom3CKWF.js +5 -0
  186. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/10.DEbnxfND.js +5 -0
  187. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/11.DMlayLCK.js +5 -0
  188. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/12.Bu89A1eX.js +5 -0
  189. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/2.BnHalSCi.js +5 -0
  190. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/3.B86rlHXU.js +5 -0
  191. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/4.D-YT_JJe.js +5 -0
  192. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/5.CqTXvadV.js +5 -0
  193. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/6.BJZ62MCd.js +5 -0
  194. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/7.C-pKvVZy.js +5 -0
  195. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/8.B4fj2Xyu.js +5 -0
  196. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/9.C57_2xq6.js +5 -0
  197. package/frontend/.svelte-kit/output/server/_app/immutable/assets/BackLink.CvWWuO3h.css +5 -0
  198. package/frontend/.svelte-kit/output/server/_app/immutable/assets/Badge.D1SV0Gv7.css +5 -0
  199. package/frontend/.svelte-kit/output/server/_app/immutable/assets/ConfirmModal.Dd3uP-Sk.css +5 -0
  200. package/frontend/.svelte-kit/output/server/_app/immutable/assets/EmptyState.DUDM4P19.css +5 -0
  201. package/frontend/.svelte-kit/output/server/_app/immutable/assets/Modal.BuaCfdLj.css +5 -0
  202. package/frontend/.svelte-kit/output/server/_app/immutable/assets/Pagination.DZmi2Po-.css +5 -0
  203. package/frontend/.svelte-kit/output/server/_app/immutable/assets/Toast.DENwYn3g.css +5 -0
  204. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_layout.BuhMvMUj.css +5 -0
  205. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.0Eln51hV.css +5 -0
  206. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.5IJAEAjc.css +5 -0
  207. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.BR3wgXcZ.css +5 -0
  208. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.BWAH0Beh.css +5 -0
  209. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.BZfe6nWe.css +5 -0
  210. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.Bj_a_5lv.css +5 -0
  211. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.BwgOqeMo.css +5 -0
  212. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.CBHi6pzT.css +5 -0
  213. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.CFnbmY-D.css +5 -0
  214. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.DvMkmYA-.css +5 -0
  215. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page._h2o67kg.css +5 -0
  216. package/frontend/.svelte-kit/output/server/chunks/BackLink.js +5 -0
  217. package/frontend/.svelte-kit/output/server/chunks/Badge.js +5 -0
  218. package/frontend/.svelte-kit/output/server/chunks/ConfirmModal.js +5 -0
  219. package/frontend/.svelte-kit/output/server/chunks/EmptyState.js +5 -0
  220. package/frontend/.svelte-kit/output/server/chunks/Modal.js +5 -0
  221. package/frontend/.svelte-kit/output/server/chunks/Toast.js +5 -0
  222. package/frontend/.svelte-kit/output/server/chunks/attributes.js +5 -0
  223. package/frontend/.svelte-kit/output/server/chunks/auth.js +5 -0
  224. package/frontend/.svelte-kit/output/server/chunks/auth2.js +5 -0
  225. package/frontend/.svelte-kit/output/server/chunks/client.js +5 -0
  226. package/frontend/.svelte-kit/output/server/chunks/common.js +5 -0
  227. package/frontend/.svelte-kit/output/server/chunks/constants.js +5 -0
  228. package/frontend/.svelte-kit/output/server/chunks/escaping.js +5 -0
  229. package/frontend/.svelte-kit/output/server/chunks/exports.js +5 -0
  230. package/frontend/.svelte-kit/output/server/chunks/format.js +5 -0
  231. package/frontend/.svelte-kit/output/server/chunks/index-server.js +5 -0
  232. package/frontend/.svelte-kit/output/server/chunks/index.js +5 -0
  233. package/frontend/.svelte-kit/output/server/chunks/index2.js +5 -0
  234. package/frontend/.svelte-kit/output/server/chunks/internal.js +5 -0
  235. package/frontend/.svelte-kit/output/server/chunks/reports.js +5 -0
  236. package/frontend/.svelte-kit/output/server/chunks/reports2.js +5 -0
  237. package/frontend/.svelte-kit/output/server/chunks/repository.js +5 -0
  238. package/frontend/.svelte-kit/output/server/chunks/runner.js +5 -0
  239. package/frontend/.svelte-kit/output/server/chunks/stores.js +5 -0
  240. package/frontend/.svelte-kit/output/server/chunks/theme.js +5 -0
  241. package/frontend/.svelte-kit/output/server/entries/fallbacks/error.svelte.js +5 -0
  242. package/frontend/.svelte-kit/output/server/entries/pages/_layout.js +5 -0
  243. package/frontend/.svelte-kit/output/server/entries/pages/_layout.svelte.js +5 -0
  244. package/frontend/.svelte-kit/output/server/entries/pages/_page.svelte.js +5 -0
  245. package/frontend/.svelte-kit/output/server/entries/pages/login/_page.svelte.js +5 -0
  246. package/frontend/.svelte-kit/output/server/entries/pages/reports/_id_/_page.svelte.js +5 -0
  247. package/frontend/.svelte-kit/output/server/entries/pages/reports/_page.svelte.js +5 -0
  248. package/frontend/.svelte-kit/output/server/entries/pages/reports/live/_page.svelte.js +5 -0
  249. package/frontend/.svelte-kit/output/server/entries/pages/scheduled-tests/_page.svelte.js +5 -0
  250. package/frontend/.svelte-kit/output/server/entries/pages/settings/_page.svelte.js +5 -0
  251. package/frontend/.svelte-kit/output/server/entries/pages/setup/_page.svelte.js +5 -0
  252. package/frontend/.svelte-kit/output/server/entries/pages/test-repository/_page.svelte.js +5 -0
  253. package/frontend/.svelte-kit/output/server/entries/pages/test-repository/runs/_id_/_page.svelte.js +5 -0
  254. package/frontend/.svelte-kit/output/server/entries/pages/test-repository/suites/_id_/_page.svelte.js +5 -0
  255. package/frontend/.svelte-kit/output/server/index.js +5 -0
  256. package/frontend/.svelte-kit/output/server/internal.js +5 -0
  257. package/frontend/.svelte-kit/output/server/manifest-full.js +5 -0
  258. package/frontend/.svelte-kit/output/server/manifest.js +5 -0
  259. package/frontend/.svelte-kit/output/server/nodes/0.js +5 -0
  260. package/frontend/.svelte-kit/output/server/nodes/1.js +5 -0
  261. package/frontend/.svelte-kit/output/server/nodes/10.js +5 -0
  262. package/frontend/.svelte-kit/output/server/nodes/11.js +5 -0
  263. package/frontend/.svelte-kit/output/server/nodes/12.js +5 -0
  264. package/frontend/.svelte-kit/output/server/nodes/2.js +5 -0
  265. package/frontend/.svelte-kit/output/server/nodes/3.js +5 -0
  266. package/frontend/.svelte-kit/output/server/nodes/4.js +5 -0
  267. package/frontend/.svelte-kit/output/server/nodes/5.js +5 -0
  268. package/frontend/.svelte-kit/output/server/nodes/6.js +5 -0
  269. package/frontend/.svelte-kit/output/server/nodes/7.js +5 -0
  270. package/frontend/.svelte-kit/output/server/nodes/8.js +5 -0
  271. package/frontend/.svelte-kit/output/server/nodes/9.js +5 -0
  272. package/frontend/build/client/_app/immutable/assets/0.BhSG-jZH.css +5 -0
  273. package/frontend/build/client/_app/immutable/assets/0.BhSG-jZH.css.br +0 -0
  274. package/frontend/build/client/_app/immutable/assets/0.BhSG-jZH.css.gz +0 -0
  275. package/frontend/build/client/_app/immutable/assets/10.CdaQUS6U.css +5 -0
  276. package/frontend/build/client/_app/immutable/assets/10.CdaQUS6U.css.br +0 -0
  277. package/frontend/build/client/_app/immutable/assets/10.CdaQUS6U.css.gz +0 -0
  278. package/frontend/build/client/_app/immutable/assets/11.BWAH0Beh.css +5 -0
  279. package/frontend/build/client/_app/immutable/assets/11.BWAH0Beh.css.br +0 -0
  280. package/frontend/build/client/_app/immutable/assets/11.BWAH0Beh.css.gz +0 -0
  281. package/frontend/build/client/_app/immutable/assets/12.Bj_a_5lv.css +5 -0
  282. package/frontend/build/client/_app/immutable/assets/12.Bj_a_5lv.css.br +0 -0
  283. package/frontend/build/client/_app/immutable/assets/12.Bj_a_5lv.css.gz +0 -0
  284. package/frontend/build/client/_app/immutable/assets/2.DvMkmYA-.css +5 -0
  285. package/frontend/build/client/_app/immutable/assets/2.DvMkmYA-.css.br +0 -0
  286. package/frontend/build/client/_app/immutable/assets/2.DvMkmYA-.css.gz +0 -0
  287. package/frontend/build/client/_app/immutable/assets/3.BR3wgXcZ.css +5 -0
  288. package/frontend/build/client/_app/immutable/assets/3.BR3wgXcZ.css.br +0 -0
  289. package/frontend/build/client/_app/immutable/assets/3.BR3wgXcZ.css.gz +0 -0
  290. package/frontend/build/client/_app/immutable/assets/4.0Eln51hV.css +5 -0
  291. package/frontend/build/client/_app/immutable/assets/4.0Eln51hV.css.br +0 -0
  292. package/frontend/build/client/_app/immutable/assets/4.0Eln51hV.css.gz +0 -0
  293. package/frontend/build/client/_app/immutable/assets/5.DFV9OgAu.css +5 -0
  294. package/frontend/build/client/_app/immutable/assets/5.DFV9OgAu.css.br +0 -0
  295. package/frontend/build/client/_app/immutable/assets/5.DFV9OgAu.css.gz +0 -0
  296. package/frontend/build/client/_app/immutable/assets/6.CFnbmY-D.css +5 -0
  297. package/frontend/build/client/_app/immutable/assets/6.CFnbmY-D.css.br +0 -0
  298. package/frontend/build/client/_app/immutable/assets/6.CFnbmY-D.css.gz +0 -0
  299. package/frontend/build/client/_app/immutable/assets/7.CBHi6pzT.css +5 -0
  300. package/frontend/build/client/_app/immutable/assets/7.CBHi6pzT.css.br +0 -0
  301. package/frontend/build/client/_app/immutable/assets/7.CBHi6pzT.css.gz +0 -0
  302. package/frontend/build/client/_app/immutable/assets/8.BwgOqeMo.css +5 -0
  303. package/frontend/build/client/_app/immutable/assets/8.BwgOqeMo.css.br +0 -0
  304. package/frontend/build/client/_app/immutable/assets/8.BwgOqeMo.css.gz +0 -0
  305. package/frontend/build/client/_app/immutable/assets/9._h2o67kg.css +5 -0
  306. package/frontend/build/client/_app/immutable/assets/9._h2o67kg.css.br +0 -0
  307. package/frontend/build/client/_app/immutable/assets/9._h2o67kg.css.gz +0 -0
  308. package/frontend/build/client/_app/immutable/assets/BackLink.CvWWuO3h.css +5 -0
  309. package/frontend/build/client/_app/immutable/assets/BackLink.CvWWuO3h.css.br +0 -0
  310. package/frontend/build/client/_app/immutable/assets/BackLink.CvWWuO3h.css.gz +0 -0
  311. package/frontend/build/client/_app/immutable/assets/Badge.D1SV0Gv7.css +5 -0
  312. package/frontend/build/client/_app/immutable/assets/Badge.D1SV0Gv7.css.br +0 -0
  313. package/frontend/build/client/_app/immutable/assets/Badge.D1SV0Gv7.css.gz +0 -0
  314. package/frontend/build/client/_app/immutable/assets/Button.FOWXxvc7.css +5 -0
  315. package/frontend/build/client/_app/immutable/assets/Button.FOWXxvc7.css.br +0 -0
  316. package/frontend/build/client/_app/immutable/assets/Button.FOWXxvc7.css.gz +0 -0
  317. package/frontend/build/client/_app/immutable/assets/ConfirmModal.Dd3uP-Sk.css +5 -0
  318. package/frontend/build/client/_app/immutable/assets/ConfirmModal.Dd3uP-Sk.css.br +0 -0
  319. package/frontend/build/client/_app/immutable/assets/ConfirmModal.Dd3uP-Sk.css.gz +0 -0
  320. package/frontend/build/client/_app/immutable/assets/EmptyState.DUDM4P19.css +5 -0
  321. package/frontend/build/client/_app/immutable/assets/EmptyState.DUDM4P19.css.br +0 -0
  322. package/frontend/build/client/_app/immutable/assets/EmptyState.DUDM4P19.css.gz +0 -0
  323. package/frontend/build/client/_app/immutable/assets/Modal.BZpm0HP8.css +5 -0
  324. package/frontend/build/client/_app/immutable/assets/Modal.BZpm0HP8.css.br +0 -0
  325. package/frontend/build/client/_app/immutable/assets/Modal.BZpm0HP8.css.gz +0 -0
  326. package/frontend/build/client/_app/immutable/assets/Modal.BuaCfdLj.css +5 -0
  327. package/frontend/build/client/_app/immutable/assets/Modal.BuaCfdLj.css.br +0 -0
  328. package/frontend/build/client/_app/immutable/assets/Modal.BuaCfdLj.css.gz +0 -0
  329. package/frontend/build/client/_app/immutable/assets/Pagination.DZmi2Po-.css +5 -0
  330. package/frontend/build/client/_app/immutable/assets/Pagination.DZmi2Po-.css.br +0 -0
  331. package/frontend/build/client/_app/immutable/assets/Pagination.DZmi2Po-.css.gz +0 -0
  332. package/frontend/build/client/_app/immutable/assets/Toast.DENwYn3g.css +5 -0
  333. package/frontend/build/client/_app/immutable/assets/Toast.DENwYn3g.css.br +0 -0
  334. package/frontend/build/client/_app/immutable/assets/Toast.DENwYn3g.css.gz +0 -0
  335. package/frontend/build/client/_app/immutable/assets/_layout.BuhMvMUj.css +5 -0
  336. package/frontend/build/client/_app/immutable/assets/_layout.BuhMvMUj.css.br +0 -0
  337. package/frontend/build/client/_app/immutable/assets/_layout.BuhMvMUj.css.gz +0 -0
  338. package/frontend/build/client/_app/immutable/assets/_page.0Eln51hV.css +5 -0
  339. package/frontend/build/client/_app/immutable/assets/_page.0Eln51hV.css.br +0 -0
  340. package/frontend/build/client/_app/immutable/assets/_page.0Eln51hV.css.gz +0 -0
  341. package/frontend/build/client/_app/immutable/assets/_page.5IJAEAjc.css +5 -0
  342. package/frontend/build/client/_app/immutable/assets/_page.5IJAEAjc.css.br +0 -0
  343. package/frontend/build/client/_app/immutable/assets/_page.5IJAEAjc.css.gz +0 -0
  344. package/frontend/build/client/_app/immutable/assets/_page.BR3wgXcZ.css +5 -0
  345. package/frontend/build/client/_app/immutable/assets/_page.BR3wgXcZ.css.br +0 -0
  346. package/frontend/build/client/_app/immutable/assets/_page.BR3wgXcZ.css.gz +0 -0
  347. package/frontend/build/client/_app/immutable/assets/_page.BWAH0Beh.css +5 -0
  348. package/frontend/build/client/_app/immutable/assets/_page.BWAH0Beh.css.br +0 -0
  349. package/frontend/build/client/_app/immutable/assets/_page.BWAH0Beh.css.gz +0 -0
  350. package/frontend/build/client/_app/immutable/assets/_page.BZfe6nWe.css +5 -0
  351. package/frontend/build/client/_app/immutable/assets/_page.BZfe6nWe.css.br +0 -0
  352. package/frontend/build/client/_app/immutable/assets/_page.BZfe6nWe.css.gz +0 -0
  353. package/frontend/build/client/_app/immutable/assets/_page.Bj_a_5lv.css +5 -0
  354. package/frontend/build/client/_app/immutable/assets/_page.Bj_a_5lv.css.br +0 -0
  355. package/frontend/build/client/_app/immutable/assets/_page.Bj_a_5lv.css.gz +0 -0
  356. package/frontend/build/client/_app/immutable/assets/_page.BwgOqeMo.css +5 -0
  357. package/frontend/build/client/_app/immutable/assets/_page.BwgOqeMo.css.br +0 -0
  358. package/frontend/build/client/_app/immutable/assets/_page.BwgOqeMo.css.gz +0 -0
  359. package/frontend/build/client/_app/immutable/assets/_page.CBHi6pzT.css +5 -0
  360. package/frontend/build/client/_app/immutable/assets/_page.CBHi6pzT.css.br +0 -0
  361. package/frontend/build/client/_app/immutable/assets/_page.CBHi6pzT.css.gz +0 -0
  362. package/frontend/build/client/_app/immutable/assets/_page.CFnbmY-D.css +5 -0
  363. package/frontend/build/client/_app/immutable/assets/_page.CFnbmY-D.css.br +0 -0
  364. package/frontend/build/client/_app/immutable/assets/_page.CFnbmY-D.css.gz +0 -0
  365. package/frontend/build/client/_app/immutable/assets/_page.DvMkmYA-.css +5 -0
  366. package/frontend/build/client/_app/immutable/assets/_page.DvMkmYA-.css.br +0 -0
  367. package/frontend/build/client/_app/immutable/assets/_page.DvMkmYA-.css.gz +0 -0
  368. package/frontend/build/client/_app/immutable/assets/_page._h2o67kg.css +5 -0
  369. package/frontend/build/client/_app/immutable/assets/_page._h2o67kg.css.br +0 -0
  370. package/frontend/build/client/_app/immutable/assets/_page._h2o67kg.css.gz +0 -0
  371. package/frontend/build/client/_app/immutable/chunks/2zMvi6fR.js +5 -0
  372. package/frontend/build/client/_app/immutable/chunks/2zMvi6fR.js.br +0 -0
  373. package/frontend/build/client/_app/immutable/chunks/2zMvi6fR.js.gz +0 -0
  374. package/frontend/build/client/_app/immutable/chunks/3InFPLvE.js +5 -0
  375. package/frontend/build/client/_app/immutable/chunks/3InFPLvE.js.br +0 -0
  376. package/frontend/build/client/_app/immutable/chunks/3InFPLvE.js.gz +0 -0
  377. package/frontend/build/client/_app/immutable/chunks/BAACMRH5.js +5 -0
  378. package/frontend/build/client/_app/immutable/chunks/BAACMRH5.js.br +0 -0
  379. package/frontend/build/client/_app/immutable/chunks/BAACMRH5.js.gz +0 -0
  380. package/frontend/build/client/_app/immutable/chunks/BAMUGSh7.js +5 -0
  381. package/frontend/build/client/_app/immutable/chunks/BAMUGSh7.js.br +0 -0
  382. package/frontend/build/client/_app/immutable/chunks/BAMUGSh7.js.gz +0 -0
  383. package/frontend/build/client/_app/immutable/chunks/BKuh57WU.js +5 -0
  384. package/frontend/build/client/_app/immutable/chunks/BKuh57WU.js.br +0 -0
  385. package/frontend/build/client/_app/immutable/chunks/BKuh57WU.js.gz +0 -0
  386. package/frontend/build/client/_app/immutable/chunks/BUwxRmHf.js +5 -0
  387. package/frontend/build/client/_app/immutable/chunks/BUwxRmHf.js.br +0 -0
  388. package/frontend/build/client/_app/immutable/chunks/BUwxRmHf.js.gz +0 -0
  389. package/frontend/build/client/_app/immutable/chunks/Bfc47y5P.js +5 -0
  390. package/frontend/build/client/_app/immutable/chunks/Bfc47y5P.js.br +0 -0
  391. package/frontend/build/client/_app/immutable/chunks/Bfc47y5P.js.gz +0 -0
  392. package/frontend/build/client/_app/immutable/chunks/BmZ9upwL.js +5 -0
  393. package/frontend/build/client/_app/immutable/chunks/BmZ9upwL.js.br +0 -0
  394. package/frontend/build/client/_app/immutable/chunks/BmZ9upwL.js.gz +0 -0
  395. package/frontend/build/client/_app/immutable/chunks/C1Vid6wz.js +5 -0
  396. package/frontend/build/client/_app/immutable/chunks/C1Vid6wz.js.br +0 -0
  397. package/frontend/build/client/_app/immutable/chunks/C1Vid6wz.js.gz +0 -0
  398. package/frontend/build/client/_app/immutable/chunks/C8OURRSs.js +5 -0
  399. package/frontend/build/client/_app/immutable/chunks/C8OURRSs.js.br +0 -0
  400. package/frontend/build/client/_app/immutable/chunks/C8OURRSs.js.gz +0 -0
  401. package/frontend/build/client/_app/immutable/chunks/CGeZlCaI.js +5 -0
  402. package/frontend/build/client/_app/immutable/chunks/CGeZlCaI.js.br +0 -0
  403. package/frontend/build/client/_app/immutable/chunks/CGeZlCaI.js.gz +0 -0
  404. package/frontend/build/client/_app/immutable/chunks/CKDtmZUQ.js +5 -0
  405. package/frontend/build/client/_app/immutable/chunks/CKDtmZUQ.js.br +0 -0
  406. package/frontend/build/client/_app/immutable/chunks/CKDtmZUQ.js.gz +0 -0
  407. package/frontend/build/client/_app/immutable/chunks/CMBQQ9RZ.js +5 -0
  408. package/frontend/build/client/_app/immutable/chunks/CMBQQ9RZ.js.br +0 -0
  409. package/frontend/build/client/_app/immutable/chunks/CMBQQ9RZ.js.gz +0 -0
  410. package/frontend/build/client/_app/immutable/chunks/CMEtqUfQ.js +5 -0
  411. package/frontend/build/client/_app/immutable/chunks/CMEtqUfQ.js.br +0 -0
  412. package/frontend/build/client/_app/immutable/chunks/CMEtqUfQ.js.gz +0 -0
  413. package/frontend/build/client/_app/immutable/chunks/CNdcuGR0.js +5 -0
  414. package/frontend/build/client/_app/immutable/chunks/CNdcuGR0.js.br +0 -0
  415. package/frontend/build/client/_app/immutable/chunks/CNdcuGR0.js.gz +0 -0
  416. package/frontend/build/client/_app/immutable/chunks/CUC98kMZ.js +5 -0
  417. package/frontend/build/client/_app/immutable/chunks/CUC98kMZ.js.br +0 -0
  418. package/frontend/build/client/_app/immutable/chunks/CUC98kMZ.js.gz +0 -0
  419. package/frontend/build/client/_app/immutable/chunks/CgnwuKKO.js +5 -0
  420. package/frontend/build/client/_app/immutable/chunks/CgnwuKKO.js.br +0 -0
  421. package/frontend/build/client/_app/immutable/chunks/CgnwuKKO.js.gz +0 -0
  422. package/frontend/build/client/_app/immutable/chunks/Co3xKhEp.js +5 -0
  423. package/frontend/build/client/_app/immutable/chunks/Co3xKhEp.js.br +0 -0
  424. package/frontend/build/client/_app/immutable/chunks/Co3xKhEp.js.gz +0 -0
  425. package/frontend/build/client/_app/immutable/chunks/CuqnZUex.js +5 -0
  426. package/frontend/build/client/_app/immutable/chunks/CuqnZUex.js.br +5 -1
  427. package/frontend/build/client/_app/immutable/chunks/CuqnZUex.js.gz +0 -0
  428. package/frontend/build/client/_app/immutable/chunks/CzlPaHY9.js +5 -0
  429. package/frontend/build/client/_app/immutable/chunks/CzlPaHY9.js.br +0 -0
  430. package/frontend/build/client/_app/immutable/chunks/CzlPaHY9.js.gz +0 -0
  431. package/frontend/build/client/_app/immutable/chunks/DCgHuuvs.js +5 -0
  432. package/frontend/build/client/_app/immutable/chunks/DCgHuuvs.js.br +0 -0
  433. package/frontend/build/client/_app/immutable/chunks/DCgHuuvs.js.gz +0 -0
  434. package/frontend/build/client/_app/immutable/chunks/DEKAA-Fm.js +5 -0
  435. package/frontend/build/client/_app/immutable/chunks/DEKAA-Fm.js.br +0 -0
  436. package/frontend/build/client/_app/immutable/chunks/DEKAA-Fm.js.gz +0 -0
  437. package/frontend/build/client/_app/immutable/chunks/DHM6H-es.js +5 -0
  438. package/frontend/build/client/_app/immutable/chunks/DHM6H-es.js.br +0 -0
  439. package/frontend/build/client/_app/immutable/chunks/DHM6H-es.js.gz +0 -0
  440. package/frontend/build/client/_app/immutable/chunks/DMZwI8Cj.js +5 -0
  441. package/frontend/build/client/_app/immutable/chunks/DMZwI8Cj.js.br +0 -0
  442. package/frontend/build/client/_app/immutable/chunks/DMZwI8Cj.js.gz +0 -0
  443. package/frontend/build/client/_app/immutable/chunks/DSLBMEpr.js +5 -0
  444. package/frontend/build/client/_app/immutable/chunks/DSLBMEpr.js.br +0 -0
  445. package/frontend/build/client/_app/immutable/chunks/DSLBMEpr.js.gz +0 -0
  446. package/frontend/build/client/_app/immutable/chunks/DbgD7JLa.js +5 -0
  447. package/frontend/build/client/_app/immutable/chunks/DbgD7JLa.js.br +0 -0
  448. package/frontend/build/client/_app/immutable/chunks/DbgD7JLa.js.gz +0 -0
  449. package/frontend/build/client/_app/immutable/chunks/Dhf4xQKo.js +5 -0
  450. package/frontend/build/client/_app/immutable/chunks/Dhf4xQKo.js.br +0 -0
  451. package/frontend/build/client/_app/immutable/chunks/Dhf4xQKo.js.gz +0 -0
  452. package/frontend/build/client/_app/immutable/chunks/Dlnxgs49.js +5 -0
  453. package/frontend/build/client/_app/immutable/chunks/Dlnxgs49.js.br +0 -0
  454. package/frontend/build/client/_app/immutable/chunks/Dlnxgs49.js.gz +0 -0
  455. package/frontend/build/client/_app/immutable/chunks/TOCyHP97.js +5 -0
  456. package/frontend/build/client/_app/immutable/chunks/TOCyHP97.js.br +0 -0
  457. package/frontend/build/client/_app/immutable/chunks/TOCyHP97.js.gz +0 -0
  458. package/frontend/build/client/_app/immutable/chunks/t-La0hgu.js +5 -0
  459. package/frontend/build/client/_app/immutable/chunks/t-La0hgu.js.br +0 -0
  460. package/frontend/build/client/_app/immutable/chunks/t-La0hgu.js.gz +0 -0
  461. package/frontend/build/client/_app/immutable/chunks/w0D4Uzlq.js +5 -0
  462. package/frontend/build/client/_app/immutable/chunks/w0D4Uzlq.js.br +0 -0
  463. package/frontend/build/client/_app/immutable/chunks/w0D4Uzlq.js.gz +0 -0
  464. package/frontend/build/client/_app/immutable/entry/app.B0GH5ONo.js +5 -0
  465. package/frontend/build/client/_app/immutable/entry/app.B0GH5ONo.js.br +0 -0
  466. package/frontend/build/client/_app/immutable/entry/app.B0GH5ONo.js.gz +0 -0
  467. package/frontend/build/client/_app/immutable/entry/start.Bn25CW9X.js +5 -0
  468. package/frontend/build/client/_app/immutable/entry/start.Bn25CW9X.js.br +0 -0
  469. package/frontend/build/client/_app/immutable/entry/start.Bn25CW9X.js.gz +0 -0
  470. package/frontend/build/client/_app/immutable/nodes/0.-imXt0f5.js +5 -0
  471. package/frontend/build/client/_app/immutable/nodes/0.-imXt0f5.js.br +0 -0
  472. package/frontend/build/client/_app/immutable/nodes/0.-imXt0f5.js.gz +0 -0
  473. package/frontend/build/client/_app/immutable/nodes/1.Dom3CKWF.js +5 -0
  474. package/frontend/build/client/_app/immutable/nodes/1.Dom3CKWF.js.br +0 -0
  475. package/frontend/build/client/_app/immutable/nodes/1.Dom3CKWF.js.gz +0 -0
  476. package/frontend/build/client/_app/immutable/nodes/10.DEbnxfND.js +5 -0
  477. package/frontend/build/client/_app/immutable/nodes/10.DEbnxfND.js.br +0 -0
  478. package/frontend/build/client/_app/immutable/nodes/10.DEbnxfND.js.gz +0 -0
  479. package/frontend/build/client/_app/immutable/nodes/11.DMlayLCK.js +5 -0
  480. package/frontend/build/client/_app/immutable/nodes/11.DMlayLCK.js.br +0 -0
  481. package/frontend/build/client/_app/immutable/nodes/11.DMlayLCK.js.gz +0 -0
  482. package/frontend/build/client/_app/immutable/nodes/12.Bu89A1eX.js +5 -0
  483. package/frontend/build/client/_app/immutable/nodes/12.Bu89A1eX.js.br +0 -0
  484. package/frontend/build/client/_app/immutable/nodes/12.Bu89A1eX.js.gz +0 -0
  485. package/frontend/build/client/_app/immutable/nodes/2.BnHalSCi.js +5 -0
  486. package/frontend/build/client/_app/immutable/nodes/2.BnHalSCi.js.br +0 -0
  487. package/frontend/build/client/_app/immutable/nodes/2.BnHalSCi.js.gz +0 -0
  488. package/frontend/build/client/_app/immutable/nodes/3.B86rlHXU.js +5 -0
  489. package/frontend/build/client/_app/immutable/nodes/3.B86rlHXU.js.br +0 -0
  490. package/frontend/build/client/_app/immutable/nodes/3.B86rlHXU.js.gz +0 -0
  491. package/frontend/build/client/_app/immutable/nodes/4.D-YT_JJe.js +5 -0
  492. package/frontend/build/client/_app/immutable/nodes/4.D-YT_JJe.js.br +0 -0
  493. package/frontend/build/client/_app/immutable/nodes/4.D-YT_JJe.js.gz +0 -0
  494. package/frontend/build/client/_app/immutable/nodes/5.CqTXvadV.js +5 -0
  495. package/frontend/build/client/_app/immutable/nodes/5.CqTXvadV.js.br +0 -0
  496. package/frontend/build/client/_app/immutable/nodes/5.CqTXvadV.js.gz +0 -0
  497. package/frontend/build/client/_app/immutable/nodes/6.BJZ62MCd.js +5 -0
  498. package/frontend/build/client/_app/immutable/nodes/6.BJZ62MCd.js.br +0 -0
  499. package/frontend/build/client/_app/immutable/nodes/6.BJZ62MCd.js.gz +0 -0
  500. package/frontend/build/client/_app/immutable/nodes/7.C-pKvVZy.js +5 -0
  501. package/frontend/build/client/_app/immutable/nodes/7.C-pKvVZy.js.br +0 -0
  502. package/frontend/build/client/_app/immutable/nodes/7.C-pKvVZy.js.gz +0 -0
  503. package/frontend/build/client/_app/immutable/nodes/8.B4fj2Xyu.js +5 -0
  504. package/frontend/build/client/_app/immutable/nodes/8.B4fj2Xyu.js.br +0 -0
  505. package/frontend/build/client/_app/immutable/nodes/8.B4fj2Xyu.js.gz +0 -0
  506. package/frontend/build/client/_app/immutable/nodes/9.C57_2xq6.js +5 -0
  507. package/frontend/build/client/_app/immutable/nodes/9.C57_2xq6.js.br +0 -0
  508. package/frontend/build/client/_app/immutable/nodes/9.C57_2xq6.js.gz +0 -0
  509. package/frontend/build/client/_app/version.json.br +0 -0
  510. package/frontend/build/client/_app/version.json.gz +0 -0
  511. package/frontend/build/env.js +5 -0
  512. package/frontend/build/handler.js +5 -0
  513. package/frontend/build/index.js +5 -0
  514. package/frontend/build/server/chunks/0-DG1Uv07a.js +5 -0
  515. package/frontend/build/server/chunks/0-DG1Uv07a.js.map +4 -0
  516. package/frontend/build/server/chunks/1-iB0ogJU_.js +5 -0
  517. package/frontend/build/server/chunks/1-iB0ogJU_.js.map +4 -0
  518. package/frontend/build/server/chunks/10-l6_ikL_D.js +5 -0
  519. package/frontend/build/server/chunks/10-l6_ikL_D.js.map +4 -0
  520. package/frontend/build/server/chunks/11-Df9vGZ_v.js +5 -0
  521. package/frontend/build/server/chunks/11-Df9vGZ_v.js.map +4 -0
  522. package/frontend/build/server/chunks/12-DqvjCln4.js +5 -0
  523. package/frontend/build/server/chunks/12-DqvjCln4.js.map +4 -0
  524. package/frontend/build/server/chunks/2-Cte30Dgy.js +5 -0
  525. package/frontend/build/server/chunks/2-Cte30Dgy.js.map +4 -0
  526. package/frontend/build/server/chunks/3-BYKCZi4U.js +5 -0
  527. package/frontend/build/server/chunks/3-BYKCZi4U.js.map +4 -0
  528. package/frontend/build/server/chunks/4-Bo04DC5Z.js +5 -0
  529. package/frontend/build/server/chunks/4-Bo04DC5Z.js.map +4 -0
  530. package/frontend/build/server/chunks/5-B6Jc1foX.js +5 -0
  531. package/frontend/build/server/chunks/5-B6Jc1foX.js.map +4 -0
  532. package/frontend/build/server/chunks/6-dXZKtN86.js +5 -0
  533. package/frontend/build/server/chunks/6-dXZKtN86.js.map +4 -0
  534. package/frontend/build/server/chunks/7-DiQJZEF_.js +5 -0
  535. package/frontend/build/server/chunks/7-DiQJZEF_.js.map +4 -0
  536. package/frontend/build/server/chunks/8-D-Bp7HzS.js +5 -0
  537. package/frontend/build/server/chunks/8-D-Bp7HzS.js.map +4 -0
  538. package/frontend/build/server/chunks/9-BynjuGUO.js +5 -0
  539. package/frontend/build/server/chunks/9-BynjuGUO.js.map +4 -0
  540. package/frontend/build/server/chunks/BackLink-BJ8NaemM.js +5 -0
  541. package/frontend/build/server/chunks/BackLink-BJ8NaemM.js.map +4 -0
  542. package/frontend/build/server/chunks/Badge-44ZTDx0V.js +5 -0
  543. package/frontend/build/server/chunks/Badge-44ZTDx0V.js.map +4 -0
  544. package/frontend/build/server/chunks/ConfirmModal-C0EAXcq_.js +5 -0
  545. package/frontend/build/server/chunks/ConfirmModal-C0EAXcq_.js.map +4 -0
  546. package/frontend/build/server/chunks/EmptyState-BA_eXaeR.js +5 -0
  547. package/frontend/build/server/chunks/EmptyState-BA_eXaeR.js.map +4 -0
  548. package/frontend/build/server/chunks/Modal-DChG187n.js +5 -0
  549. package/frontend/build/server/chunks/Modal-DChG187n.js.map +4 -0
  550. package/frontend/build/server/chunks/Toast-v-q4ZnYq.js +5 -0
  551. package/frontend/build/server/chunks/Toast-v-q4ZnYq.js.map +4 -0
  552. package/frontend/build/server/chunks/_layout.svelte-CHVI3Prt.js +5 -0
  553. package/frontend/build/server/chunks/_layout.svelte-CHVI3Prt.js.map +4 -0
  554. package/frontend/build/server/chunks/_page.svelte-1as75nPc.js +5 -0
  555. package/frontend/build/server/chunks/_page.svelte-1as75nPc.js.map +4 -0
  556. package/frontend/build/server/chunks/_page.svelte-1jo8MapI.js +5 -0
  557. package/frontend/build/server/chunks/_page.svelte-1jo8MapI.js.map +4 -0
  558. package/frontend/build/server/chunks/_page.svelte-BuFusRK1.js +5 -0
  559. package/frontend/build/server/chunks/_page.svelte-BuFusRK1.js.map +4 -0
  560. package/frontend/build/server/chunks/_page.svelte-CAVyp70V.js +5 -0
  561. package/frontend/build/server/chunks/_page.svelte-CAVyp70V.js.map +4 -0
  562. package/frontend/build/server/chunks/_page.svelte-CB8-QBNg.js +5 -0
  563. package/frontend/build/server/chunks/_page.svelte-CB8-QBNg.js.map +4 -0
  564. package/frontend/build/server/chunks/_page.svelte-CWWeZuid.js +5 -0
  565. package/frontend/build/server/chunks/_page.svelte-CWWeZuid.js.map +4 -0
  566. package/frontend/build/server/chunks/_page.svelte-CpB-fgJl.js +5 -0
  567. package/frontend/build/server/chunks/_page.svelte-CpB-fgJl.js.map +4 -0
  568. package/frontend/build/server/chunks/_page.svelte-D3E6Eojd.js +5 -0
  569. package/frontend/build/server/chunks/_page.svelte-D3E6Eojd.js.map +4 -0
  570. package/frontend/build/server/chunks/_page.svelte-Kg69DDaM.js +5 -0
  571. package/frontend/build/server/chunks/_page.svelte-Kg69DDaM.js.map +4 -0
  572. package/frontend/build/server/chunks/_page.svelte-L9fuiN_q.js +5 -0
  573. package/frontend/build/server/chunks/_page.svelte-L9fuiN_q.js.map +4 -0
  574. package/frontend/build/server/chunks/_page.svelte-wKyUmA2e.js +5 -0
  575. package/frontend/build/server/chunks/_page.svelte-wKyUmA2e.js.map +4 -0
  576. package/frontend/build/server/chunks/attributes-BeaNKpgU.js +5 -0
  577. package/frontend/build/server/chunks/attributes-BeaNKpgU.js.map +4 -0
  578. package/frontend/build/server/chunks/auth-Chw-wBpI.js +5 -0
  579. package/frontend/build/server/chunks/auth-Chw-wBpI.js.map +4 -0
  580. package/frontend/build/server/chunks/auth2-C02yYDpz.js +5 -0
  581. package/frontend/build/server/chunks/auth2-C02yYDpz.js.map +4 -0
  582. package/frontend/build/server/chunks/client-DEmxR_a-.js +5 -0
  583. package/frontend/build/server/chunks/client-DEmxR_a-.js.map +4 -0
  584. package/frontend/build/server/chunks/common-DYYTVHPw.js +5 -0
  585. package/frontend/build/server/chunks/common-DYYTVHPw.js.map +4 -0
  586. package/frontend/build/server/chunks/constants-CXTaQaB6.js +5 -0
  587. package/frontend/build/server/chunks/constants-CXTaQaB6.js.map +4 -0
  588. package/frontend/build/server/chunks/error.svelte-BD3xNemk.js +5 -0
  589. package/frontend/build/server/chunks/error.svelte-BD3xNemk.js.map +4 -0
  590. package/frontend/build/server/chunks/escaping-CqgfEcN3.js +5 -0
  591. package/frontend/build/server/chunks/escaping-CqgfEcN3.js.map +4 -0
  592. package/frontend/build/server/chunks/exports-D0QHgeSL.js +5 -0
  593. package/frontend/build/server/chunks/exports-D0QHgeSL.js.map +4 -0
  594. package/frontend/build/server/chunks/format-CiNRMfHu.js +5 -0
  595. package/frontend/build/server/chunks/format-CiNRMfHu.js.map +4 -0
  596. package/frontend/build/server/chunks/index-server-B3M9vyh2.js +5 -0
  597. package/frontend/build/server/chunks/index-server-B3M9vyh2.js.map +4 -0
  598. package/frontend/build/server/chunks/index2-5R1tbrep.js +5 -0
  599. package/frontend/build/server/chunks/index2-5R1tbrep.js.map +4 -0
  600. package/frontend/build/server/chunks/reports-D_ApeeNh.js +5 -0
  601. package/frontend/build/server/chunks/reports-D_ApeeNh.js.map +4 -0
  602. package/frontend/build/server/chunks/reports2-BtElN_53.js +5 -0
  603. package/frontend/build/server/chunks/reports2-BtElN_53.js.map +4 -0
  604. package/frontend/build/server/chunks/repository-FYYV_vKE.js +5 -0
  605. package/frontend/build/server/chunks/repository-FYYV_vKE.js.map +4 -0
  606. package/frontend/build/server/chunks/runner-DC7hXbnY.js +5 -0
  607. package/frontend/build/server/chunks/runner-DC7hXbnY.js.map +4 -0
  608. package/frontend/build/server/chunks/stores-BLenR3c1.js +5 -0
  609. package/frontend/build/server/chunks/stores-BLenR3c1.js.map +4 -0
  610. package/frontend/build/server/chunks/theme-nAx-GvX_.js +5 -0
  611. package/frontend/build/server/chunks/theme-nAx-GvX_.js.map +4 -0
  612. package/frontend/build/server/index.js +5 -0
  613. package/frontend/build/server/index.js.map +4 -0
  614. package/frontend/build/server/manifest.js +5 -0
  615. package/frontend/build/server/manifest.js.map +4 -0
  616. package/frontend/build/shims.js +5 -0
  617. package/frontend/src/routes/settings/+page.svelte +4 -5
  618. package/package.json +1 -1
@@ -6,13 +6,13 @@
6
6
  /**
7
7
  * Plum MCP Server
8
8
  *
9
- * Exposes Plum's Test Repository and test runner to Claude over stdio.
10
- *
11
- * Configuration (env vars):
12
- * PLUM_API_URL — Plum backend base URL (default: http://localhost:3001)
13
- * PLUM_API_KEY — Must match PLUM_MCP_KEY set in backend/.env
9
+ * Exposes Plum's Test Repository and test runner to any MCP-compatible AI client
10
+ * over Streamable HTTP (see routes/mcp.routes.js for the transport wiring). Tool
11
+ * handlers call the same services the REST routes use directly — this module
12
+ * runs in-process inside the Plum backend, not as a separate subprocess.
14
13
  */
15
14
 
15
+ const fs = require('fs');
16
16
  const path = require('path');
17
17
 
18
18
  // Use absolute paths to bypass the SDK's wildcard export mapping
@@ -26,38 +26,14 @@ const sdkCjs = path.resolve(
26
26
  'cjs'
27
27
  );
28
28
  const { McpServer } = require(path.join(sdkCjs, 'server', 'mcp.js'));
29
- const { StdioServerTransport } = require(path.join(sdkCjs, 'server', 'stdio.js'));
30
29
  const { z } = require('zod');
31
- const { AUTH_SCHEME } = require('../lib/authHeader');
30
+ const { TRIGGER_TYPE } = require('../constants/triggers');
32
31
  const { JOB_STATUS } = require('../constants/jobStatus');
33
-
34
- const API_URL = (process.env.PLUM_API_URL || 'http://localhost:3001').replace(/\/$/, '');
35
- const API_KEY = process.env.PLUM_API_KEY || '';
36
-
37
- // ---------------------------------------------------------------------------
38
- // HTTP helpers
39
- // ---------------------------------------------------------------------------
40
-
41
- async function api(method, path, body) {
42
- const res = await fetch(`${API_URL}${path}`, {
43
- method,
44
- headers: {
45
- 'Content-Type': 'application/json',
46
- Authorization: `${AUTH_SCHEME.API_KEY} ${API_KEY}`
47
- },
48
- body: body !== undefined ? JSON.stringify(body) : undefined
49
- });
50
- const json = await res.json().catch(() => ({}));
51
- if (!res.ok) {
52
- throw new Error(json.error || `HTTP ${res.status} ${method} ${path}`);
53
- }
54
- return json;
55
- }
56
-
57
- const get = (path) => api('GET', path);
58
- const post = (path, body) => api('POST', path, body);
59
- const put = (path, body) => api('PUT', path, body);
60
- const del = (path) => api('DELETE', path);
32
+ const { SCREENSHOTS_DIR } = require('../lib/reportFilename');
33
+ const testSuiteService = require('../services/testSuiteService');
34
+ const testCaseService = require('../services/testCaseService');
35
+ const triggerService = require('../services/triggerService');
36
+ const reportService = require('../services/reportService');
61
37
 
62
38
  // ---------------------------------------------------------------------------
63
39
  // Polling helper for test runs
@@ -67,7 +43,7 @@ async function pollJob(jobId, { maxMs = 600_000, intervalMs = 5_000 } = {}) {
67
43
  const deadline = Date.now() + maxMs;
68
44
  while (Date.now() < deadline) {
69
45
  await new Promise((r) => setTimeout(r, intervalMs));
70
- const job = await get(`/trigger/${jobId}`);
46
+ const job = triggerService.getJob(jobId);
71
47
  if (job.status !== JOB_STATUS.RUNNING) return job;
72
48
  }
73
49
  return { status: 'timeout', jobId };
@@ -78,8 +54,6 @@ async function pollJob(jobId, { maxMs = 600_000, intervalMs = 5_000 } = {}) {
78
54
  // ---------------------------------------------------------------------------
79
55
 
80
56
  function summariseReport(report) {
81
- // GET /reports/:id hoists content.features to a top-level `features` key
82
- // and strips `content` entirely — see reportService.getReportDetail.
83
57
  const features = report.features ?? report.content?.features ?? [];
84
58
  const allScenarios = features.flatMap((f) => f.scenarios ?? []);
85
59
  const total = allScenarios.length;
@@ -114,447 +88,469 @@ function summariseReport(report) {
114
88
  // ---------------------------------------------------------------------------
115
89
 
116
90
  const SCREENSHOT_FILENAME_RE = /^[\w.-]+\.(png|jpg|jpeg)$/i;
117
-
118
- function screenshotUrl(filename) {
119
- return `${API_URL}/screenshots/${filename}`;
120
- }
121
-
122
91
  const SCREENSHOT_MIME_TYPES = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg' };
123
92
 
124
93
  // ---------------------------------------------------------------------------
125
94
  // Server setup
126
95
  // ---------------------------------------------------------------------------
127
96
 
128
- const server = new McpServer({
129
- name: 'plum',
130
- version: '1.0.0'
131
- });
132
-
133
- // -- Test Repository: Suites -------------------------------------------------
134
-
135
- server.tool(
136
- 'list_test_suites',
137
- 'List all test suites in the Plum Test Repository.',
138
- {
139
- page: z.number().int().positive().optional().describe('Page number (default 1)'),
140
- limit: z.number().int().positive().max(100).optional().describe('Results per page (default 20)')
141
- },
142
- async ({ page = 1, limit = 20 }) => {
143
- const data = await get(`/test-suites?page=${page}&limit=${limit}`);
144
- return {
145
- content: [
146
- {
147
- type: 'text',
148
- text: JSON.stringify(data, null, 2)
149
- }
150
- ]
151
- };
152
- }
153
- );
97
+ /**
98
+ * Builds a fresh McpServer with all Plum tools registered, closing over the
99
+ * authenticated user for this request (see routes/mcp.routes.js — a new
100
+ * server/transport pair is created per request in stateless mode).
101
+ */
102
+ function createMcpServer({ userId }) {
103
+ const server = new McpServer({
104
+ name: 'plum',
105
+ version: '1.0.0'
106
+ });
154
107
 
155
- server.tool(
156
- 'get_test_suite',
157
- 'Get a test suite by ID, including all its test cases.',
158
- {
159
- suiteId: z.string().describe('The suite ID (e.g. the database UUID, not the displayId)')
160
- },
161
- async ({ suiteId }) => {
162
- const data = await get(`/test-suites/${suiteId}`);
163
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
164
- }
165
- );
108
+ // -- Test Repository: Suites -----------------------------------------------
109
+
110
+ server.tool(
111
+ 'list_test_suites',
112
+ 'List all test suites in the Plum Test Repository.',
113
+ {
114
+ page: z.number().int().positive().optional().describe('Page number (default 1)'),
115
+ limit: z
116
+ .number()
117
+ .int()
118
+ .positive()
119
+ .max(100)
120
+ .optional()
121
+ .describe('Results per page (default 20)')
122
+ },
123
+ async ({ page = 1, limit = 20 }) => {
124
+ const data = await testSuiteService.getAll({ page, limit });
125
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
126
+ }
127
+ );
166
128
 
167
- server.tool(
168
- 'create_test_suite',
169
- 'Create a new test suite in the Plum Test Repository.',
170
- {
171
- name: z.string().min(1).describe('Suite name, e.g. "User Authentication"'),
172
- description: z.string().optional().describe('What this suite covers'),
173
- priority: z
174
- .enum(['Critical', 'High', 'Medium', 'Low'])
175
- .optional()
176
- .describe('Suite priority (default Medium)')
177
- },
178
- async ({ name, description, priority }) => {
179
- const data = await post('/test-suites', { name, description, priority });
180
- return { content: [{ type: 'text', text: JSON.stringify(data.suite, null, 2) }] };
181
- }
182
- );
129
+ server.tool(
130
+ 'get_test_suite',
131
+ 'Get a test suite by ID, including all its test cases.',
132
+ {
133
+ suiteId: z.string().describe('The suite ID (e.g. the database UUID, not the displayId)')
134
+ },
135
+ async ({ suiteId }) => {
136
+ const suite = await testSuiteService.getById(suiteId);
137
+ if (!suite) throw new Error('Suite not found');
138
+ return { content: [{ type: 'text', text: JSON.stringify(suite, null, 2) }] };
139
+ }
140
+ );
141
+
142
+ server.tool(
143
+ 'create_test_suite',
144
+ 'Create a new test suite in the Plum Test Repository.',
145
+ {
146
+ name: z.string().min(1).describe('Suite name, e.g. "User Authentication"'),
147
+ description: z.string().optional().describe('What this suite covers'),
148
+ priority: z
149
+ .enum(['Critical', 'High', 'Medium', 'Low'])
150
+ .optional()
151
+ .describe('Suite priority (default Medium)')
152
+ },
153
+ async ({ name, description, priority }) => {
154
+ const suite = await testSuiteService.create({
155
+ name,
156
+ description,
157
+ priority,
158
+ createdById: userId
159
+ });
160
+ return { content: [{ type: 'text', text: JSON.stringify(suite, null, 2) }] };
161
+ }
162
+ );
163
+
164
+ server.tool(
165
+ 'update_test_suite',
166
+ "Update a test suite's name, description, or priority. Only the fields provided are changed.",
167
+ {
168
+ suiteId: z.string().describe('UUID of the suite to update'),
169
+ name: z.string().min(1).optional(),
170
+ description: z.string().optional(),
171
+ priority: z.enum(['Critical', 'High', 'Medium', 'Low']).optional()
172
+ },
173
+ async ({ suiteId, name, description, priority }) => {
174
+ const suite = await testSuiteService.update(suiteId, { name, description, priority });
175
+ return { content: [{ type: 'text', text: JSON.stringify(suite, null, 2) }] };
176
+ }
177
+ );
183
178
 
184
- server.tool(
185
- 'update_test_suite',
186
- "Update a test suite's name, description, or priority. Only the fields provided are changed.",
187
- {
188
- suiteId: z.string().describe('UUID of the suite to update'),
189
- name: z.string().min(1).optional(),
190
- description: z.string().optional(),
191
- priority: z.enum(['Critical', 'High', 'Medium', 'Low']).optional()
192
- },
193
- async ({ suiteId, name, description, priority }) => {
194
- const data = await put(`/test-suites/${suiteId}`, { name, description, priority });
195
- return { content: [{ type: 'text', text: JSON.stringify(data.suite, null, 2) }] };
196
- }
197
- );
179
+ server.tool(
180
+ 'delete_test_suite',
181
+ 'Permanently delete a test suite and all of its test cases. This cannot be undone.',
182
+ {
183
+ suiteId: z.string().describe('UUID of the suite to delete')
184
+ },
185
+ async ({ suiteId }) => {
186
+ await testSuiteService.remove(suiteId);
187
+ return { content: [{ type: 'text', text: `Suite ${suiteId} deleted.` }] };
188
+ }
189
+ );
190
+
191
+ // -- Test Repository: Cases -------------------------------------------------
192
+
193
+ server.tool(
194
+ 'create_test_case',
195
+ 'Create a test case inside an existing test suite.',
196
+ {
197
+ suiteId: z.string().describe('UUID of the parent test suite'),
198
+ title: z
199
+ .string()
200
+ .min(1)
201
+ .describe('Case title, e.g. "User can log in with valid credentials"'),
202
+ description: z.string().optional().describe('Scope, assumptions, or edge cases'),
203
+ priority: z.enum(['Critical', 'High', 'Medium', 'Low']).optional()
204
+ },
205
+ async ({ suiteId, title, description, priority }) => {
206
+ const testCase = await testCaseService.create({
207
+ suiteId,
208
+ title,
209
+ description,
210
+ priority,
211
+ createdById: userId
212
+ });
213
+ return { content: [{ type: 'text', text: JSON.stringify(testCase, null, 2) }] };
214
+ }
215
+ );
198
216
 
199
- server.tool(
200
- 'delete_test_suite',
201
- 'Permanently delete a test suite and all of its test cases. This cannot be undone.',
202
- {
203
- suiteId: z.string().describe('UUID of the suite to delete')
204
- },
205
- async ({ suiteId }) => {
206
- await del(`/test-suites/${suiteId}`);
207
- return { content: [{ type: 'text', text: `Suite ${suiteId} deleted.` }] };
208
- }
209
- );
217
+ server.tool(
218
+ 'get_test_case',
219
+ 'Get a test case by ID, including its manual steps and recent execution history.',
220
+ {
221
+ caseId: z.string().describe('UUID of the test case')
222
+ },
223
+ async ({ caseId }) => {
224
+ const testCase = await testCaseService.getById(caseId);
225
+ if (!testCase) throw new Error('Test case not found');
226
+ return { content: [{ type: 'text', text: JSON.stringify(testCase, null, 2) }] };
227
+ }
228
+ );
229
+
230
+ server.tool(
231
+ 'update_test_case',
232
+ "Update a test case's title, description, or priority. Only the fields provided are changed.",
233
+ {
234
+ caseId: z.string().describe('UUID of the test case to update'),
235
+ title: z.string().min(1).optional(),
236
+ description: z.string().optional(),
237
+ priority: z.enum(['Critical', 'High', 'Medium', 'Low']).optional()
238
+ },
239
+ async ({ caseId, title, description, priority }) => {
240
+ const testCase = await testCaseService.update(caseId, { title, description, priority });
241
+ return { content: [{ type: 'text', text: JSON.stringify(testCase, null, 2) }] };
242
+ }
243
+ );
210
244
 
211
- // -- Test Repository: Cases -------------------------------------------------
212
-
213
- server.tool(
214
- 'create_test_case',
215
- 'Create a test case inside an existing test suite.',
216
- {
217
- suiteId: z.string().describe('UUID of the parent test suite'),
218
- title: z.string().min(1).describe('Case title, e.g. "User can log in with valid credentials"'),
219
- description: z.string().optional().describe('Scope, assumptions, or edge cases'),
220
- priority: z.enum(['Critical', 'High', 'Medium', 'Low']).optional()
221
- },
222
- async ({ suiteId, title, description, priority }) => {
223
- const data = await post('/test-cases', { suiteId, title, description, priority });
224
- return { content: [{ type: 'text', text: JSON.stringify(data.testCase, null, 2) }] };
225
- }
226
- );
245
+ server.tool(
246
+ 'delete_test_case',
247
+ 'Permanently delete a test case and its steps. This cannot be undone.',
248
+ {
249
+ caseId: z.string().describe('UUID of the test case to delete')
250
+ },
251
+ async ({ caseId }) => {
252
+ await testCaseService.remove(caseId);
253
+ return { content: [{ type: 'text', text: `Test case ${caseId} deleted.` }] };
254
+ }
255
+ );
256
+
257
+ server.tool(
258
+ 'set_test_steps',
259
+ 'Set (replace) the manual test steps for a test case. Each step has an action, optional test data, and optional expected output.',
260
+ {
261
+ caseId: z.string().describe('UUID of the test case'),
262
+ steps: z
263
+ .array(
264
+ z.object({
265
+ action: z.string().describe('What the tester does'),
266
+ testData: z.string().optional().describe('Specific input or data to use'),
267
+ expectedOutput: z.string().optional().describe('What should happen')
268
+ })
269
+ )
270
+ .describe('Ordered list of steps. Replaces any existing steps.')
271
+ },
272
+ async ({ caseId, steps }) => {
273
+ const saved = await testCaseService.upsertSteps(caseId, steps);
274
+ return { content: [{ type: 'text', text: JSON.stringify(saved, null, 2) }] };
275
+ }
276
+ );
277
+
278
+ // -- Test Runner ------------------------------------------------------------
279
+
280
+ server.tool(
281
+ 'run_tests',
282
+ [
283
+ 'Trigger a Plum test run and wait for results. Returns a pass/fail summary.',
284
+ '',
285
+ 'For PR testing: pass baseUrl to override the default BASE_URL for this run only.',
286
+ 'If the app runs on localhost, use host.docker.internal instead (e.g. http://host.docker.internal:3000)',
287
+ 'so the Plum Docker container can reach it.',
288
+ '',
289
+ 'Tag filter examples: "@login" "@TC-001 or @TC-002" "@smoke and not @slow"',
290
+ 'Leave tag blank to run all tests.'
291
+ ].join('\n'),
292
+ {
293
+ tag: z
294
+ .string()
295
+ .optional()
296
+ .describe('Cucumber tag expression to filter scenarios. Leave blank to run all.'),
297
+ browser: z
298
+ .enum(['chromium', 'firefox'])
299
+ .optional()
300
+ .describe('Browser to run tests in (default chromium)'),
301
+ workers: z
302
+ .number()
303
+ .int()
304
+ .positive()
305
+ .max(10)
306
+ .optional()
307
+ .describe('Number of parallel workers (default 1)'),
308
+ baseUrl: z
309
+ .string()
310
+ .url()
311
+ .optional()
312
+ .describe(
313
+ 'Override the app URL for this run only. Use host.docker.internal instead of localhost.'
314
+ ),
315
+ testRunId: z
316
+ .string()
317
+ .optional()
318
+ .describe(
319
+ 'Link results to a Plum Test Run. Automated entries will be marked pass/fail automatically.'
320
+ )
321
+ },
322
+ async ({ tag, browser, workers, baseUrl, testRunId }) => {
323
+ const jobId = await triggerService.startRun({
324
+ tag,
325
+ browser,
326
+ workers,
327
+ baseUrl,
328
+ testRunId,
329
+ trigger: TRIGGER_TYPE.MCP
330
+ });
331
+
332
+ const job = await pollJob(jobId);
333
+
334
+ if (job.status === 'timeout') {
335
+ return {
336
+ content: [
337
+ {
338
+ type: 'text',
339
+ text: `Tests are still running after 10 minutes. Job ID: ${jobId}\nUse get_run_status("${jobId}") to check later.`
340
+ }
341
+ ]
342
+ };
343
+ }
227
344
 
228
- server.tool(
229
- 'get_test_case',
230
- 'Get a test case by ID, including its manual steps and recent execution history.',
231
- {
232
- caseId: z.string().describe('UUID of the test case')
233
- },
234
- async ({ caseId }) => {
235
- const data = await get(`/test-cases/${caseId}`);
236
- return { content: [{ type: 'text', text: JSON.stringify(data.testCase, null, 2) }] };
237
- }
238
- );
345
+ if (!job.reportId) {
346
+ return {
347
+ content: [
348
+ {
349
+ type: 'text',
350
+ text: `Tests finished with exit code ${job.exitCode} but no report was found. The run may have been cancelled or produced no output.`
351
+ }
352
+ ]
353
+ };
354
+ }
239
355
 
240
- server.tool(
241
- 'update_test_case',
242
- "Update a test case's title, description, or priority. Only the fields provided are changed.",
243
- {
244
- caseId: z.string().describe('UUID of the test case to update'),
245
- title: z.string().min(1).optional(),
246
- description: z.string().optional(),
247
- priority: z.enum(['Critical', 'High', 'Medium', 'Low']).optional()
248
- },
249
- async ({ caseId, title, description, priority }) => {
250
- const data = await put(`/test-cases/${caseId}`, { title, description, priority });
251
- return { content: [{ type: 'text', text: JSON.stringify(data.testCase, null, 2) }] };
252
- }
253
- );
356
+ const report = await reportService.getReportDetail(job.reportId);
357
+ const summary = summariseReport(report);
358
+
359
+ const lines = [
360
+ `Status: ${summary.status?.toUpperCase()}`,
361
+ `Scenarios: ${summary.summary.total} total, ${summary.summary.passed} passed, ${summary.summary.failed} failed`,
362
+ `Browser: ${summary.browser}`,
363
+ `Tags: ${summary.tags || 'all tests'}`,
364
+ ''
365
+ ];
366
+
367
+ if (summary.failures.length > 0) {
368
+ lines.push('Failed scenarios:');
369
+ for (const f of summary.failures) {
370
+ lines.push(` ✗ ${f.feature} › ${f.scenario}`);
371
+ if (f.failedStep) lines.push(` Step: ${f.failedStep}`);
372
+ if (f.error) lines.push(` Error: ${f.error.split('\n')[0]}`);
373
+ }
374
+ } else {
375
+ lines.push('All scenarios passed.');
376
+ }
254
377
 
255
- server.tool(
256
- 'delete_test_case',
257
- 'Permanently delete a test case and its steps. This cannot be undone.',
258
- {
259
- caseId: z.string().describe('UUID of the test case to delete')
260
- },
261
- async ({ caseId }) => {
262
- await del(`/test-cases/${caseId}`);
263
- return { content: [{ type: 'text', text: `Test case ${caseId} deleted.` }] };
264
- }
265
- );
378
+ lines.push('', `Report ID: ${summary.id}`);
266
379
 
267
- server.tool(
268
- 'set_test_steps',
269
- 'Set (replace) the manual test steps for a test case. Each step has an action, optional test data, and optional expected output.',
270
- {
271
- caseId: z.string().describe('UUID of the test case'),
272
- steps: z
273
- .array(
274
- z.object({
275
- action: z.string().describe('What the tester does'),
276
- testData: z.string().optional().describe('Specific input or data to use'),
277
- expectedOutput: z.string().optional().describe('What should happen')
278
- })
279
- )
280
- .describe('Ordered list of steps. Replaces any existing steps.')
281
- },
282
- async ({ caseId, steps }) => {
283
- const data = await put(`/test-cases/${caseId}/steps`, { steps });
284
- return { content: [{ type: 'text', text: JSON.stringify(data.steps, null, 2) }] };
285
- }
286
- );
380
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
381
+ }
382
+ );
287
383
 
288
- // -- Test Runner ------------------------------------------------------------
289
-
290
- server.tool(
291
- 'run_tests',
292
- [
293
- 'Trigger a Plum test run and wait for results. Returns a pass/fail summary.',
294
- '',
295
- 'For PR testing: pass baseUrl to override the default BASE_URL for this run only.',
296
- 'If the app runs on localhost, use host.docker.internal instead (e.g. http://host.docker.internal:3000)',
297
- 'so the Plum Docker container can reach it.',
298
- '',
299
- 'Tag filter examples: "@login" "@TC-001 or @TC-002" "@smoke and not @slow"',
300
- 'Leave tag blank to run all tests.'
301
- ].join('\n'),
302
- {
303
- tag: z
304
- .string()
305
- .optional()
306
- .describe('Cucumber tag expression to filter scenarios. Leave blank to run all.'),
307
- browser: z
308
- .enum(['chromium', 'firefox'])
309
- .optional()
310
- .describe('Browser to run tests in (default chromium)'),
311
- workers: z
312
- .number()
313
- .int()
314
- .positive()
315
- .max(10)
316
- .optional()
317
- .describe('Number of parallel workers (default 1)'),
318
- baseUrl: z
319
- .string()
320
- .url()
321
- .optional()
322
- .describe(
323
- 'Override the app URL for this run only. Use host.docker.internal instead of localhost.'
324
- ),
325
- testRunId: z
326
- .string()
327
- .optional()
328
- .describe(
329
- 'Link results to a Plum Test Run. Automated entries will be marked pass/fail automatically.'
330
- )
331
- },
332
- async ({ tag, browser, workers, baseUrl, testRunId }) => {
333
- const { jobId } = await post('/trigger', {
334
- tag,
335
- browser,
336
- workers,
337
- baseUrl,
338
- testRunId,
339
- source: 'mcp'
340
- });
384
+ server.tool(
385
+ 'get_run_status',
386
+ 'Check the status of a test run that was started with run_tests. Returns status, exit code, and report ID when done.',
387
+ {
388
+ jobId: z.string().uuid().describe('Job ID returned by run_tests')
389
+ },
390
+ async ({ jobId }) => {
391
+ const job = triggerService.getJob(jobId);
392
+ if (!job) throw new Error('Job not found');
393
+ return { content: [{ type: 'text', text: JSON.stringify(job, null, 2) }] };
394
+ }
395
+ );
396
+
397
+ // -- Reports ----------------------------------------------------------------
398
+
399
+ server.tool(
400
+ 'list_reports',
401
+ 'List recent Plum test reports.',
402
+ {
403
+ limit: z
404
+ .number()
405
+ .int()
406
+ .positive()
407
+ .max(50)
408
+ .optional()
409
+ .describe('Number of reports (default 10)')
410
+ },
411
+ async ({ limit = 10 }) => {
412
+ const data = await reportService.getReports({ limit });
413
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
414
+ }
415
+ );
341
416
 
342
- const job = await pollJob(jobId);
417
+ server.tool(
418
+ 'get_report_summary',
419
+ 'Get a human-readable summary of a Plum test report, including a list of failed scenarios.',
420
+ {
421
+ reportId: z.number().int().describe('Numeric report ID')
422
+ },
423
+ async ({ reportId }) => {
424
+ const detail = await reportService.getReportDetail(reportId);
425
+ if (!detail) throw new Error('Report not found');
426
+ const summary = summariseReport(detail);
427
+ const lines = [
428
+ `Report #${summary.id} — ${summary.status?.toUpperCase()}`,
429
+ `Date: ${summary.createdAt}`,
430
+ `Browser: ${summary.browser}`,
431
+ `Tags: ${summary.tags || 'all tests'}`,
432
+ `Scenarios: ${summary.summary.total} total, ${summary.summary.passed} passed, ${summary.summary.failed} failed`,
433
+ ''
434
+ ];
435
+ if (summary.failures.length > 0) {
436
+ lines.push('Failures:');
437
+ for (const f of summary.failures) {
438
+ lines.push(` ✗ ${f.feature} › ${f.scenario}`);
439
+ if (f.failedStep) lines.push(` Step: ${f.failedStep}`);
440
+ if (f.error) lines.push(` Error: ${f.error.split('\n')[0]}`);
441
+ }
442
+ } else {
443
+ lines.push('All scenarios passed.');
444
+ }
445
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
446
+ }
447
+ );
448
+
449
+ server.tool(
450
+ 'get_report_scenario_detail',
451
+ [
452
+ 'Get full per-scenario, per-step detail for a Plum test report — the data needed to diagnose',
453
+ "and self-heal a failing test: every step's status, duration, full error message, and",
454
+ 'screenshot URL (if one was captured for that step).',
455
+ '',
456
+ 'Use get_report_screenshot to fetch the actual screenshot image for a filename returned here,',
457
+ 'and get_report_logs for the raw test-run stdout/stderr.'
458
+ ].join('\n'),
459
+ {
460
+ reportId: z.number().int().describe('Numeric report ID'),
461
+ onlyFailed: z
462
+ .boolean()
463
+ .optional()
464
+ .describe('Only include scenarios with a failed step (default true)')
465
+ },
466
+ async ({ reportId, onlyFailed = true }) => {
467
+ const detail = await reportService.getReportDetail(reportId);
468
+ if (!detail) throw new Error('Report not found');
469
+ const features = detail.features ?? [];
470
+
471
+ const scenarios = features.flatMap((feature) =>
472
+ (feature.scenarios ?? [])
473
+ .filter((s) => !onlyFailed || s.status === 'failed')
474
+ .map((s) => ({
475
+ feature: feature.name,
476
+ scenario: s.name,
477
+ tags: s.tags ?? [],
478
+ status: s.status,
479
+ duration: s.duration,
480
+ steps: (s.steps ?? []).map((st) => ({
481
+ keyword: st.keyword,
482
+ name: st.name,
483
+ status: st.status,
484
+ duration: st.duration,
485
+ error: st.error ?? null,
486
+ screenshot: st.screenshot ?? null
487
+ }))
488
+ }))
489
+ );
343
490
 
344
- if (job.status === 'timeout') {
345
491
  return {
346
- content: [
347
- {
348
- type: 'text',
349
- text: `Tests are still running after 10 minutes. Job ID: ${jobId}\nUse get_run_status("${jobId}") to check later.`
350
- }
351
- ]
492
+ content: [{ type: 'text', text: JSON.stringify({ reportId, scenarios }, null, 2) }]
352
493
  };
353
494
  }
495
+ );
496
+
497
+ server.tool(
498
+ 'get_report_screenshot',
499
+ 'Fetch a screenshot captured during a test step and return it as an image, so it can be viewed ' +
500
+ "directly. Get the filename from get_report_scenario_detail's step.screenshot field.",
501
+ {
502
+ filename: z.string().describe('Screenshot filename, e.g. "3f9c1e2a-....png"')
503
+ },
504
+ async ({ filename }) => {
505
+ if (!SCREENSHOT_FILENAME_RE.test(filename)) {
506
+ throw new Error(`Invalid screenshot filename: ${filename}`);
507
+ }
508
+ const filePath = path.join(SCREENSHOTS_DIR, filename);
509
+ let buffer;
510
+ try {
511
+ buffer = await fs.promises.readFile(filePath);
512
+ } catch {
513
+ throw new Error(`Screenshot not found: ${filename}`);
514
+ }
515
+ const ext = filename.split('.').pop().toLowerCase();
354
516
 
355
- if (!job.reportId) {
356
517
  return {
357
518
  content: [
358
519
  {
359
- type: 'text',
360
- text: `Tests finished with exit code ${job.exitCode} but no report was found. The run may have been cancelled or produced no output.`
520
+ type: 'image',
521
+ data: buffer.toString('base64'),
522
+ mimeType: SCREENSHOT_MIME_TYPES[ext]
361
523
  }
362
524
  ]
363
525
  };
364
526
  }
365
-
366
- const report = await get(`/reports/${job.reportId}`);
367
- const summary = summariseReport(report.report ?? report);
368
-
369
- const lines = [
370
- `Status: ${summary.status?.toUpperCase()}`,
371
- `Scenarios: ${summary.summary.total} total, ${summary.summary.passed} passed, ${summary.summary.failed} failed`,
372
- `Browser: ${summary.browser}`,
373
- `Tags: ${summary.tags || 'all tests'}`,
374
- ''
375
- ];
376
-
377
- if (summary.failures.length > 0) {
378
- lines.push('Failed scenarios:');
379
- for (const f of summary.failures) {
380
- lines.push(` ✗ ${f.feature} ${f.scenario}`);
381
- if (f.failedStep) lines.push(` Step: ${f.failedStep}`);
382
- if (f.error) lines.push(` Error: ${f.error.split('\n')[0]}`);
383
- }
384
- } else {
385
- lines.push('All scenarios passed.');
386
- }
387
-
388
- lines.push('', `Report ID: ${summary.id}`);
389
-
390
- return { content: [{ type: 'text', text: lines.join('\n') }] };
391
- }
392
- );
393
-
394
- server.tool(
395
- 'get_run_status',
396
- 'Check the status of a test run that was started with run_tests. Returns status, exit code, and report ID when done.',
397
- {
398
- jobId: z.string().uuid().describe('Job ID returned by run_tests')
399
- },
400
- async ({ jobId }) => {
401
- const job = await get(`/trigger/${jobId}`);
402
- return { content: [{ type: 'text', text: JSON.stringify(job, null, 2) }] };
403
- }
404
- );
405
-
406
- // -- Reports ----------------------------------------------------------------
407
-
408
- server.tool(
409
- 'list_reports',
410
- 'List recent Plum test reports.',
411
- {
412
- limit: z.number().int().positive().max(50).optional().describe('Number of reports (default 10)')
413
- },
414
- async ({ limit = 10 }) => {
415
- const data = await get(`/reports?limit=${limit}`);
416
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
417
- }
418
- );
419
-
420
- server.tool(
421
- 'get_report_summary',
422
- 'Get a human-readable summary of a Plum test report, including a list of failed scenarios.',
423
- {
424
- reportId: z.number().int().describe('Numeric report ID')
425
- },
426
- async ({ reportId }) => {
427
- const data = await get(`/reports/${reportId}`);
428
- const summary = summariseReport(data.report ?? data);
429
- const lines = [
430
- `Report #${summary.id} — ${summary.status?.toUpperCase()}`,
431
- `Date: ${summary.createdAt}`,
432
- `Browser: ${summary.browser}`,
433
- `Tags: ${summary.tags || 'all tests'}`,
434
- `Scenarios: ${summary.summary.total} total, ${summary.summary.passed} passed, ${summary.summary.failed} failed`,
435
- ''
436
- ];
437
- if (summary.failures.length > 0) {
438
- lines.push('Failures:');
439
- for (const f of summary.failures) {
440
- lines.push(` ✗ ${f.feature} › ${f.scenario}`);
441
- if (f.failedStep) lines.push(` Step: ${f.failedStep}`);
442
- if (f.error) lines.push(` Error: ${f.error.split('\n')[0]}`);
527
+ );
528
+
529
+ server.tool(
530
+ 'get_report_logs',
531
+ 'Get the raw stdout/stderr log output captured during a Plum test run, tagged per runner. ' +
532
+ 'Useful for diagnosing failures with no clear step-level error (crashes, timeouts, setup errors).',
533
+ {
534
+ reportId: z.number().int().describe('Numeric report ID'),
535
+ tail: z
536
+ .number()
537
+ .int()
538
+ .positive()
539
+ .optional()
540
+ .describe('Only return the last N lines (default: full log)')
541
+ },
542
+ async ({ reportId, tail }) => {
543
+ const detail = await reportService.getReportDetail(reportId);
544
+ if (!detail) throw new Error('Report not found');
545
+ let logs = detail.logs ?? '';
546
+ if (tail) {
547
+ logs = logs.split('\n').slice(-tail).join('\n');
443
548
  }
444
- } else {
445
- lines.push('All scenarios passed.');
549
+ return { content: [{ type: 'text', text: logs || '(no logs captured for this report)' }] };
446
550
  }
447
- return { content: [{ type: 'text', text: lines.join('\n') }] };
448
- }
449
- );
450
-
451
- server.tool(
452
- 'get_report_scenario_detail',
453
- [
454
- 'Get full per-scenario, per-step detail for a Plum test report — the data needed to diagnose',
455
- "and self-heal a failing test: every step's status, duration, full error message, and",
456
- 'screenshot URL (if one was captured for that step).',
457
- '',
458
- 'Use get_report_screenshot to fetch the actual screenshot image for a filename returned here,',
459
- 'and get_report_logs for the raw test-run stdout/stderr.'
460
- ].join('\n'),
461
- {
462
- reportId: z.number().int().describe('Numeric report ID'),
463
- onlyFailed: z
464
- .boolean()
465
- .optional()
466
- .describe('Only include scenarios with a failed step (default true)')
467
- },
468
- async ({ reportId, onlyFailed = true }) => {
469
- const data = await get(`/reports/${reportId}`);
470
- const features = data.features ?? data.content?.features ?? [];
471
-
472
- const scenarios = features.flatMap((feature) =>
473
- (feature.scenarios ?? [])
474
- .filter((s) => !onlyFailed || s.status === 'failed')
475
- .map((s) => ({
476
- feature: feature.name,
477
- scenario: s.name,
478
- tags: s.tags ?? [],
479
- status: s.status,
480
- duration: s.duration,
481
- steps: (s.steps ?? []).map((st) => ({
482
- keyword: st.keyword,
483
- name: st.name,
484
- status: st.status,
485
- duration: st.duration,
486
- error: st.error ?? null,
487
- screenshot: st.screenshot ?? null,
488
- screenshotUrl: st.screenshot ? screenshotUrl(st.screenshot) : null
489
- }))
490
- }))
491
- );
492
-
493
- return { content: [{ type: 'text', text: JSON.stringify({ reportId, scenarios }, null, 2) }] };
494
- }
495
- );
496
-
497
- server.tool(
498
- 'get_report_screenshot',
499
- 'Fetch a screenshot captured during a test step and return it as an image, so it can be viewed ' +
500
- "directly. Get the filename from get_report_scenario_detail's step.screenshot field.",
501
- {
502
- filename: z.string().describe('Screenshot filename, e.g. "3f9c1e2a-....png"')
503
- },
504
- async ({ filename }) => {
505
- if (!SCREENSHOT_FILENAME_RE.test(filename)) {
506
- throw new Error(`Invalid screenshot filename: ${filename}`);
507
- }
508
- const res = await fetch(screenshotUrl(filename));
509
- if (!res.ok) throw new Error(`Screenshot not found: ${filename}`);
510
- const buffer = Buffer.from(await res.arrayBuffer());
511
- const ext = filename.split('.').pop().toLowerCase();
512
-
513
- return {
514
- content: [
515
- {
516
- type: 'image',
517
- data: buffer.toString('base64'),
518
- mimeType: SCREENSHOT_MIME_TYPES[ext]
519
- }
520
- ]
521
- };
522
- }
523
- );
524
-
525
- server.tool(
526
- 'get_report_logs',
527
- 'Get the raw stdout/stderr log output captured during a Plum test run, tagged per runner. ' +
528
- 'Useful for diagnosing failures with no clear step-level error (crashes, timeouts, setup errors).',
529
- {
530
- reportId: z.number().int().describe('Numeric report ID'),
531
- tail: z
532
- .number()
533
- .int()
534
- .positive()
535
- .optional()
536
- .describe('Only return the last N lines (default: full log)')
537
- },
538
- async ({ reportId, tail }) => {
539
- const data = await get(`/reports/${reportId}`);
540
- let logs = data.logs ?? '';
541
- if (tail) {
542
- logs = logs.split('\n').slice(-tail).join('\n');
543
- }
544
- return { content: [{ type: 'text', text: logs || '(no logs captured for this report)' }] };
545
- }
546
- );
547
-
548
- // ---------------------------------------------------------------------------
549
- // Start
550
- // ---------------------------------------------------------------------------
551
+ );
551
552
 
552
- async function main() {
553
- const transport = new StdioServerTransport();
554
- await server.connect(transport);
553
+ return server;
555
554
  }
556
555
 
557
- main().catch((err) => {
558
- console.error(err);
559
- process.exit(1);
560
- });
556
+ module.exports = { createMcpServer };