@miragon/pulse-analytics-connector 0.0.0-stage → 0.21.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 (357) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -2
  3. package/dist/bpmn-name-lookup.d.ts +72 -0
  4. package/dist/bpmn-name-lookup.d.ts.map +1 -0
  5. package/dist/bpmn-name-lookup.js +123 -0
  6. package/dist/bpmn-name-lookup.js.map +1 -0
  7. package/dist/bpmn-name-lookup.test.d.ts +2 -0
  8. package/dist/bpmn-name-lookup.test.d.ts.map +1 -0
  9. package/dist/bpmn-name-lookup.test.js +287 -0
  10. package/dist/bpmn-name-lookup.test.js.map +1 -0
  11. package/dist/bpmn-names.d.ts +38 -0
  12. package/dist/bpmn-names.d.ts.map +1 -0
  13. package/dist/bpmn-names.js +22 -0
  14. package/dist/bpmn-names.js.map +1 -0
  15. package/dist/brand-gates.test.d.ts +2 -0
  16. package/dist/brand-gates.test.d.ts.map +1 -0
  17. package/dist/brand-gates.test.js +58 -0
  18. package/dist/brand-gates.test.js.map +1 -0
  19. package/dist/builder-texts.d.ts +4 -0
  20. package/dist/builder-texts.d.ts.map +1 -0
  21. package/dist/builder-texts.js +14 -0
  22. package/dist/builder-texts.js.map +1 -0
  23. package/dist/builder-texts.test.d.ts +2 -0
  24. package/dist/builder-texts.test.d.ts.map +1 -0
  25. package/dist/builder-texts.test.js +32 -0
  26. package/dist/builder-texts.test.js.map +1 -0
  27. package/dist/definition.d.ts +3 -0
  28. package/dist/definition.d.ts.map +1 -0
  29. package/dist/definition.js +151 -0
  30. package/dist/definition.js.map +1 -0
  31. package/dist/engine-ids.d.ts +42 -0
  32. package/dist/engine-ids.d.ts.map +1 -0
  33. package/dist/engine-ids.js +50 -0
  34. package/dist/engine-ids.js.map +1 -0
  35. package/dist/engine-scope.test.d.ts +2 -0
  36. package/dist/engine-scope.test.d.ts.map +1 -0
  37. package/dist/engine-scope.test.js +260 -0
  38. package/dist/engine-scope.test.js.map +1 -0
  39. package/dist/heatmap-data.d.ts +15 -0
  40. package/dist/heatmap-data.d.ts.map +1 -0
  41. package/dist/heatmap-data.js +2 -0
  42. package/dist/heatmap-data.js.map +1 -0
  43. package/dist/index.d.ts +5 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +3 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/instructions.d.ts +8 -0
  48. package/dist/instructions.d.ts.map +1 -0
  49. package/dist/instructions.js +33 -0
  50. package/dist/instructions.js.map +1 -0
  51. package/dist/messages/de.ask-ai.d.ts +9 -0
  52. package/dist/messages/de.ask-ai.d.ts.map +1 -0
  53. package/dist/messages/de.ask-ai.js +18 -0
  54. package/dist/messages/de.ask-ai.js.map +1 -0
  55. package/dist/messages/de.builder.d.ts +12 -0
  56. package/dist/messages/de.builder.d.ts.map +1 -0
  57. package/dist/messages/de.builder.js +53 -0
  58. package/dist/messages/de.builder.js.map +1 -0
  59. package/dist/messages/de.server.d.ts +8 -0
  60. package/dist/messages/de.server.d.ts.map +1 -0
  61. package/dist/messages/de.server.js +26 -0
  62. package/dist/messages/de.server.js.map +1 -0
  63. package/dist/messages/de.sweep.d.ts +10 -0
  64. package/dist/messages/de.sweep.d.ts.map +1 -0
  65. package/dist/messages/de.sweep.js +203 -0
  66. package/dist/messages/de.sweep.js.map +1 -0
  67. package/dist/messages/en.ask-ai.d.ts +25 -0
  68. package/dist/messages/en.ask-ai.d.ts.map +1 -0
  69. package/dist/messages/en.ask-ai.js +24 -0
  70. package/dist/messages/en.ask-ai.js.map +1 -0
  71. package/dist/messages/en.builder.d.ts +12 -0
  72. package/dist/messages/en.builder.d.ts.map +1 -0
  73. package/dist/messages/en.builder.js +53 -0
  74. package/dist/messages/en.builder.js.map +1 -0
  75. package/dist/messages/en.server.d.ts +8 -0
  76. package/dist/messages/en.server.d.ts.map +1 -0
  77. package/dist/messages/en.server.js +26 -0
  78. package/dist/messages/en.server.js.map +1 -0
  79. package/dist/messages/en.sweep.d.ts +11 -0
  80. package/dist/messages/en.sweep.d.ts.map +1 -0
  81. package/dist/messages/en.sweep.js +204 -0
  82. package/dist/messages/en.sweep.js.map +1 -0
  83. package/dist/messages/glossary.d.ts +26 -0
  84. package/dist/messages/glossary.d.ts.map +1 -0
  85. package/dist/messages/glossary.js +37 -0
  86. package/dist/messages/glossary.js.map +1 -0
  87. package/dist/messages/glossary.test.d.ts +2 -0
  88. package/dist/messages/glossary.test.d.ts.map +1 -0
  89. package/dist/messages/glossary.test.js +109 -0
  90. package/dist/messages/glossary.test.js.map +1 -0
  91. package/dist/messages/index.d.ts +12 -0
  92. package/dist/messages/index.d.ts.map +1 -0
  93. package/dist/messages/index.js +23 -0
  94. package/dist/messages/index.js.map +1 -0
  95. package/dist/messages/plural.d.ts +10 -0
  96. package/dist/messages/plural.d.ts.map +1 -0
  97. package/dist/messages/plural.js +17 -0
  98. package/dist/messages/plural.js.map +1 -0
  99. package/dist/messages/use-t.d.ts +10 -0
  100. package/dist/messages/use-t.d.ts.map +1 -0
  101. package/dist/messages/use-t.js +10 -0
  102. package/dist/messages/use-t.js.map +1 -0
  103. package/dist/module.d.ts +48 -0
  104. package/dist/module.d.ts.map +1 -0
  105. package/dist/module.js +174 -0
  106. package/dist/module.js.map +1 -0
  107. package/dist/module.test.d.ts +2 -0
  108. package/dist/module.test.d.ts.map +1 -0
  109. package/dist/module.test.js +230 -0
  110. package/dist/module.test.js.map +1 -0
  111. package/dist/named-views.d.ts +27 -0
  112. package/dist/named-views.d.ts.map +1 -0
  113. package/dist/named-views.js +73 -0
  114. package/dist/named-views.js.map +1 -0
  115. package/dist/named-views.test.d.ts +2 -0
  116. package/dist/named-views.test.d.ts.map +1 -0
  117. package/dist/named-views.test.js +110 -0
  118. package/dist/named-views.test.js.map +1 -0
  119. package/dist/plugin.d.ts +44 -0
  120. package/dist/plugin.d.ts.map +1 -0
  121. package/dist/plugin.js +52 -0
  122. package/dist/plugin.js.map +1 -0
  123. package/dist/prometheus-wiring.test.d.ts +2 -0
  124. package/dist/prometheus-wiring.test.d.ts.map +1 -0
  125. package/dist/prometheus-wiring.test.js +142 -0
  126. package/dist/prometheus-wiring.test.js.map +1 -0
  127. package/dist/server-locale.d.ts +48 -0
  128. package/dist/server-locale.d.ts.map +1 -0
  129. package/dist/server-locale.js +33 -0
  130. package/dist/server-locale.js.map +1 -0
  131. package/dist/settings-tools.d.ts +23 -0
  132. package/dist/settings-tools.d.ts.map +1 -0
  133. package/dist/settings-tools.js +100 -0
  134. package/dist/settings-tools.js.map +1 -0
  135. package/dist/settings.d.ts +77 -0
  136. package/dist/settings.d.ts.map +1 -0
  137. package/dist/settings.js +99 -0
  138. package/dist/settings.js.map +1 -0
  139. package/dist/settings.test.d.ts +2 -0
  140. package/dist/settings.test.d.ts.map +1 -0
  141. package/dist/settings.test.js +299 -0
  142. package/dist/settings.test.js.map +1 -0
  143. package/dist/steps/app-config.d.ts +30 -0
  144. package/dist/steps/app-config.d.ts.map +1 -0
  145. package/dist/steps/app-config.js +46 -0
  146. package/dist/steps/app-config.js.map +1 -0
  147. package/dist/steps/dashboard.d.ts +12 -0
  148. package/dist/steps/dashboard.d.ts.map +1 -0
  149. package/dist/steps/dashboard.js +58 -0
  150. package/dist/steps/dashboard.js.map +1 -0
  151. package/dist/steps/failure-dashboard.d.ts +10 -0
  152. package/dist/steps/failure-dashboard.d.ts.map +1 -0
  153. package/dist/steps/failure-dashboard.js +32 -0
  154. package/dist/steps/failure-dashboard.js.map +1 -0
  155. package/dist/steps/index.d.ts +3 -0
  156. package/dist/steps/index.d.ts.map +1 -0
  157. package/dist/steps/index.js +3 -0
  158. package/dist/steps/index.js.map +1 -0
  159. package/dist/steps/steps.test.d.ts +2 -0
  160. package/dist/steps/steps.test.d.ts.map +1 -0
  161. package/dist/steps/steps.test.js +153 -0
  162. package/dist/steps/steps.test.js.map +1 -0
  163. package/dist/tool-names.d.ts +13 -0
  164. package/dist/tool-names.d.ts.map +1 -0
  165. package/dist/tool-names.js +27 -0
  166. package/dist/tool-names.js.map +1 -0
  167. package/dist/tools/cluster-compare.d.ts +8 -0
  168. package/dist/tools/cluster-compare.d.ts.map +1 -0
  169. package/dist/tools/cluster-compare.js +17 -0
  170. package/dist/tools/cluster-compare.js.map +1 -0
  171. package/dist/tools/element.d.ts +8 -0
  172. package/dist/tools/element.d.ts.map +1 -0
  173. package/dist/tools/element.js +25 -0
  174. package/dist/tools/element.js.map +1 -0
  175. package/dist/tools/engine-compare.d.ts +8 -0
  176. package/dist/tools/engine-compare.d.ts.map +1 -0
  177. package/dist/tools/engine-compare.js +18 -0
  178. package/dist/tools/engine-compare.js.map +1 -0
  179. package/dist/tools/engine-compare.test.d.ts +2 -0
  180. package/dist/tools/engine-compare.test.d.ts.map +1 -0
  181. package/dist/tools/engine-compare.test.js +166 -0
  182. package/dist/tools/engine-compare.test.js.map +1 -0
  183. package/dist/tools/engine-landscape.d.ts +7 -0
  184. package/dist/tools/engine-landscape.d.ts.map +1 -0
  185. package/dist/tools/engine-landscape.js +13 -0
  186. package/dist/tools/engine-landscape.js.map +1 -0
  187. package/dist/tools/engine-landscape.test.d.ts +2 -0
  188. package/dist/tools/engine-landscape.test.d.ts.map +1 -0
  189. package/dist/tools/engine-landscape.test.js +110 -0
  190. package/dist/tools/engine-landscape.test.js.map +1 -0
  191. package/dist/tools/failures.d.ts +7 -0
  192. package/dist/tools/failures.d.ts.map +1 -0
  193. package/dist/tools/failures.js +13 -0
  194. package/dist/tools/failures.js.map +1 -0
  195. package/dist/tools/health.d.ts +7 -0
  196. package/dist/tools/health.d.ts.map +1 -0
  197. package/dist/tools/health.js +23 -0
  198. package/dist/tools/health.js.map +1 -0
  199. package/dist/tools/health.test.d.ts +2 -0
  200. package/dist/tools/health.test.d.ts.map +1 -0
  201. package/dist/tools/health.test.js +119 -0
  202. package/dist/tools/health.test.js.map +1 -0
  203. package/dist/tools/index.d.ts +18 -0
  204. package/dist/tools/index.d.ts.map +1 -0
  205. package/dist/tools/index.js +35 -0
  206. package/dist/tools/index.js.map +1 -0
  207. package/dist/tools/performance.d.ts +8 -0
  208. package/dist/tools/performance.d.ts.map +1 -0
  209. package/dist/tools/performance.js +25 -0
  210. package/dist/tools/performance.js.map +1 -0
  211. package/dist/tools/performance.test.d.ts +2 -0
  212. package/dist/tools/performance.test.d.ts.map +1 -0
  213. package/dist/tools/performance.test.js +176 -0
  214. package/dist/tools/performance.test.js.map +1 -0
  215. package/dist/tools/version-compare.d.ts +8 -0
  216. package/dist/tools/version-compare.d.ts.map +1 -0
  217. package/dist/tools/version-compare.js +17 -0
  218. package/dist/tools/version-compare.js.map +1 -0
  219. package/dist/tools/version-compare.test.d.ts +2 -0
  220. package/dist/tools/version-compare.test.d.ts.map +1 -0
  221. package/dist/tools/version-compare.test.js +82 -0
  222. package/dist/tools/version-compare.test.js.map +1 -0
  223. package/dist/toolsets.d.ts +17 -0
  224. package/dist/toolsets.d.ts.map +1 -0
  225. package/dist/toolsets.js +47 -0
  226. package/dist/toolsets.js.map +1 -0
  227. package/dist/toolsets.test.d.ts +2 -0
  228. package/dist/toolsets.test.d.ts.map +1 -0
  229. package/dist/toolsets.test.js +91 -0
  230. package/dist/toolsets.test.js.map +1 -0
  231. package/dist/view-title.test.d.ts +2 -0
  232. package/dist/view-title.test.d.ts.map +1 -0
  233. package/dist/view-title.test.js +91 -0
  234. package/dist/view-title.test.js.map +1 -0
  235. package/dist/widget-tools/comparisons.d.ts +13 -0
  236. package/dist/widget-tools/comparisons.d.ts.map +1 -0
  237. package/dist/widget-tools/comparisons.js +177 -0
  238. package/dist/widget-tools/comparisons.js.map +1 -0
  239. package/dist/widget-tools/comparisons.test.d.ts +2 -0
  240. package/dist/widget-tools/comparisons.test.d.ts.map +1 -0
  241. package/dist/widget-tools/comparisons.test.js +123 -0
  242. package/dist/widget-tools/comparisons.test.js.map +1 -0
  243. package/dist/widget-tools/shared.d.ts +36 -0
  244. package/dist/widget-tools/shared.d.ts.map +1 -0
  245. package/dist/widget-tools/shared.js +39 -0
  246. package/dist/widget-tools/shared.js.map +1 -0
  247. package/dist/widget-tools.d.ts +25 -0
  248. package/dist/widget-tools.d.ts.map +1 -0
  249. package/dist/widget-tools.js +269 -0
  250. package/dist/widget-tools.js.map +1 -0
  251. package/dist/widget-tools.test.d.ts +2 -0
  252. package/dist/widget-tools.test.d.ts.map +1 -0
  253. package/dist/widget-tools.test.js +272 -0
  254. package/dist/widget-tools.test.js.map +1 -0
  255. package/package.json +91 -3
  256. package/src/bpmn-name-lookup.test.ts +333 -0
  257. package/src/bpmn-name-lookup.ts +197 -0
  258. package/src/bpmn-names.ts +51 -0
  259. package/src/brand-gates.test.ts +69 -0
  260. package/src/builder-texts.test.ts +40 -0
  261. package/src/builder-texts.ts +15 -0
  262. package/src/definition.ts +181 -0
  263. package/src/engine-ids.ts +85 -0
  264. package/src/engine-scope.test.ts +324 -0
  265. package/src/heatmap-data.ts +14 -0
  266. package/src/index.ts +4 -0
  267. package/src/instructions.ts +33 -0
  268. package/src/messages/GLOSSARY.md +88 -0
  269. package/src/messages/de.ask-ai.ts +28 -0
  270. package/src/messages/de.builder.ts +77 -0
  271. package/src/messages/de.server.ts +39 -0
  272. package/src/messages/de.sweep.ts +244 -0
  273. package/src/messages/en.ask-ai.ts +34 -0
  274. package/src/messages/en.builder.ts +76 -0
  275. package/src/messages/en.server.ts +39 -0
  276. package/src/messages/en.sweep.ts +240 -0
  277. package/src/messages/glossary.test.ts +122 -0
  278. package/src/messages/glossary.ts +62 -0
  279. package/src/messages/index.ts +24 -0
  280. package/src/messages/plural.ts +18 -0
  281. package/src/messages/use-t.ts +12 -0
  282. package/src/module.test.ts +319 -0
  283. package/src/module.ts +211 -0
  284. package/src/named-views.test.ts +141 -0
  285. package/src/named-views.ts +97 -0
  286. package/src/plugin.ts +94 -0
  287. package/src/prometheus-wiring.test.ts +174 -0
  288. package/src/server-locale.ts +59 -0
  289. package/src/settings-tools.ts +170 -0
  290. package/src/settings.test.ts +402 -0
  291. package/src/settings.ts +114 -0
  292. package/src/steps/app-config.ts +75 -0
  293. package/src/steps/dashboard.ts +73 -0
  294. package/src/steps/failure-dashboard.ts +38 -0
  295. package/src/steps/index.ts +2 -0
  296. package/src/steps/steps.test.ts +216 -0
  297. package/src/tool-names.ts +26 -0
  298. package/src/tools/cluster-compare.ts +28 -0
  299. package/src/tools/element.ts +35 -0
  300. package/src/tools/engine-compare.test.ts +210 -0
  301. package/src/tools/engine-compare.ts +30 -0
  302. package/src/tools/engine-landscape.test.ts +138 -0
  303. package/src/tools/engine-landscape.ts +21 -0
  304. package/src/tools/failures.ts +19 -0
  305. package/src/tools/health.test.ts +156 -0
  306. package/src/tools/health.ts +29 -0
  307. package/src/tools/index.ts +44 -0
  308. package/src/tools/performance.test.ts +210 -0
  309. package/src/tools/performance.ts +38 -0
  310. package/src/tools/version-compare.test.ts +97 -0
  311. package/src/tools/version-compare.ts +28 -0
  312. package/src/toolsets.test.ts +107 -0
  313. package/src/toolsets.ts +56 -0
  314. package/src/view-title.test.ts +123 -0
  315. package/src/widget-tools/comparisons.test.ts +170 -0
  316. package/src/widget-tools/comparisons.ts +229 -0
  317. package/src/widget-tools/shared.ts +67 -0
  318. package/src/widget-tools.test.ts +306 -0
  319. package/src/widget-tools.ts +375 -0
  320. package/src/widgets/analytics-dashboard/activity-bottleneck-table.tsx +179 -0
  321. package/src/widgets/analytics-dashboard/definition-breakdown.tsx +108 -0
  322. package/src/widgets/analytics-dashboard/execution-performance-kpi.tsx +94 -0
  323. package/src/widgets/analytics-dashboard/execution-summary-kpi.tsx +122 -0
  324. package/src/widgets/analytics-dashboard/lib.ts +43 -0
  325. package/src/widgets/bpmn-heatmap.test.tsx +75 -0
  326. package/src/widgets/bpmn-heatmap.tsx +127 -0
  327. package/src/widgets/catalogue-sync.test.ts +16 -0
  328. package/src/widgets/cluster-compare.tsx +148 -0
  329. package/src/widgets/comparison-shared.test.ts +237 -0
  330. package/src/widgets/comparison-shared.tsx +371 -0
  331. package/src/widgets/comparison-widgets.test.tsx +307 -0
  332. package/src/widgets/dashboard-widgets.test.tsx +397 -0
  333. package/src/widgets/engine-compare.tsx +87 -0
  334. package/src/widgets/engine-landscape.tsx +352 -0
  335. package/src/widgets/failure-dashboard/error-patterns-table.tsx +122 -0
  336. package/src/widgets/failure-dashboard/failure-rate-table.test.ts +80 -0
  337. package/src/widgets/failure-dashboard/failure-rate-table.tsx +170 -0
  338. package/src/widgets/failure-dashboard/lib.ts +32 -0
  339. package/src/widgets/failure-dashboard/summary-kpi.tsx +122 -0
  340. package/src/widgets/failure-landscape-widgets.test.tsx +393 -0
  341. package/src/widgets/figure.tsx +79 -0
  342. package/src/widgets/hand-off.test.ts +126 -0
  343. package/src/widgets/hand-off.ts +102 -0
  344. package/src/widgets/index.ts +124 -0
  345. package/src/widgets/model-descriptions.dashboard.test.ts +280 -0
  346. package/src/widgets/model-descriptions.test.ts +251 -0
  347. package/src/widgets/model-descriptions.ts +428 -0
  348. package/src/widgets/names.test.ts +32 -0
  349. package/src/widgets/names.ts +39 -0
  350. package/src/widgets/query-gate.test.tsx +57 -0
  351. package/src/widgets/query-gate.tsx +50 -0
  352. package/src/widgets/settings-section.test.tsx +174 -0
  353. package/src/widgets/settings-section.tsx +244 -0
  354. package/src/widgets/version-compare.test.ts +68 -0
  355. package/src/widgets/version-compare.tsx +96 -0
  356. package/src/widgets/view-meta.ts +18 -0
  357. package/src/widgets/widget-render.test-support.tsx +103 -0
