bi-superpowers 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/.claude-plugin/marketplace.json +31 -0
  2. package/.claude-plugin/plugin.json +34 -0
  3. package/.claude-plugin/skill-manifest.json +79 -0
  4. package/.mcp.json +13 -0
  5. package/.plugin/plugin.json +14 -0
  6. package/LICENSE +21 -0
  7. package/README.md +849 -0
  8. package/bin/build-plugin.js +97 -0
  9. package/bin/cli.js +891 -0
  10. package/bin/commands/autoupdate.js +128 -0
  11. package/bin/commands/build-desktop.js +368 -0
  12. package/bin/commands/create-from-template.js +165 -0
  13. package/bin/commands/diff.js +435 -0
  14. package/bin/commands/install.js +542 -0
  15. package/bin/commands/lint.js +441 -0
  16. package/bin/commands/mcp-setup.js +255 -0
  17. package/bin/commands/session-update.js +204 -0
  18. package/bin/commands/smoke-test.js +20 -0
  19. package/bin/commands/uninstall.js +611 -0
  20. package/bin/commands/update-check.js +427 -0
  21. package/bin/commands/validate-cases.js +264 -0
  22. package/bin/commands/validate-projects.js +426 -0
  23. package/bin/commands/watch.js +251 -0
  24. package/bin/lib/agents.js +62 -0
  25. package/bin/lib/base-template-smoke.js +299 -0
  26. package/bin/lib/claude-hooks.js +160 -0
  27. package/bin/lib/generators/claude-plugin.js +529 -0
  28. package/bin/lib/generators/index.js +116 -0
  29. package/bin/lib/generators/shared.js +257 -0
  30. package/bin/lib/mcp-config.js +835 -0
  31. package/bin/lib/microsoft-mcp.js +206 -0
  32. package/bin/lib/powerbi-mcp-session.js +140 -0
  33. package/bin/lib/skills.js +164 -0
  34. package/bin/lib/template-scaffold.js +366 -0
  35. package/bin/mcp/powerbi-modeling-launcher.js +42 -0
  36. package/bin/postinstall.js +50 -0
  37. package/bin/utils/mcp-detect.js +346 -0
  38. package/bin/utils/tui.js +314 -0
  39. package/commands/bi-connect.md +520 -0
  40. package/commands/bi-dax.md +464 -0
  41. package/commands/bi-kickoff.md +550 -0
  42. package/commands/bi-modeling.md +485 -0
  43. package/commands/bi-performance.md +521 -0
  44. package/commands/bi-powerquery.md +229 -0
  45. package/commands/bi-refactor.md +249 -0
  46. package/commands/bi-scorecard.md +268 -0
  47. package/commands/bi-start.md +272 -0
  48. package/config.example.json +23 -0
  49. package/config.json +23 -0
  50. package/desktop-extension/manifest.json +30 -0
  51. package/desktop-extension/package.json +10 -0
  52. package/desktop-extension/server.js +137 -0
  53. package/package.json +94 -0
  54. package/skills/bi-connect/SKILL.md +522 -0
  55. package/skills/bi-connect/scripts/update-check.js +427 -0
  56. package/skills/bi-dax/SKILL.md +466 -0
  57. package/skills/bi-dax/scripts/update-check.js +427 -0
  58. package/skills/bi-kickoff/SKILL.md +552 -0
  59. package/skills/bi-kickoff/references/flow.html +78 -0
  60. package/skills/bi-kickoff/references/flow.md +62 -0
  61. package/skills/bi-kickoff/scripts/update-check.js +427 -0
  62. package/skills/bi-modeling/SKILL.md +487 -0
  63. package/skills/bi-modeling/scripts/update-check.js +427 -0
  64. package/skills/bi-performance/SKILL.md +523 -0
  65. package/skills/bi-performance/scripts/install-tabular-editor.ps1 +159 -0
  66. package/skills/bi-performance/scripts/run-bpa.ps1 +265 -0
  67. package/skills/bi-performance/scripts/update-check.js +427 -0
  68. package/skills/bi-powerquery/SKILL.md +231 -0
  69. package/skills/bi-powerquery/references/base-template-data-contract.md +323 -0
  70. package/skills/bi-powerquery/references/power-query-standards.md +74 -0
  71. package/skills/bi-powerquery/scripts/new-powerquery-staging.ps1 +371 -0
  72. package/skills/bi-powerquery/scripts/test-powerquery-contract.ps1 +225 -0
  73. package/skills/bi-powerquery/scripts/update-check.js +427 -0
  74. package/skills/bi-refactor/SKILL.md +251 -0
  75. package/skills/bi-refactor/references/flow.md +27 -0
  76. package/skills/bi-refactor/scripts/update-check.js +427 -0
  77. package/skills/bi-scorecard/SKILL.md +270 -0
  78. package/skills/bi-scorecard/examples/base-template-scorecard-overlay.json +82 -0
  79. package/skills/bi-scorecard/scripts/new-scorecard-blueprint-from-base-template.ps1 +124 -0
  80. package/skills/bi-scorecard/scripts/powerbi-goal-status-rules-api.ps1 +39 -0
  81. package/skills/bi-scorecard/scripts/powerbi-goal-values-api.ps1 +48 -0
  82. package/skills/bi-scorecard/scripts/powerbi-goals-api.ps1 +68 -0
  83. package/skills/bi-scorecard/scripts/powerbi-rest-common.ps1 +197 -0
  84. package/skills/bi-scorecard/scripts/powerbi-scorecards-api.ps1 +53 -0
  85. package/skills/bi-scorecard/scripts/update-check.js +427 -0
  86. package/skills/bi-start/SKILL.md +274 -0
  87. package/skills/bi-start/scripts/update-check.js +427 -0
  88. package/src/content/base.md +197 -0
  89. package/src/content/mcp-requirements.json +57 -0
  90. package/src/content/routing.md +201 -0
  91. package/src/content/skills/bi-connect.md +493 -0
  92. package/src/content/skills/bi-dax.md +437 -0
  93. package/src/content/skills/bi-kickoff/SKILL.md +523 -0
  94. package/src/content/skills/bi-kickoff/references/flow.html +78 -0
  95. package/src/content/skills/bi-kickoff/references/flow.md +62 -0
  96. package/src/content/skills/bi-modeling.md +458 -0
  97. package/src/content/skills/bi-performance/SKILL.md +494 -0
  98. package/src/content/skills/bi-performance/scripts/install-tabular-editor.ps1 +159 -0
  99. package/src/content/skills/bi-performance/scripts/run-bpa.ps1 +265 -0
  100. package/src/content/skills/bi-powerquery/SKILL.md +202 -0
  101. package/src/content/skills/bi-powerquery/references/base-template-data-contract.md +323 -0
  102. package/src/content/skills/bi-powerquery/references/power-query-standards.md +74 -0
  103. package/src/content/skills/bi-powerquery/scripts/new-powerquery-staging.ps1 +371 -0
  104. package/src/content/skills/bi-powerquery/scripts/test-powerquery-contract.ps1 +225 -0
  105. package/src/content/skills/bi-refactor/SKILL.md +222 -0
  106. package/src/content/skills/bi-refactor/references/flow.md +27 -0
  107. package/src/content/skills/bi-scorecard/SKILL.md +241 -0
  108. package/src/content/skills/bi-scorecard/examples/base-template-scorecard-blueprint.expected.json +105 -0
  109. package/src/content/skills/bi-scorecard/examples/base-template-scorecard-overlay.json +82 -0
  110. package/src/content/skills/bi-scorecard/scripts/new-scorecard-blueprint-from-base-template.ps1 +124 -0
  111. package/src/content/skills/bi-scorecard/scripts/powerbi-goal-status-rules-api.ps1 +39 -0
  112. package/src/content/skills/bi-scorecard/scripts/powerbi-goal-values-api.ps1 +48 -0
  113. package/src/content/skills/bi-scorecard/scripts/powerbi-goals-api.ps1 +68 -0
  114. package/src/content/skills/bi-scorecard/scripts/powerbi-rest-common.ps1 +197 -0
  115. package/src/content/skills/bi-scorecard/scripts/powerbi-scorecards-api.ps1 +53 -0
  116. package/src/content/skills/bi-start.md +266 -0
  117. package/templates/base-template/AGENTS.md +33 -0
  118. package/templates/base-template/base-template.Report/.platform +11 -0
  119. package/templates/base-template/base-template.Report/StaticResources/RegisteredResources/BISuperpowers.json +3888 -0
  120. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BaseThemes/CY18SU07.json +177 -0
  121. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BaseThemes/Fluent2-CY26SU03.json +4104 -0
  122. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/AccessibleCityPark.json +26 -0
  123. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/AccessibleDefault.json +26 -0
  124. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/AccessibleNeutral.json +26 -0
  125. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/AccessibleOrchid.json +26 -0
  126. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/AccessibleTidal.json +26 -0
  127. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Bloom.json +139 -0
  128. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/CityPark.json +40 -0
  129. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Classroom.json +40 -0
  130. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/ColorblindSafe.json +48 -0
  131. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/CopilotDefault.json +1861 -0
  132. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Divergent.json +127 -0
  133. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Electric.json +48 -0
  134. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Frontier.json +136 -0
  135. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/HighContrast.json +40 -0
  136. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Highrise.json +41 -0
  137. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Innovate.json +227 -0
  138. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/NewExecutive.json +41 -0
  139. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Solar.json +33 -0
  140. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Storm.json +25 -0
  141. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Sunset.json +48 -0
  142. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Temperature.json +33 -0
  143. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Tidal.json +100 -0
  144. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Twilight.json +40 -0
  145. package/templates/base-template/base-template.Report/definition/bookmarks/1d40d43c7ade66e8603c.bookmark.json +2297 -0
  146. package/templates/base-template/base-template.Report/definition/bookmarks/af068ff51c0ca3089ea7.bookmark.json +2300 -0
  147. package/templates/base-template/base-template.Report/definition/bookmarks/bookmarks.json +11 -0
  148. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/page.json +130 -0
  149. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/0352fd80d074693a65db/mobile.json +11 -0
  150. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/0352fd80d074693a65db/visual.json +669 -0
  151. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/1c5a14bf493697344b68/mobile.json +11 -0
  152. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/1c5a14bf493697344b68/visual.json +723 -0
  153. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/3486cf7624c5b109b4e5/mobile.json +11 -0
  154. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/3486cf7624c5b109b4e5/visual.json +333 -0
  155. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/4d8b989008edc0db28d1/mobile.json +11 -0
  156. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/4d8b989008edc0db28d1/visual.json +109 -0
  157. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/55e10ac7d76a1954f94f/mobile.json +31 -0
  158. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/55e10ac7d76a1954f94f/visual.json +378 -0
  159. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/57f52ecf4490f70e4da1/mobile.json +11 -0
  160. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/57f52ecf4490f70e4da1/visual.json +175 -0
  161. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/5f4d76bbc870118e9840/mobile.json +11 -0
  162. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/5f4d76bbc870118e9840/visual.json +468 -0
  163. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/73629e1abebb7a444b59/mobile.json +11 -0
  164. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/73629e1abebb7a444b59/visual.json +359 -0
  165. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/749cb1388c7e0a88161c/mobile.json +11 -0
  166. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/749cb1388c7e0a88161c/visual.json +690 -0
  167. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/90677f13cea5d1275990/visual.json +17 -0
  168. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/92cf92e3da10493adb78/mobile.json +11 -0
  169. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/92cf92e3da10493adb78/visual.json +468 -0
  170. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/9fe17b1971f68443fc15/mobile.json +10 -0
  171. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/9fe17b1971f68443fc15/visual.json +328 -0
  172. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/a30bd0950630ed94e8a3/mobile.json +11 -0
  173. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/a30bd0950630ed94e8a3/visual.json +578 -0
  174. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/a56e91d9400a835e4814/mobile.json +11 -0
  175. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/a56e91d9400a835e4814/visual.json +432 -0
  176. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/aded24cd205c0b528642/mobile.json +11 -0
  177. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/aded24cd205c0b528642/visual.json +801 -0
  178. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/af34b26f14a8a724c9a9/mobile.json +37 -0
  179. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/af34b26f14a8a724c9a9/visual.json +1318 -0
  180. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/b529688fe5a226643322/visual.json +209 -0
  181. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/c4c6f332d05e72e2eb06/mobile.json +11 -0
  182. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/c4c6f332d05e72e2eb06/visual.json +174 -0
  183. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/fa81f184e2cb0e8b087c/mobile.json +29 -0
  184. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/fa81f184e2cb0e8b087c/visual.json +241 -0
  185. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/page.json +130 -0
  186. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/07e9c4302e29029c5462/mobile.json +11 -0
  187. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/07e9c4302e29029c5462/visual.json +690 -0
  188. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/109ceede4bc015b0c006/mobile.json +11 -0
  189. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/109ceede4bc015b0c006/visual.json +468 -0
  190. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/118257e006d472277e10/mobile.json +11 -0
  191. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/118257e006d472277e10/visual.json +359 -0
  192. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/2caf02e0137c4a1280cc/mobile.json +11 -0
  193. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/2caf02e0137c4a1280cc/visual.json +669 -0
  194. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/311e76fe3c9edad68204/mobile.json +11 -0
  195. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/311e76fe3c9edad68204/visual.json +109 -0
  196. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/31c21f8cbeb3b208940a/visual.json +209 -0
  197. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/3ab72c25062437149b03/visual.json +17 -0
  198. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/5959867442abcb0ce2b3/mobile.json +11 -0
  199. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/5959867442abcb0ce2b3/visual.json +788 -0
  200. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/5b96e0f88d192b044a13/mobile.json +11 -0
  201. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/5b96e0f88d192b044a13/visual.json +592 -0
  202. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/64e749a63d0786000e22/mobile.json +11 -0
  203. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/64e749a63d0786000e22/visual.json +468 -0
  204. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/7ae1ca604edac6586ad0/mobile.json +11 -0
  205. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/7ae1ca604edac6586ad0/visual.json +1310 -0
  206. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/840300733885141a6603/mobile.json +11 -0
  207. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/840300733885141a6603/visual.json +175 -0
  208. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/a38448cdb203279273d2/mobile.json +11 -0
  209. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/a38448cdb203279273d2/visual.json +516 -0
  210. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/d1e86f213a3841d12e20/visual.json +328 -0
  211. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/d4a484c1bcc8ee3075e2/mobile.json +11 -0
  212. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/d4a484c1bcc8ee3075e2/visual.json +432 -0
  213. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/d87cb5cf06acca19bbb5/mobile.json +11 -0
  214. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/d87cb5cf06acca19bbb5/visual.json +241 -0
  215. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/e243da2677209ed69408/mobile.json +11 -0
  216. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/e243da2677209ed69408/visual.json +174 -0
  217. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/f3aaf24f5b22b67573b0/mobile.json +11 -0
  218. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/f3aaf24f5b22b67573b0/visual.json +333 -0
  219. package/templates/base-template/base-template.Report/definition/pages/pages.json +8 -0
  220. package/templates/base-template/base-template.Report/definition/report.json +89 -0
  221. package/templates/base-template/base-template.Report/definition/version.json +4 -0
  222. package/templates/base-template/base-template.Report/definition.pbir +9 -0
  223. package/templates/base-template/base-template.SemanticModel/.platform +11 -0
  224. package/templates/base-template/base-template.SemanticModel/definition/cultures/es-AR.tmdl +11185 -0
  225. package/templates/base-template/base-template.SemanticModel/definition/database.tmdl +3 -0
  226. package/templates/base-template/base-template.SemanticModel/definition/expressions.tmdl +234 -0
  227. package/templates/base-template/base-template.SemanticModel/definition/functions.tmdl +637 -0
  228. package/templates/base-template/base-template.SemanticModel/definition/model.tmdl +82 -0
  229. package/templates/base-template/base-template.SemanticModel/definition/relationships.tmdl +271 -0
  230. package/templates/base-template/base-template.SemanticModel/definition/tables/Calendario.tmdl +200 -0
  231. package/templates/base-template/base-template.SemanticModel/definition/tables/Campa/303/261as.tmdl +75 -0
  232. package/templates/base-template/base-template.SemanticModel/definition/tables/Canales.tmdl +84 -0
  233. package/templates/base-template/base-template.SemanticModel/definition/tables/Clientes.tmdl +143 -0
  234. package/templates/base-template/base-template.SemanticModel/definition/tables/Devoluciones.tmdl +95 -0
  235. package/templates/base-template/base-template.SemanticModel/definition/tables/Ejecuci/303/263n proyectos.tmdl" +130 -0
  236. package/templates/base-template/base-template.SemanticModel/definition/tables/Entregas.tmdl +122 -0
  237. package/templates/base-template/base-template.SemanticModel/definition/tables/Equipos m/303/251tricas.tmdl" +40 -0
  238. package/templates/base-template/base-template.SemanticModel/definition/tables/Equipos.tmdl +73 -0
  239. package/templates/base-template/base-template.SemanticModel/definition/tables/Horas.tmdl +122 -0
  240. package/templates/base-template/base-template.SemanticModel/definition/tables/Interacciones clientes.tmdl +146 -0
  241. package/templates/base-template/base-template.SemanticModel/definition/tables/Leads.tmdl +119 -0
  242. package/templates/base-template/base-template.SemanticModel/definition/tables/Monedas.tmdl +44 -0
  243. package/templates/base-template/base-template.SemanticModel/definition/tables/Movimientos financieros.tmdl +145 -0
  244. package/templates/base-template/base-template.SemanticModel/definition/tables/M/303/251tricas.tmdl +1294 -0
  245. package/templates/base-template/base-template.SemanticModel/definition/tables/N/303/263mina.tmdl +110 -0
  246. package/templates/base-template/base-template.SemanticModel/definition/tables/Oportunidades.tmdl +135 -0
  247. package/templates/base-template/base-template.SemanticModel/definition/tables/Presupuesto.tmdl +125 -0
  248. package/templates/base-template/base-template.SemanticModel/definition/tables/Productos.tmdl +98 -0
  249. package/templates/base-template/base-template.SemanticModel/definition/tables/Proyectos.tmdl +77 -0
  250. package/templates/base-template/base-template.SemanticModel/definition/tables/Servicios.tmdl +75 -0
  251. package/templates/base-template/base-template.SemanticModel/definition/tables/Tareas proyecto.tmdl +102 -0
  252. package/templates/base-template/base-template.SemanticModel/definition/tables/Tipo de cambio.tmdl +67 -0
  253. package/templates/base-template/base-template.SemanticModel/definition/tables/Ventas.tmdl +180 -0
  254. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux An/303/241lisis dimensiones.tmdl" +38 -0
  255. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Comparaciones.tmdl +227 -0
  256. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Compatibilidad m/303/251trica-dimensi/303/263n.tmdl" +68 -0
  257. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Modelo configuraci/303/263n.tmdl" +44 -0
  258. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Modo fechas.tmdl +36 -0
  259. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux M/303/251trica-Equipo.tmdl" +102 -0
  260. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Overrides m/303/251trica-dimensi/303/263n.tmdl" +54 -0
  261. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Per/303/255odos.tmdl" +182 -0
  262. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Rango fechas modo.tmdl +36 -0
  263. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Rango fechas.tmdl +27 -0
  264. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Vista de calendario.tmdl +30 -0
  265. package/templates/base-template/base-template.SemanticModel/definition/tables/_GC C/303/241lculo.tmdl" +70 -0
  266. package/templates/base-template/base-template.SemanticModel/definition/tables/_GC Eje X.tmdl +63 -0
  267. package/templates/base-template/base-template.SemanticModel/definition/tables/_GC M/303/251trica.tmdl" +374 -0
  268. package/templates/base-template/base-template.SemanticModel/definition/tables/_GC Tipo c/303/241lculo.tmdl" +223 -0
  269. package/templates/base-template/base-template.SemanticModel/definition/tables/_PC Dimensi/303/263n.tmdl" +98 -0
  270. package/templates/base-template/base-template.SemanticModel/definition/tables/_PC Eje X.tmdl +68 -0
  271. package/templates/base-template/base-template.SemanticModel/definition/tables//303/223rdenes servicio.tmdl" +151 -0
  272. package/templates/base-template/base-template.SemanticModel/definition.pbism +5 -0
  273. package/templates/base-template/base-template.SemanticModel/diagramLayout.json +568 -0
  274. package/templates/base-template/base-template.pbip +14 -0
  275. package/templates/base-template/template.manifest.json +41 -0
  276. package/theme/BISuperpowers.json +3888 -0