package/src/module.ts ADDED
@@ -0,0 +1,211 @@
1
+ import { z } from "zod"
2
+ import type { AppPlugin } from "@miragon/mcp-toolkit-core"
3
+ import type { MCPServer } from "mcp-use"
4
+ import { createPlugin } from "./plugin.js"
5
+ import { analyticsToolsets } from "./toolsets.js"
6
+ import { analyticsInstructions } from "./instructions.js"
7
+ import type { FetchBpmnXml } from "./bpmn-name-lookup.js"
8
+ import type { ProfileSource } from "./server-locale.js"
9
+ import { NO_ENGINE_SCOPE_MESSAGE } from "./engine-ids.js"
10
+
11
+ /**
12
+ * Self-contained module definition for host apps: everything the app needs to
13
+ * mount the analytics module without knowing its config surface. Conforms
14
+ * structurally to the app's `ModuleDefinition` port — no import of the app.
15
+ */
16
+
17
+ /** The longest delay a Node timer holds — a longer deadline would fire at once. */
18
+ const MAX_TIMEOUT_MS = 2_147_483_647
19
+
20
+ const HEADERS_ERROR =
21
+ 'PROMETHEUS_HEADERS must be a JSON object of string header values, e.g. {"X-Scope-OrgID":"tenant-a"}'
22
+
23
+ /** `PROMETHEUS_HEADERS` JSON text (env) or an object (direct caller) → a header record. */
24
+ const headersSchema = z
25
+ .union([
26
+ z.record(z.string(), z.string()),
27
+ z.string().transform((text, ctx) => {
28
+ try {
29
+ return JSON.parse(text) as unknown
30
+ } catch {
31
+ // Never echo the text: header values are often secrets (API keys).
32
+ ctx.addIssue({ code: "custom", message: HEADERS_ERROR })
33
+ return z.NEVER
34
+ }
35
+ }),
36
+ ])
37
+ .pipe(z.record(z.string(), z.string({ error: HEADERS_ERROR }), { error: HEADERS_ERROR }))
38
+
39
+ const analyticsConfigSchema = z.object({
40
+ url: z.string().default("http://localhost:9090"),
41
+ /**
42
+ * Prometheus auth (all optional): a bearer token, OR basic auth — explicit
43
+ * username + password or userinfo in `url` — plus extra headers (tenant
44
+ * ids). The client rejects ambiguous combinations at boot and keeps every
45
+ * credential out of model-visible errors.
46
+ */
47
+ bearerToken: z.string().optional(),
48
+ username: z.string().optional(),
49
+ password: z.string().optional(),
50
+ headers: headersSchema.optional(),
51
+ // PROMETHEUS_TIMEOUT_MS — strict: a typo fails the boot instead of being
52
+ // coerced into some other deadline. Unset = the client default (30 s).
53
+ timeoutMs: z
54
+ .union([
55
+ z.number(),
56
+ z
57
+ .string()
58
+ .regex(/^\d+$/, "PROMETHEUS_TIMEOUT_MS must be a whole number of milliseconds")
59
+ .transform(Number),
60
+ ])
61
+ .pipe(
62
+ z
63
+ .number()
64
+ .int("PROMETHEUS_TIMEOUT_MS must be a whole number of milliseconds")
65
+ .min(1, "PROMETHEUS_TIMEOUT_MS must be at least 1 ms")
66
+ .max(MAX_TIMEOUT_MS, `PROMETHEUS_TIMEOUT_MS must be at most ${MAX_TIMEOUT_MS} ms`),
67
+ )
68
+ .optional(),
69
+ /**
70
+ * `ANALYTICS_ENGINE_IDS` — the engine ids a STANDALONE analytics boot may
71
+ * read (comma-separated `engine_id` values). Only the fallback: when the
72
+ * composition root injects its configured engines (`shared.engineIds`,
73
+ * from camunda7) those win. See `engine-ids.ts`.
74
+ */
75
+ engineIds: z
76
+ .union([
77
+ z.array(z.string()),
78
+ z.string().transform((text) =>
79
+ text
80
+ .split(",")
81
+ .map((id) => id.trim())
82
+ .filter((id) => id.length > 0),
83
+ ),
84
+ ])
85
+ .optional(),
86
+ /**
87
+ * The effective toolset the composition root resolved from the
88
+ * `MCP_ACTIVE_MODULES` suffix — always a concrete declared name there. The
89
+ * module's tools are read-only by nature; the toolset only gates the one
90
+ * durable write, `analytics_save_settings` (`standard` only). Kept a plain
91
+ * string here so a direct caller's unknown name still fails closed (warning
92
+ * + `read-only`) instead of throwing — `createPlugin` below resolves it.
93
+ */
94
+ toolset: z.string().optional(),
95
+ })
96
+
97
+ /** Cross-module resources the host app threads in (structural, app-owned). */
98
+ interface AnalyticsModuleShared {
99
+ profileStore?: ProfileSource
100
+ fetchBpmnXml?: FetchBpmnXml
101
+ /**
102
+ * The engine ids this server is configured for (the camunda7 module's
103
+ * engines) — analytics reads only these. Wins over `ANALYTICS_ENGINE_IDS`.
104
+ */
105
+ engineIds?: readonly string[]
106
+ }
107
+
108
+ /**
109
+ * The engine ids analytics covers: the injected ones, else
110
+ * `ANALYTICS_ENGINE_IDS`, else none (fail-closed). Warns at boot for the two
111
+ * surprising cases — no scope at all, and an env list the injected one
112
+ * overrides.
113
+ */
114
+ function effectiveEngineIds(
115
+ injected: readonly string[] | undefined,
116
+ fromEnv: readonly string[] | undefined,
117
+ ): readonly string[] {
118
+ if (injected && injected.length > 0) {
119
+ if (fromEnv && fromEnv.length > 0) {
120
+ console.warn(
121
+ `[analytics] ANALYTICS_ENGINE_IDS is ignored: the server's configured engines (${injected.join(", ")}) scope analytics.`,
122
+ )
123
+ }
124
+ return injected
125
+ }
126
+ if (fromEnv && fromEnv.length > 0) return fromEnv
127
+ console.warn(`[analytics] ${NO_ENGINE_SCOPE_MESSAGE}`)
128
+ return []
129
+ }
130
+
131
+ export const analyticsModule = {
132
+ name: "analytics",
133
+
134
+ /**
135
+ * Pure env → raw-config mapping. `PROMETHEUS_URL` is trimmed so an empty
136
+ * assignment (`PROMETHEUS_URL=` left in a .env or compose env_file) falls
137
+ * through to the schema default instead of producing an invalid-URL client.
138
+ */
139
+ configFromEnv(env: NodeJS.ProcessEnv): Record<string, unknown> {
140
+ // Blank = unset for every variable (a `KEY=` left in a .env file).
141
+ // Credentials are passed verbatim otherwise — never trimmed into another secret.
142
+ const trimmed = (name: string) => env[name]?.trim() || undefined
143
+ const verbatim = (name: string) => (env[name]?.trim() ? env[name] : undefined)
144
+ return {
145
+ url: trimmed("PROMETHEUS_URL"),
146
+ bearerToken: verbatim("PROMETHEUS_BEARER_TOKEN"),
147
+ username: verbatim("PROMETHEUS_USERNAME"),
148
+ password: verbatim("PROMETHEUS_PASSWORD"),
149
+ headers: trimmed("PROMETHEUS_HEADERS"),
150
+ timeoutMs: trimmed("PROMETHEUS_TIMEOUT_MS"),
151
+ engineIds: trimmed("ANALYTICS_ENGINE_IDS"),
152
+ }
153
+ },
154
+
155
+ /** This module's slice of the app's unknown-env-var typo warner. */
156
+ knownEnvVars: [
157
+ "PROMETHEUS_URL",
158
+ "PROMETHEUS_BEARER_TOKEN",
159
+ "PROMETHEUS_USERNAME",
160
+ "PROMETHEUS_PASSWORD",
161
+ "PROMETHEUS_HEADERS",
162
+ "PROMETHEUS_TIMEOUT_MS",
163
+ "ANALYTICS_ENGINE_IDS",
164
+ ] as const,
165
+
166
+ /**
167
+ * `read-only` (the floor) | `standard` (adds the caller's own settings save,
168
+ * `analytics_save_settings`; everything else is read-only anyway). No suffix
169
+ * means `read-only` without OAuth and `standard` with it; an empty or unknown
170
+ * suffix falls back to `read-only`. See `toolsets.ts`.
171
+ */
172
+ toolsets: analyticsToolsets,
173
+
174
+ /**
175
+ * Boot-time hints for active deployments. The code default (:9090) matches a
176
+ * bare Prometheus, NOT the repo's compose stack (:8460) — without the hint
177
+ * every analytics query 404s silently.
178
+ */
179
+ bootWarnings(env: NodeJS.ProcessEnv): string[] {
180
+ if (env.PROMETHEUS_URL?.trim()) return []
181
+ return [
182
+ "PROMETHEUS_URL is not set — defaulting to http://localhost:9090. The repo's Compose stack publishes Prometheus on :8460 (PROMETHEUS_URL=http://localhost:8460).",
183
+ ]
184
+ },
185
+
186
+ /**
187
+ * The module's server-instructions snippet: what `engine` means here (a
188
+ * metric filter over the configured engines — their fleet aggregate when
189
+ * omitted, camunda7's saved default does not apply), the periods and the
190
+ * health routing.
191
+ */
192
+ instructions(): string {
193
+ return analyticsInstructions()
194
+ },
195
+
196
+ createPlugin(
197
+ config: Record<string, unknown>,
198
+ shared: AnalyticsModuleShared,
199
+ ): AppPlugin<MCPServer> {
200
+ const { toolset, engineIds, ...parsed } = analyticsConfigSchema.parse(config)
201
+ return createPlugin({
202
+ ...parsed,
203
+ engineIds: effectiveEngineIds(shared.engineIds, engineIds),
204
+ // Resolved once, here: missing → the read-only floor, unknown → warning +
205
+ // floor. The plugin only ever sees a declared name.
206
+ toolset: analyticsToolsets.resolve(toolset),
207
+ fetchBpmnXml: shared.fetchBpmnXml,
208
+ profileStore: shared.profileStore,
209
+ })
210
+ },
211
+ }
@@ -0,0 +1,141 @@
1
+ import { describe, expect, it, vi } from "vitest"
2
+ import type {
3
+ AnalyticsDashboardData,
4
+ EngineLandscapeResult,
5
+ FailureDashboardData,
6
+ } from "@miragon/pulse-analytics-client"
7
+ import { namedDashboard, namedFailureDashboard, namedLandscape } from "./named-views.js"
8
+ import { createNameLookup, type NameLookup } from "./bpmn-name-lookup.js"
9
+ import { elementNameOf, processNameOf } from "./bpmn-names.js"
10
+
11
+ /**
12
+ * Every view asks for the names of exactly the ids it lists, no more, by
13
+ * priority, on the engines its rows come from.
14
+ */
15
+ describe("named views", () => {
16
+ const lookup = () => vi.fn<NameLookup>(() => Promise.resolve({}))
17
+
18
+ it("the dashboard: its scope, the elements of its activities, then its definitions", async () => {
19
+ const namesFor = lookup()
20
+ const data = {
21
+ processDefinitionKey: "order",
22
+ engines: ["prod-b", "prod-a", "prod-c"],
23
+ reportingEngines: ["prod-a", "prod-b"],
24
+ definitionBreakdown: [{ processDefinitionKey: "order" }, { processDefinitionKey: "refund" }],
25
+ activityBreakdown: [
26
+ { processDefinitionKey: "order", activityId: "Task_A" },
27
+ { processDefinitionKey: "order", activityId: "Task_B" },
28
+ ],
29
+ } as unknown as AnalyticsDashboardData
30
+ const named = await namedDashboard(data, namesFor)
31
+ expect(named.names).toEqual({})
32
+ const [keys, request] = namesFor.mock.calls[0]
33
+ expect([...keys]).toEqual(["order", "order", "order", "order", "refund"])
34
+ expect(request?.elementIds).toEqual({ order: ["Task_A", "Task_B"], refund: [] })
35
+ // The reporting engines, in the scope's (configuration) order; a silent one has no rows.
36
+ expect(request?.engines).toEqual(["prod-b", "prod-a"])
37
+ })
38
+
39
+ it("the dashboard reads on its whole scope when the payload does not say which engines report", async () => {
40
+ const namesFor = lookup()
41
+ await namedDashboard(
42
+ {
43
+ processDefinitionKey: null,
44
+ engines: ["prod-a", "prod-b"],
45
+ definitionBreakdown: [],
46
+ activityBreakdown: [],
47
+ } as unknown as AnalyticsDashboardData,
48
+ namesFor,
49
+ )
50
+ expect(namesFor.mock.calls[0][1]?.engines).toEqual(["prod-a", "prod-b"])
51
+ })
52
+
53
+ it("names a process key such as constructor like any other", async () => {
54
+ const namesFor = lookup()
55
+ await namedDashboard(
56
+ {
57
+ processDefinitionKey: null,
58
+ engines: null,
59
+ definitionBreakdown: [{ processDefinitionKey: "constructor" }],
60
+ activityBreakdown: [{ processDefinitionKey: "constructor", activityId: "Task_A" }],
61
+ } as unknown as AnalyticsDashboardData,
62
+ namesFor,
63
+ )
64
+ expect(namesFor.mock.calls[0][1]?.elementIds).toEqual({ constructor: ["Task_A"] })
65
+ expect(namesFor.mock.calls[0][1]?.engines).toBeNull()
66
+ })
67
+
68
+ // The lookup reads at most 40 models per view: a fleet with more
69
+ // definitions must not lose the names of its bottleneck activities to the
70
+ // long tail of the breakdown (sorted by starts).
71
+ it("keeps the activities' names when the fleet has more than 40 definitions", async () => {
72
+ const fetchBpmnXml = vi.fn((key: string) =>
73
+ Promise.resolve(
74
+ `<process id="${key}" name="Process ${key}"><userTask id="Task_Slow" name="Review"/></process>`,
75
+ ),
76
+ )
77
+ const definitions = Array.from({ length: 45 }, (_, i) => ({ processDefinitionKey: `p${i}` }))
78
+ const data = {
79
+ processDefinitionKey: null,
80
+ engines: ["prod-a"],
81
+ reportingEngines: ["prod-a"],
82
+ definitionBreakdown: definitions,
83
+ // A long-running, low-volume process: the last of the breakdown.
84
+ activityBreakdown: [{ processDefinitionKey: "p44", activityId: "Task_Slow" }],
85
+ } as unknown as AnalyticsDashboardData
86
+ const { names } = await namedDashboard(data, createNameLookup(fetchBpmnXml))
87
+ expect(elementNameOf(names, "p44", "Task_Slow")).toBe("Review")
88
+ expect(processNameOf(names, "p44")).toBe("Process p44")
89
+ expect(fetchBpmnXml).toHaveBeenCalledTimes(40)
90
+ // The cap falls on the end of the breakdown instead.
91
+ expect(processNameOf(names, "p0")).toBe("Process p0")
92
+ expect(processNameOf(names, "p40")).toBeNull()
93
+ })
94
+
95
+ it("the failure dashboard: process names only, its most affected first, on its engines", async () => {
96
+ const failures = lookup()
97
+ await namedFailureDashboard(
98
+ {
99
+ engines: ["prod-a", "prod-b"],
100
+ errorPatterns: [{ processDefinitionKey: "a" }],
101
+ processBreakdown: [{ processDefinitionKey: "b" }],
102
+ mostAffectedProcess: "b",
103
+ } as unknown as FailureDashboardData,
104
+ failures,
105
+ )
106
+ expect([...failures.mock.calls[0][0]]).toEqual(["b", "a", "b"])
107
+ expect(failures.mock.calls[0][1]).toEqual({
108
+ elementIds: { a: [], b: [] },
109
+ engines: ["prod-a", "prod-b"],
110
+ })
111
+
112
+ const none = lookup()
113
+ await namedFailureDashboard(
114
+ {
115
+ engines: ["prod-a"],
116
+ errorPatterns: [],
117
+ processBreakdown: [],
118
+ mostAffectedProcess: null,
119
+ } as unknown as FailureDashboardData,
120
+ none,
121
+ )
122
+ expect([...none.mock.calls[0][0]]).toEqual([])
123
+ })
124
+
125
+ it("the landscape: each process read on the engines it runs on", async () => {
126
+ const landscape = lookup()
127
+ await namedLandscape(
128
+ {
129
+ processes: [
130
+ { processDefinitionKey: "c", engineIds: ["prod-b", "prod-c"] },
131
+ { processDefinitionKey: "d", engineIds: ["prod-a"] },
132
+ ],
133
+ } as unknown as EngineLandscapeResult,
134
+ landscape,
135
+ )
136
+ expect(landscape.mock.calls[0][1]).toEqual({
137
+ elementIds: { c: [], d: [] },
138
+ enginesByKey: { c: ["prod-b", "prod-c"], d: ["prod-a"] },
139
+ })
140
+ })
141
+ })
@@ -0,0 +1,97 @@
1
+ import type {
2
+ AnalyticsDashboardData,
3
+ EngineLandscapeResult,
4
+ FailureDashboardData,
5
+ } from "@miragon/pulse-analytics-client"
6
+ import type { NameLookup } from "./bpmn-name-lookup.js"
7
+ import type { Named } from "./bpmn-names.js"
8
+
9
+ /**
10
+ * The names behind the ids a view lists (U13), added to its payload once,
11
+ * here — the show tool, its `*_data` feed and the pipeline step of a view
12
+ * all pass their figures through the same function, so the three paths show
13
+ * the same names. Names are enrichment: a model that cannot be read leaves
14
+ * its key out of `names` and the view shows the id, never fails.
15
+ *
16
+ * Each model is read on the engines the view's rows come from (a process
17
+ * that runs only on the third engine is still named), and a view names its
18
+ * keys by priority: the lookup reads at most so many models per view.
19
+ */
20
+
21
+ /**
22
+ * The dashboard: its scope, then the processes of the activity breakdown
23
+ * (whose element names the activity table shows: a bottleneck in a
24
+ * low-volume process must not lose its names to the long tail of the
25
+ * breakdown), then the per-definition breakdown. Its models are read on the
26
+ * engines in scope that report, in configuration order.
27
+ */
28
+ export async function namedDashboard(
29
+ data: AnalyticsDashboardData,
30
+ namesFor: NameLookup,
31
+ ): Promise<Named<AnalyticsDashboardData>> {
32
+ const keys = [
33
+ ...(data.processDefinitionKey ? [data.processDefinitionKey] : []),
34
+ ...data.activityBreakdown.map((a) => a.processDefinitionKey),
35
+ ...data.definitionBreakdown.map((d) => d.processDefinitionKey),
36
+ ]
37
+ const elementIds = new Map<string, string[]>(keys.map((key) => [key, []]))
38
+ for (const a of data.activityBreakdown) elementIds.get(a.processDefinitionKey)?.push(a.activityId)
39
+ return {
40
+ ...data,
41
+ names: await namesFor(keys, {
42
+ elementIds: Object.fromEntries(elementIds),
43
+ engines: reportingInOrder(data.engines, data.reportingEngines),
44
+ }),
45
+ }
46
+ }
47
+
48
+ /** The failure dashboard: the processes of its incident groups and its per-process table. */
49
+ export async function namedFailureDashboard(
50
+ data: FailureDashboardData,
51
+ namesFor: NameLookup,
52
+ ): Promise<Named<FailureDashboardData>> {
53
+ const keys = [
54
+ ...(data.mostAffectedProcess ? [data.mostAffectedProcess] : []),
55
+ ...data.errorPatterns.map((p) => p.processDefinitionKey),
56
+ ...data.processBreakdown.map((p) => p.processDefinitionKey),
57
+ ]
58
+ return {
59
+ ...data,
60
+ names: await namesFor(keys, { elementIds: noElements(keys), engines: data.engines }),
61
+ }
62
+ }
63
+
64
+ /** The landscape: the processes of its what-runs-where matrix, each read on the engines it runs on. */
65
+ export async function namedLandscape(
66
+ data: EngineLandscapeResult,
67
+ namesFor: NameLookup,
68
+ ): Promise<Named<EngineLandscapeResult>> {
69
+ const keys = data.processes.map((p) => p.processDefinitionKey)
70
+ return {
71
+ ...data,
72
+ names: await namesFor(keys, {
73
+ elementIds: noElements(keys),
74
+ enginesByKey: Object.fromEntries(
75
+ data.processes.map((p) => [p.processDefinitionKey, p.engineIds]),
76
+ ),
77
+ }),
78
+ }
79
+ }
80
+
81
+ /** Process names only: no element of these models is on screen. */
82
+ function noElements(keys: readonly string[]): Record<string, string[]> {
83
+ return Object.fromEntries(keys.map((key) => [key, []]))
84
+ }
85
+
86
+ /**
87
+ * The engines in scope (configuration order: the default engine first) that
88
+ * report metrics: the rows come from them, a silent engine has none. All of
89
+ * the scope when the payload does not say which report.
90
+ */
91
+ function reportingInOrder(
92
+ engines: readonly string[] | null,
93
+ reporting: readonly string[] | undefined,
94
+ ): readonly string[] | null {
95
+ if (!engines || !reporting) return engines ?? reporting ?? null
96
+ return engines.filter((id) => reporting.includes(id))
97
+ }
package/src/plugin.ts ADDED
@@ -0,0 +1,94 @@
1
+ import type { AppPlugin } from "@miragon/mcp-toolkit-core"
2
+ import type { MCPServer } from "mcp-use"
3
+ import { installMcpRequestContext } from "@miragon/pulse-widget-shell/server"
4
+ import { createPrometheusClient, type PrometheusConfig } from "@miragon/pulse-analytics-client"
5
+ import { registerTools } from "./tools/index.js"
6
+ import { registerSettingsTools } from "./settings-tools.js"
7
+ import { registerWidgetTools } from "./widget-tools.js"
8
+ import { createNameLookup, type FetchBpmnXml } from "./bpmn-name-lookup.js"
9
+ import { definition } from "./definition.js"
10
+ import type { ProfileSource } from "./server-locale.js"
11
+ import { analyticsToolsets } from "./toolsets.js"
12
+ import { createEngineScope } from "./engine-ids.js"
13
+ import type { AnalyticsAppConfig } from "./steps/app-config.js"
14
+
15
+ /**
16
+ * `PrometheusConfig` carries the connection: the base URL (e.g.
17
+ * http://localhost:9090), optional bearer/basic auth + extra headers, and the
18
+ * per-query deadline (`timeoutMs`).
19
+ */
20
+ export interface AnalyticsPluginConfig extends PrometheusConfig {
21
+ /**
22
+ * Optional BPMN-XML lookup used by widget tools that enrich metric data with
23
+ * engine lookups — the BPMN heatmap's diagram, and the process and activity
24
+ * names the dashboards, the failure dashboard and the landscape show in
25
+ * place of ids. Injected by the host app (which owns the engine client) so
26
+ * this module stays free of engine SDKs. When absent, the heatmap degrades
27
+ * to a non-diagram fallback and the views show ids.
28
+ */
29
+ fetchBpmnXml?: FetchBpmnXml
30
+ /**
31
+ * Profile store (shared with camunda7): locale for model-facing summaries,
32
+ * plus this module's own settings slice (`modules.analytics`) — read for
33
+ * period/minBucketSize defaults, written by `analytics_save_settings`.
34
+ */
35
+ profileStore?: ProfileSource
36
+ /**
37
+ * The module's toolset (`analyticsModule` passes the one the composition
38
+ * root resolved). `"read-only"` — also what an ABSENT or unknown toolset
39
+ * resolves to (an unknown name warns) — registers no settings save tool;
40
+ * `"standard"` registers it, given a writable `profileStore`. Typed `string`
41
+ * like camunda7's, so callers can pass a configured value through; it is
42
+ * resolved fail-closed here.
43
+ */
44
+ toolset?: string
45
+ /**
46
+ * The engine ids analytics may read — the server's configured engines.
47
+ * Empty or absent: every engine-reading tool refuses (fail-closed), see
48
+ * `engine-ids.ts`.
49
+ */
50
+ engineIds?: readonly string[]
51
+ }
52
+
53
+ export function createPlugin(config: AnalyticsPluginConfig): AppPlugin<MCPServer> {
54
+ const client = createPrometheusClient({
55
+ url: config.url,
56
+ bearerToken: config.bearerToken,
57
+ username: config.username,
58
+ password: config.password,
59
+ headers: config.headers,
60
+ timeoutMs: config.timeoutMs,
61
+ })
62
+ const engineScope = createEngineScope(config.engineIds)
63
+ // ONE name lookup (and its per-caller cache) for the tools, the feeds and the steps.
64
+ const namesFor = createNameLookup(config.fetchBpmnXml)
65
+ // The steps get the profile store too: a step's omitted period resolves to
66
+ // the caller's saved default, like every tool's.
67
+ const appConfig = {
68
+ client,
69
+ engineScope,
70
+ profileStore: config.profileStore,
71
+ namesFor,
72
+ } satisfies AnalyticsAppConfig
73
+ return {
74
+ definition,
75
+ appConfig,
76
+ // Tools and feeds resolve the caller (saved defaults, summary locale) from
77
+ // the handler ctx. The pipeline steps get none, so their saved-period
78
+ // lookup reads the ambient request info — installed here like camunda7
79
+ // does (idempotent; the host installs it too).
80
+ registerTools: (server) => {
81
+ installMcpRequestContext(server)
82
+ registerTools(server, client, engineScope, config.profileStore)
83
+ },
84
+ registerWidgetTools: (server) => {
85
+ registerWidgetTools(server, client, {
86
+ fetchBpmnXml: config.fetchBpmnXml,
87
+ namesFor,
88
+ profileStore: config.profileStore,
89
+ engineScope,
90
+ })
91
+ registerSettingsTools(server, config.profileStore, analyticsToolsets.resolve(config.toolset))
92
+ },
93
+ }
94
+ }