@@ -0,0 +1,835 @@
1
+ /**
2
+ * Multi-Agent MCP Configuration Writer
3
+ * =====================================
4
+ *
5
+ * Writes the bi-superpowers MCP config (2 servers: powerbi-modeling-mcp
6
+ * + microsoft-learn) into each supported agent's expected config location
7
+ * at the user level (home directory).
8
+ *
9
+ * Each agent has its own config format and path. This module normalizes
10
+ * the write operation across all 5 supported agents:
11
+ *
12
+ * | Agent | Path | Format |
13
+ * |-----------------|---------------------------------------|-------------------|
14
+ * | Claude Code | ~/.claude.json | JSON mcpServers |
15
+ * | GitHub Copilot | ~/.copilot/mcp-config.json | JSON mcpServers |
16
+ * | Codex | ~/.codex/config.toml | TOML mcp_servers |
17
+ * | Gemini CLI | ~/.gemini/settings.json | JSON mcpServers |
18
+ * | Kilo Code | ~/.config/kilo/kilo.jsonc | JSON mcp |
19
+ *
20
+ * The launcher path for the Power BI Modeling MCP is resolved to an
21
+ * absolute path at install time so it works in agents that don't
22
+ * support Claude Code's `${CLAUDE_PLUGIN_ROOT}` variable.
23
+ *
24
+ * @module lib/mcp-config
25
+ */
26
+
27
+ const fs = require('fs');
28
+ const path = require('path');
29
+ const os = require('os');
30
+
31
+ const { MICROSOFT_LEARN_URL } = require('./microsoft-mcp');
32
+ const MODELING_SERVER_NAME = 'powerbi-modeling-mcp';
33
+ const LEARN_SERVER_NAME = 'microsoft-learn';
34
+ // Tools that need an explicit Codex approval gate.
35
+ // Most are semantic-model mutations. trace_operations is also gated because it
36
+ // can capture query text/metadata and export full event details to a local file.
37
+ const CODEX_POWERBI_APPROVAL_TOOLS = [
38
+ 'calendar_operations',
39
+ 'calculation_group_operations',
40
+ 'column_operations',
41
+ 'culture_operations',
42
+ 'database_operations',
43
+ 'function_operations',
44
+ 'measure_operations',
45
+ 'model_operations',
46
+ 'named_expression_operations',
47
+ 'object_translation_operations',
48
+ 'partition_operations',
49
+ 'perspective_operations',
50
+ 'query_group_operations',
51
+ 'relationship_operations',
52
+ 'security_role_operations',
53
+ 'table_operations',
54
+ 'trace_operations',
55
+ 'transaction_operations',
56
+ 'user_hierarchy_operations',
57
+ ];
58
+ // Tools deliberately left ungated in Codex after review. They support model
59
+ // discovery, connection selection, and DAX validation/querying, but do not
60
+ // mutate semantic-model metadata. If Microsoft adds new MCP tools, tests must
61
+ // classify them as approval-gated or explicitly reviewed here.
62
+ const CODEX_POWERBI_REVIEWED_UNGATED_TOOLS = ['connection_operations', 'dax_query_operations'];
63
+ const CODEX_POWERBI_WRITE_TOOLS = CODEX_POWERBI_APPROVAL_TOOLS;
64
+
65
+ /**
66
+ * Resolves the absolute path to the Power BI Modeling MCP launcher
67
+ * inside the installed npm package.
68
+ */
69
+ function getLauncherAbsolutePath(packageDir) {
70
+ return path.join(packageDir, 'bin', 'mcp', 'powerbi-modeling-launcher.js');
71
+ }
72
+
73
+ /**
74
+ * Safely read a JSON file. Returns null if missing or unparseable.
75
+ *
76
+ * NOTE: this swallows parse errors. Do NOT use it before a write — a
77
+ * silent null on a corrupt user config means a spread merge ends up
78
+ * replacing the whole config with our 2 servers (data loss). Use
79
+ * `readJsonStrict` for write paths instead.
80
+ */
81
+ function readJsonSafe(filePath) {
82
+ if (!fs.existsSync(filePath)) return null;
83
+ try {
84
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
85
+ } catch (_) {
86
+ return null;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Read a JSON file for write paths. Returns null only if the file is
92
+ * absent or empty. Throws an actionable error if the file exists with
93
+ * non-empty content but is not parseable — silently overwriting a
94
+ * user's corrupt config (especially `~/.claude.json`) destroys data.
95
+ *
96
+ * @throws {Error} if the file exists but is unreadable or unparseable
97
+ */
98
+ function readJsonStrict(filePath) {
99
+ if (!fs.existsSync(filePath)) return null;
100
+ let raw;
101
+ try {
102
+ raw = fs.readFileSync(filePath, 'utf8');
103
+ } catch (err) {
104
+ throw new Error(
105
+ `Could not read ${filePath}: ${err.message}. ` + 'Check file permissions and retry.'
106
+ );
107
+ }
108
+ if (raw.trim() === '') return null;
109
+ try {
110
+ return JSON.parse(raw);
111
+ } catch (err) {
112
+ throw new Error(
113
+ `Refusing to overwrite ${filePath}: file exists but is not valid JSON ` +
114
+ `(${err.message}). Inspect or move it manually before retrying. ` +
115
+ `If a previous-install backup exists at ${filePath}.bak, restore from there.`
116
+ );
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Read a JSONC file for write paths. This accepts JSON with comments and
122
+ * trailing commas, which is how Kilo Code documents its config format.
123
+ * Returns null only if the file is absent or empty.
124
+ *
125
+ * @throws {Error} if the file exists but cannot be parsed as JSONC
126
+ */
127
+ function readJsoncStrict(filePath) {
128
+ if (!fs.existsSync(filePath)) return null;
129
+ let raw;
130
+ try {
131
+ raw = fs.readFileSync(filePath, 'utf8');
132
+ } catch (err) {
133
+ throw new Error(
134
+ `Could not read ${filePath}: ${err.message}. ` + 'Check file permissions and retry.'
135
+ );
136
+ }
137
+ if (raw.trim() === '') return null;
138
+
139
+ let stripped = '';
140
+ let inString = false;
141
+ let escaped = false;
142
+ let inLineComment = false;
143
+ let inBlockComment = false;
144
+
145
+ for (let i = 0; i < raw.length; i++) {
146
+ const ch = raw[i];
147
+ const next = raw[i + 1];
148
+
149
+ if (inLineComment) {
150
+ if (ch === '\n' || ch === '\r') {
151
+ inLineComment = false;
152
+ stripped += ch;
153
+ }
154
+ continue;
155
+ }
156
+
157
+ if (inBlockComment) {
158
+ if (ch === '*' && next === '/') {
159
+ inBlockComment = false;
160
+ i++;
161
+ }
162
+ continue;
163
+ }
164
+
165
+ if (inString) {
166
+ stripped += ch;
167
+ if (escaped) {
168
+ escaped = false;
169
+ } else if (ch === '\\') {
170
+ escaped = true;
171
+ } else if (ch === '"') {
172
+ inString = false;
173
+ }
174
+ continue;
175
+ }
176
+
177
+ if (ch === '"') {
178
+ inString = true;
179
+ stripped += ch;
180
+ continue;
181
+ }
182
+
183
+ if (ch === '/' && next === '/') {
184
+ inLineComment = true;
185
+ i++;
186
+ continue;
187
+ }
188
+
189
+ if (ch === '/' && next === '*') {
190
+ inBlockComment = true;
191
+ i++;
192
+ continue;
193
+ }
194
+
195
+ if (ch === ',') {
196
+ let lookahead = i + 1;
197
+ while (lookahead < raw.length && /\s/.test(raw[lookahead])) {
198
+ lookahead++;
199
+ }
200
+ if (lookahead < raw.length && (raw[lookahead] === '}' || raw[lookahead] === ']')) {
201
+ continue;
202
+ }
203
+ }
204
+
205
+ stripped += ch;
206
+ }
207
+
208
+ try {
209
+ return JSON.parse(stripped);
210
+ } catch (err) {
211
+ throw new Error(
212
+ `Refusing to overwrite ${filePath}: file exists but is not valid JSONC ` +
213
+ `(${err.message}). Inspect or move it manually before retrying. ` +
214
+ `If a previous-install backup exists at ${filePath}.bak, restore from there.`
215
+ );
216
+ }
217
+ }
218
+
219
+ function describeJsonShape(value) {
220
+ if (value === null) {
221
+ return 'null';
222
+ }
223
+ if (Array.isArray(value)) {
224
+ return 'array';
225
+ }
226
+ return typeof value;
227
+ }
228
+
229
+ function isObjectRecord(value) {
230
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
231
+ }
232
+
233
+ function assertObjectRecord(value, description, configPath) {
234
+ if (isObjectRecord(value)) {
235
+ return;
236
+ }
237
+
238
+ throw new Error(
239
+ `Refusing to write MCP config: ${configPath} contains non-object JSON at ${description} ` +
240
+ `(${describeJsonShape(value)}). Fix or remove that value, then re-run.`
241
+ );
242
+ }
243
+
244
+ function hasNonEmptyFileContent(filePath) {
245
+ return fs.existsSync(filePath) && fs.readFileSync(filePath, 'utf8').trim() !== '';
246
+ }
247
+
248
+ /**
249
+ * Reject symlink attacks before writing to a file. We use lstatSync so
250
+ * we detect the link itself (not what it points to). If a user home has
251
+ * `~/.claude.json` pointing to `/etc/passwd`, we refuse to write.
252
+ * @throws {Error} if filePath exists and is a symbolic link
253
+ */
254
+ function assertNotSymlink(filePath) {
255
+ if (!fs.existsSync(filePath)) return;
256
+ if (fs.lstatSync(filePath).isSymbolicLink()) {
257
+ throw new Error(
258
+ `Refusing to write MCP config: ${filePath} is a symbolic link. ` +
259
+ 'Remove the symlink and re-run the install.'
260
+ );
261
+ }
262
+ }
263
+
264
+ function normalizeResolvedPath(value) {
265
+ let resolved = path.resolve(value);
266
+ if (process.platform === 'win32') {
267
+ if (resolved.startsWith('\\\\?\\UNC\\')) {
268
+ resolved = `\\\\${resolved.slice('\\\\?\\UNC\\'.length)}`;
269
+ } else if (resolved.startsWith('\\\\?\\')) {
270
+ resolved = resolved.slice('\\\\?\\'.length);
271
+ }
272
+ return resolved.toLowerCase();
273
+ }
274
+ return resolved;
275
+ }
276
+
277
+ function resolvesElsewhere(value) {
278
+ const realpathSync = fs.realpathSync.native || fs.realpathSync;
279
+ const parent = path.dirname(value);
280
+ if (parent === value) return false;
281
+
282
+ const expectedFromParent = path.join(realpathSync(parent), path.basename(value));
283
+ return normalizeResolvedPath(realpathSync(value)) !== normalizeResolvedPath(expectedFromParent);
284
+ }
285
+
286
+ /**
287
+ * Walk the ancestors of `filePath` up to (but not including) the user's
288
+ * home directory and reject if any ancestor is a symbolic link or path
289
+ * redirection. Needed because `assertNotSymlink` only checks the leaf —
290
+ * a symlink at a parent (e.g. `~/.copilot` → `/tmp/attacker/`) would
291
+ * still redirect every `.tmp` / `.bak` / final rename through the link,
292
+ * bypassing the leaf-only hardening.
293
+ *
294
+ * Trust boundaries:
295
+ * - We stop walking at `os.homedir()` itself. If the user's home is a
296
+ * symlink, that's their machine setup, not something we can police.
297
+ * - We also stop if, after `realpathSync`, the ancestor resolves
298
+ * outside the home subtree — defensive, covers the case where an
299
+ * earlier ancestor already redirected us elsewhere.
300
+ * - We stop at filesystem root (`path.dirname(x) === x`).
301
+ *
302
+ * On Windows, junctions and other reparse-point redirections are also
303
+ * rejected by comparing the ancestor's resolved path with its real path.
304
+ *
305
+ * @throws {Error} if any ancestor of filePath (up to home) is a symlink or redirect
306
+ */
307
+ function assertNoSymlinkedAncestor(filePath) {
308
+ const home = os.homedir();
309
+ const realpathSync = fs.realpathSync.native || fs.realpathSync;
310
+ const homeResolved = fs.existsSync(home) ? realpathSync(home) : home;
311
+ const normalizedHomeResolved = normalizeResolvedPath(homeResolved);
312
+
313
+ let current = path.dirname(filePath);
314
+ const visited = new Set();
315
+
316
+ while (current && !visited.has(current)) {
317
+ visited.add(current);
318
+
319
+ // Stop at the user's home — trust boundary.
320
+ if (current === home || current === homeResolved) return;
321
+ if (
322
+ fs.existsSync(current) &&
323
+ normalizeResolvedPath(realpathSync(current)) === normalizedHomeResolved
324
+ ) {
325
+ return;
326
+ }
327
+
328
+ if (fs.existsSync(current)) {
329
+ if (fs.lstatSync(current).isSymbolicLink()) {
330
+ throw new Error(
331
+ `Refusing to write MCP config: parent directory ${current} is a symbolic link. ` +
332
+ 'Remove the symlink (or redirect super install at a different path) and retry.'
333
+ );
334
+ }
335
+
336
+ if (resolvesElsewhere(current)) {
337
+ throw new Error(
338
+ `Refusing to write MCP config: parent directory ${current} is redirected. ` +
339
+ 'Remove the symlink, junction, or reparse-point ancestor and retry.'
340
+ );
341
+ }
342
+
343
+ // Defensive: if realpath has already pulled us outside the home
344
+ // subtree, stop walking — we're in territory we can't reason about.
345
+ // Compare with a trailing separator so a sibling like
346
+ // `C:\Users\bob-evil` is not treated as inside `C:\Users\bob`.
347
+ const normalizedCurrentResolved = normalizeResolvedPath(realpathSync(current));
348
+ if (
349
+ normalizedCurrentResolved !== normalizedHomeResolved &&
350
+ !normalizedCurrentResolved.startsWith(normalizedHomeResolved + path.sep)
351
+ ) {
352
+ return;
353
+ }
354
+ }
355
+
356
+ const parent = path.dirname(current);
357
+ if (parent === current) return; // filesystem root
358
+ current = parent;
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Full pre-write safety check: leaf must not be a symlink, and no
364
+ * ancestor up to `os.homedir()` may be a symlink either. Use this
365
+ * instead of `assertNotSymlink` on every write path inside the user
366
+ * home directory.
367
+ */
368
+ function assertPathSafeForWrite(filePath) {
369
+ assertNotSymlink(filePath);
370
+ assertNoSymlinkedAncestor(filePath);
371
+ }
372
+
373
+ /**
374
+ * Write any text content to a file using a safe pattern:
375
+ *
376
+ * 1. Refuse if the target is a symbolic link (assertNotSymlink).
377
+ * 2. If the target exists, copy it to `<target>.bak` first. This
378
+ * gives the user a one-slot recovery file overwritten on each
379
+ * install — enough to recover from a bad merge without
380
+ * accumulating stale backups across many install runs.
381
+ * 3. Write to `<target>.tmp` then atomically rename to the final
382
+ * path. `fs.renameSync` is atomic on POSIX and functionally
383
+ * atomic for same-volume renames on NTFS, which is the only
384
+ * shape we hit when writing into the user home (~/...).
385
+ * 4. On any error mid-process, best-effort cleanup of the .tmp
386
+ * file so we never leave half-written turds behind.
387
+ *
388
+ * The atomic + backup pattern matters most for `~/.claude.json` —
389
+ * Claude Code's primary user config holds project pointers, OAuth
390
+ * state, conversation history. A crash mid-write or a bad merge
391
+ * would brick the user's Claude install otherwise.
392
+ */
393
+ function writeFileAtomic(filePath, content) {
394
+ assertPathSafeForWrite(filePath);
395
+ const tmpPath = `${filePath}.tmp`;
396
+ const backupPath = `${filePath}.bak`;
397
+ assertPathSafeForWrite(tmpPath);
398
+ assertPathSafeForWrite(backupPath);
399
+
400
+ const dir = path.dirname(filePath);
401
+ if (!fs.existsSync(dir)) {
402
+ fs.mkdirSync(dir, { recursive: true });
403
+ }
404
+
405
+ if (fs.existsSync(filePath)) {
406
+ try {
407
+ fs.copyFileSync(filePath, backupPath);
408
+ } catch (err) {
409
+ throw new Error(
410
+ `Could not back up ${filePath} before writing: ${err.message}. ` +
411
+ 'Aborting to avoid losing the existing file.'
412
+ );
413
+ }
414
+ }
415
+
416
+ try {
417
+ fs.writeFileSync(tmpPath, content);
418
+ fs.renameSync(tmpPath, filePath);
419
+ } catch (err) {
420
+ try {
421
+ fs.unlinkSync(tmpPath);
422
+ } catch (_) {
423
+ // Best-effort cleanup; ignore if there is nothing to remove.
424
+ }
425
+ throw err;
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Write a JSON file using the atomic + backup pattern.
431
+ * Refuses to overwrite symbolic links for safety.
432
+ */
433
+ function writeJson(filePath, data) {
434
+ writeFileAtomic(filePath, JSON.stringify(data, null, 2) + '\n');
435
+ }
436
+
437
+ /**
438
+ * Escape a string for use inside TOML double-quoted strings.
439
+ * Covers backslash, quote, and the control characters that TOML spec
440
+ * requires escaping in basic strings: \b \t \n \f \r
441
+ *
442
+ * Note: `\b` in a JS regex is word-boundary, NOT backspace (0x08).
443
+ * We use \x08 explicitly to match the backspace character itself.
444
+ */
445
+ function tomlEscape(str) {
446
+ return (
447
+ String(str)
448
+ .replace(/\\/g, '\\\\')
449
+ .replace(/"/g, '\\"')
450
+ // eslint-disable-next-line no-control-regex
451
+ .replace(/\x08/g, '\\b')
452
+ .replace(/\t/g, '\\t')
453
+ .replace(/\n/g, '\\n')
454
+ .replace(/\f/g, '\\f')
455
+ .replace(/\r/g, '\\r')
456
+ );
457
+ }
458
+
459
+ /**
460
+ * Escape a string for safe use inside a regex pattern (as a literal).
461
+ */
462
+ function escapeRegex(str) {
463
+ return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
464
+ }
465
+
466
+ // ============================================
467
+ // JSON AGENT CONFIGURATIONS
468
+ // ============================================
469
+ // The 4 JSON-based agents (Claude Code, GitHub Copilot, Gemini CLI,
470
+ // Kilo Code) all follow the same pattern: read existing JSON, merge
471
+ // our 2 servers into a wrapper key, write back. Each agent differs
472
+ // only in:
473
+ // - The config file path
474
+ // - The wrapper key (`mcpServers` vs `mcp`)
475
+ // - Whether the stdio/local entry needs an explicit `type` field
476
+ // - The HTTP/remote URL field name (`url` vs `httpUrl`)
477
+ // - Whether the agent wants the local launcher as `command` + `args`
478
+ // or a single `command` array
479
+ //
480
+ // We describe each agent in a single table and build the writer functions
481
+ // from it so adding a new JSON agent is a one-line change.
482
+ //
483
+ // IMPORTANT: every entry below has a source URL pointing at the official
484
+ // docs where the path/format comes from. If you change any of these,
485
+ // verify the new value against the linked source first. Silent drift is
486
+ // the hardest class of bug in this file because the writes succeed even
487
+ // when the agent never reads the file.
488
+
489
+ const JSON_AGENT_CONFIGS = {
490
+ 'claude-code': {
491
+ // Source: https://code.claude.com/docs/en/mcp
492
+ // "User-scoped servers are stored in ~/.claude.json"
493
+ // Note: ~/.claude/settings.json does NOT work for MCPs (known docs bug).
494
+ // stdio entries omit `type` here; HTTP entries include it.
495
+ configPath: () => path.join(os.homedir(), '.claude.json'),
496
+ wrapperKey: 'mcpServers',
497
+ readConfig: readJsonStrict,
498
+ buildServers: (packageDir) => ({
499
+ [MODELING_SERVER_NAME]: {
500
+ command: 'node',
501
+ args: [getLauncherAbsolutePath(packageDir)],
502
+ },
503
+ [LEARN_SERVER_NAME]: {
504
+ type: 'http',
505
+ url: MICROSOFT_LEARN_URL,
506
+ },
507
+ }),
508
+ },
509
+ 'github-copilot': {
510
+ // Source: https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers
511
+ // "MCP servers are saved to ~/.copilot/mcp-config.json"
512
+ // Copilot uses `mcpServers` and local servers declare `type = local`.
513
+ configPath: () => path.join(os.homedir(), '.copilot', 'mcp-config.json'),
514
+ wrapperKey: 'mcpServers',
515
+ readConfig: readJsonStrict,
516
+ buildServers: (packageDir) => ({
517
+ [MODELING_SERVER_NAME]: {
518
+ type: 'local',
519
+ command: 'node',
520
+ args: [getLauncherAbsolutePath(packageDir)],
521
+ },
522
+ [LEARN_SERVER_NAME]: {
523
+ type: 'http',
524
+ url: MICROSOFT_LEARN_URL,
525
+ },
526
+ }),
527
+ },
528
+ 'gemini-cli': {
529
+ // Source: https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md
530
+ // "MCP config goes in ~/.gemini/settings.json under mcpServers"
531
+ // Gemini uses `httpUrl` (NOT `url`) for HTTP transports and omits
532
+ // `type` — it's inferred from which key is present.
533
+ configPath: () => path.join(os.homedir(), '.gemini', 'settings.json'),
534
+ wrapperKey: 'mcpServers',
535
+ readConfig: readJsonStrict,
536
+ buildServers: (packageDir) => ({
537
+ [MODELING_SERVER_NAME]: {
538
+ command: 'node',
539
+ args: [getLauncherAbsolutePath(packageDir)],
540
+ },
541
+ [LEARN_SERVER_NAME]: {
542
+ httpUrl: MICROSOFT_LEARN_URL,
543
+ },
544
+ }),
545
+ },
546
+ kilo: {
547
+ // Source: https://kilo.ai/docs/automate/mcp/using-in-kilo-code
548
+ // Kilo Code stores global MCP settings in ~/.config/kilo/kilo.jsonc
549
+ // under the `mcp` key. Local servers use `type = local` and `command`
550
+ // as an array; remote servers use `type = remote`.
551
+ configPath: () => path.join(os.homedir(), '.config', 'kilo', 'kilo.jsonc'),
552
+ wrapperKey: 'mcp',
553
+ readConfig: readJsoncStrict,
554
+ buildServers: (packageDir) => ({
555
+ [MODELING_SERVER_NAME]: {
556
+ type: 'local',
557
+ command: ['node', getLauncherAbsolutePath(packageDir)],
558
+ enabled: true,
559
+ timeout: 10000,
560
+ },
561
+ [LEARN_SERVER_NAME]: {
562
+ type: 'remote',
563
+ url: MICROSOFT_LEARN_URL,
564
+ enabled: true,
565
+ timeout: 15000,
566
+ },
567
+ }),
568
+ },
569
+ };
570
+
571
+ /**
572
+ * Create a writer function for a JSON-format agent.
573
+ * The returned function reads the existing config (if any), merges in
574
+ * our 2 servers, and writes back, preserving all other fields.
575
+ */
576
+ function makeJsonWriter(agentId) {
577
+ const agentConfig = JSON_AGENT_CONFIGS[agentId];
578
+ return function jsonWriter(packageDir) {
579
+ const configPath = agentConfig.configPath();
580
+ // Use strict reader on the write path: a silent parse failure here
581
+ // would discard the user's other config keys (history, OAuth, etc.)
582
+ // when the empty {} fallback is spread into the new write.
583
+ const parsedConfig = agentConfig.readConfig(configPath);
584
+ const existing =
585
+ parsedConfig === null && !hasNonEmptyFileContent(configPath) ? {} : parsedConfig;
586
+ // A real agent config is always a JSON object. If the file parses to an array
587
+ // or a primitive, spreading it ({ ...existing }) would silently corrupt or
588
+ // erase the user's config — refuse instead.
589
+ assertObjectRecord(existing, 'the document root', configPath);
590
+ const wrapperKey = agentConfig.wrapperKey;
591
+ const existingServers = Object.prototype.hasOwnProperty.call(existing, wrapperKey)
592
+ ? existing[wrapperKey]
593
+ : {};
594
+ assertObjectRecord(existingServers, wrapperKey, configPath);
595
+
596
+ const newServers = agentConfig.buildServers(packageDir);
597
+ const mergedServers = { ...existingServers, ...newServers };
598
+
599
+ writeJson(configPath, { ...existing, [wrapperKey]: mergedServers });
600
+ return configPath;
601
+ };
602
+ }
603
+
604
+ // ============================================
605
+ // CODEX (OpenAI) — TOML format
606
+ // ============================================
607
+ // Source: https://developers.openai.com/codex/mcp
608
+ // Also: https://github.com/openai/codex/blob/main/docs/config.md
609
+ // "Codex CLI reads MCP server config from ~/.codex/config.toml, where
610
+ // each server gets its own section with format [mcp_servers.my-server]"
611
+ //
612
+ // Writes ~/.codex/config.toml, appending [mcp_servers.*] sections.
613
+ // Preserves existing content by removing only our own sections before
614
+ // appending the fresh ones.
615
+ function writeCodexConfig(packageDir) {
616
+ const configPath = path.join(os.homedir(), '.codex', 'config.toml');
617
+ assertPathSafeForWrite(configPath);
618
+ const launcher = getLauncherAbsolutePath(packageDir);
619
+
620
+ let existing = '';
621
+ if (fs.existsSync(configPath)) {
622
+ existing = fs.readFileSync(configPath, 'utf8');
623
+ }
624
+
625
+ // Remove any previous bi-superpowers MCP sections so re-running install
626
+ // doesn't duplicate them. Strips each section from its header to the
627
+ // next [ header or EOF. Server names are escaped for regex safety even
628
+ // though current values are literal. The optional suffix removes child
629
+ // sections like [mcp_servers.powerbi-modeling-mcp.tools.measure_operations].
630
+ const namePattern = [MODELING_SERVER_NAME, LEARN_SERVER_NAME].map(escapeRegex).join('|');
631
+ const stripPattern = new RegExp(
632
+ `\\n?\\[mcp_servers\\.(${namePattern})(?:\\.[^\\]]+)?\\][\\s\\S]*?(?=\\n\\[|$)`,
633
+ 'g'
634
+ );
635
+ existing = existing.replace(stripPattern, '');
636
+
637
+ const writeToolApprovals = CODEX_POWERBI_APPROVAL_TOOLS.map(
638
+ (toolName) =>
639
+ `\n[mcp_servers.${MODELING_SERVER_NAME}.tools.${toolName}]\n` + 'approval_mode = "approve"\n'
640
+ ).join('');
641
+
642
+ const newSections =
643
+ `\n\n[mcp_servers.${MODELING_SERVER_NAME}]\n` +
644
+ 'command = "node"\n' +
645
+ `args = ["${tomlEscape(launcher)}"]\n` +
646
+ writeToolApprovals +
647
+ `\n[mcp_servers.${LEARN_SERVER_NAME}]\n` +
648
+ `url = "${MICROSOFT_LEARN_URL}"\n`;
649
+
650
+ const content = existing.trimEnd() + newSections;
651
+
652
+ // Use the same atomic + backup pattern as the JSON writers — a crash
653
+ // mid-write to ~/.codex/config.toml would brick the user's Codex install.
654
+ writeFileAtomic(configPath, content);
655
+ return configPath;
656
+ }
657
+
658
+ /**
659
+ * Registry mapping agent IDs to their MCP config writers.
660
+ * JSON agents are generated from JSON_AGENT_CONFIGS via makeJsonWriter;
661
+ * Codex is the only TOML-based agent and has its own writer.
662
+ */
663
+ const MCP_WRITERS = {
664
+ 'claude-code': makeJsonWriter('claude-code'),
665
+ 'github-copilot': makeJsonWriter('github-copilot'),
666
+ codex: writeCodexConfig,
667
+ 'gemini-cli': makeJsonWriter('gemini-cli'),
668
+ kilo: makeJsonWriter('kilo'),
669
+ };
670
+
671
+ /**
672
+ * Write MCP config for a specific agent.
673
+ * @param {string} agentId - One of the supported agent IDs
674
+ * @param {string} packageDir - Absolute path to the installed package
675
+ * @returns {string|null} The config path written, or null if unknown agent
676
+ * @throws {Error} If the write fails
677
+ */
678
+ function writeMcpConfigForAgent(agentId, packageDir) {
679
+ const writer = MCP_WRITERS[agentId];
680
+ if (!writer) {
681
+ return null;
682
+ }
683
+ return writer(packageDir);
684
+ }
685
+
686
+ // ============================================
687
+ // MCP REMOVAL (uninstall)
688
+ // ============================================
689
+ // The removers are the exact inverse of the writers: they delete ONLY our
690
+ // 2 servers (powerbi-modeling-mcp + microsoft-learn, plus Codex's modeling
691
+ // tool-approval subsections) and leave every other server and top-level
692
+ // key untouched. Same safety guarantees as the writers — strict parsing so
693
+ // a corrupt config is refused rather than clobbered, atomic + backup write.
694
+
695
+ /**
696
+ * Create a remover function for a JSON-format agent. Reads the existing
697
+ * config, deletes only our 2 servers from the wrapper key, and writes the
698
+ * rest back unchanged. Never removes the user's other servers or keys. If
699
+ * removing our servers empties the wrapper key, the empty `{}` is left in
700
+ * place rather than restructuring the user's document.
701
+ *
702
+ * @param {string} agentId
703
+ * @returns {function(): { configPath: string, existed: boolean, removed: string[] }}
704
+ */
705
+ function makeJsonRemover(agentId) {
706
+ const agentConfig = JSON_AGENT_CONFIGS[agentId];
707
+ return function jsonRemover() {
708
+ const configPath = agentConfig.configPath();
709
+ if (!fs.existsSync(configPath)) {
710
+ return { configPath, existed: false, removed: [] };
711
+ }
712
+ // Strict reader on the write path: refuse to touch a corrupt config
713
+ // rather than risk discarding the user's other keys.
714
+ const existing = agentConfig.readConfig(configPath);
715
+ if (!isObjectRecord(existing)) {
716
+ return { configPath, existed: true, removed: [] };
717
+ }
718
+ const wrapperKey = agentConfig.wrapperKey;
719
+ const servers = existing[wrapperKey];
720
+ if (!isObjectRecord(servers)) {
721
+ return { configPath, existed: true, removed: [] };
722
+ }
723
+
724
+ const removed = [];
725
+ for (const name of [MODELING_SERVER_NAME, LEARN_SERVER_NAME]) {
726
+ if (Object.prototype.hasOwnProperty.call(servers, name)) {
727
+ delete servers[name];
728
+ removed.push(name);
729
+ }
730
+ }
731
+ if (removed.length === 0) {
732
+ return { configPath, existed: true, removed: [] };
733
+ }
734
+
735
+ writeJson(configPath, existing);
736
+ return { configPath, existed: true, removed };
737
+ };
738
+ }
739
+
740
+ /**
741
+ * Remove our 2 MCP sections (and the modeling tool-approval subsections)
742
+ * from the Codex TOML config, preserving every other section. Reuses the
743
+ * same strip pattern writeCodexConfig uses before re-appending, so the
744
+ * two stay in lockstep.
745
+ *
746
+ * @returns {{ configPath: string, existed: boolean, removed: string[] }}
747
+ */
748
+ function removeCodexConfig() {
749
+ const configPath = path.join(os.homedir(), '.codex', 'config.toml');
750
+ if (!fs.existsSync(configPath)) {
751
+ return { configPath, existed: false, removed: [] };
752
+ }
753
+ assertPathSafeForWrite(configPath);
754
+ const before = fs.readFileSync(configPath, 'utf8');
755
+
756
+ const namePattern = [MODELING_SERVER_NAME, LEARN_SERVER_NAME].map(escapeRegex).join('|');
757
+ const stripPattern = new RegExp(
758
+ `\\n?\\[mcp_servers\\.(${namePattern})(?:\\.[^\\]]+)?\\][\\s\\S]*?(?=\\n\\[|$)`,
759
+ 'g'
760
+ );
761
+ const after = before.replace(stripPattern, '');
762
+ if (after === before) {
763
+ return { configPath, existed: true, removed: [] };
764
+ }
765
+
766
+ const removed = [];
767
+ if (before.includes(`[mcp_servers.${MODELING_SERVER_NAME}]`)) {
768
+ removed.push(MODELING_SERVER_NAME);
769
+ }
770
+ if (before.includes(`[mcp_servers.${LEARN_SERVER_NAME}]`)) {
771
+ removed.push(LEARN_SERVER_NAME);
772
+ }
773
+
774
+ // If only whitespace remains, write a truly empty file; otherwise
775
+ // normalize to a single trailing newline.
776
+ const trimmed = after.trim();
777
+ writeFileAtomic(configPath, trimmed === '' ? '' : `${after.trimEnd()}\n`);
778
+ return { configPath, existed: true, removed };
779
+ }
780
+
781
+ /**
782
+ * Registry mapping agent IDs to their MCP config removers. Mirrors
783
+ * MCP_WRITERS so install and uninstall cover exactly the same agents.
784
+ */
785
+ const MCP_REMOVERS = {
786
+ 'claude-code': makeJsonRemover('claude-code'),
787
+ 'github-copilot': makeJsonRemover('github-copilot'),
788
+ codex: removeCodexConfig,
789
+ 'gemini-cli': makeJsonRemover('gemini-cli'),
790
+ kilo: makeJsonRemover('kilo'),
791
+ };
792
+
793
+ /**
794
+ * Remove our MCP config for a specific agent.
795
+ * @param {string} agentId - One of the supported agent IDs
796
+ * @returns {{ configPath: string, existed: boolean, removed: string[] }|null}
797
+ * Removal result, or null if the agent ID is unknown.
798
+ * @throws {Error} If the rewrite fails
799
+ */
800
+ function removeMcpConfigForAgent(agentId) {
801
+ const remover = MCP_REMOVERS[agentId];
802
+ if (!remover) {
803
+ return null;
804
+ }
805
+ return remover();
806
+ }
807
+
808
+ module.exports = {
809
+ writeMcpConfigForAgent,
810
+ removeMcpConfigForAgent,
811
+ MCP_WRITERS,
812
+ MCP_REMOVERS,
813
+ MODELING_SERVER_NAME,
814
+ LEARN_SERVER_NAME,
815
+ MICROSOFT_LEARN_URL,
816
+ CODEX_POWERBI_APPROVAL_TOOLS,
817
+ CODEX_POWERBI_REVIEWED_UNGATED_TOOLS,
818
+ CODEX_POWERBI_WRITE_TOOLS,
819
+ // Exported for testing
820
+ readJsonSafe,
821
+ readJsonStrict,
822
+ readJsoncStrict,
823
+ describeJsonShape,
824
+ isObjectRecord,
825
+ assertObjectRecord,
826
+ hasNonEmptyFileContent,
827
+ writeJson,
828
+ writeFileAtomic,
829
+ tomlEscape,
830
+ escapeRegex,
831
+ assertNotSymlink,
832
+ assertNoSymlinkedAncestor,
833
+ assertPathSafeForWrite,
834
+ getLauncherAbsolutePath,
835
+ };