bi-superpowers 1.0.0 → 1.1.1

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 (207) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.claude-plugin/skill-manifest.json +1 -1
  4. package/.plugin/plugin.json +1 -1
  5. package/LICENSE +21 -21
  6. package/README.md +16 -6
  7. package/bin/cli.js +8 -1
  8. package/bin/commands/create-from-template.js +5 -0
  9. package/bin/commands/install.js +100 -15
  10. package/bin/commands/session-update.js +33 -3
  11. package/bin/commands/template-path.js +62 -0
  12. package/bin/commands/uninstall.js +76 -16
  13. package/bin/commands/update-check.js +29 -18
  14. package/bin/lib/base-template-smoke.js +80 -40
  15. package/bin/lib/install-manifest.js +198 -0
  16. package/bin/lib/powerbi-mcp-session.js +63 -17
  17. package/bin/lib/template-scaffold.js +40 -5
  18. package/bin/postinstall.js +0 -0
  19. package/commands/bi-powerquery.md +2 -1
  20. package/config.example.json +23 -23
  21. package/config.json +23 -23
  22. package/desktop-extension/manifest.json +30 -30
  23. package/desktop-extension/package.json +10 -10
  24. package/desktop-extension/server.js +76 -76
  25. package/package.json +1 -1
  26. package/skills/bi-connect/SKILL.md +1 -1
  27. package/skills/bi-connect/scripts/update-check.js +30 -19
  28. package/skills/bi-dax/SKILL.md +1 -1
  29. package/skills/bi-dax/scripts/update-check.js +30 -19
  30. package/skills/bi-kickoff/SKILL.md +1 -1
  31. package/skills/bi-kickoff/scripts/update-check.js +30 -19
  32. package/skills/bi-modeling/SKILL.md +1 -1
  33. package/skills/bi-modeling/scripts/update-check.js +30 -19
  34. package/skills/bi-performance/SKILL.md +1 -1
  35. package/skills/bi-performance/scripts/update-check.js +30 -19
  36. package/skills/bi-powerquery/SKILL.md +3 -2
  37. package/skills/bi-powerquery/references/base-template-data-contract.md +5 -2
  38. package/skills/bi-powerquery/scripts/test-powerquery-contract.ps1 +24 -2
  39. package/skills/bi-powerquery/scripts/update-check.js +30 -19
  40. package/skills/bi-refactor/SKILL.md +1 -1
  41. package/skills/bi-refactor/scripts/update-check.js +30 -19
  42. package/skills/bi-scorecard/SKILL.md +1 -1
  43. package/skills/bi-scorecard/scripts/update-check.js +30 -19
  44. package/skills/bi-start/SKILL.md +1 -1
  45. package/skills/bi-start/scripts/update-check.js +30 -19
  46. package/src/content/mcp-requirements.json +57 -57
  47. package/src/content/skills/bi-powerquery/SKILL.md +2 -1
  48. package/src/content/skills/bi-powerquery/references/base-template-data-contract.md +5 -2
  49. package/src/content/skills/bi-powerquery/scripts/test-powerquery-contract.ps1 +24 -2
  50. package/templates/base-template/base-template.Report/.platform +10 -10
  51. package/templates/base-template/base-template.Report/StaticResources/RegisteredResources/BISuperpowers.json +3887 -3887
  52. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BaseThemes/CY18SU07.json +176 -176
  53. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BaseThemes/Fluent2-CY26SU03.json +4103 -4103
  54. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/AccessibleCityPark.json +25 -25
  55. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/AccessibleDefault.json +25 -25
  56. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/AccessibleNeutral.json +25 -25
  57. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/AccessibleOrchid.json +25 -25
  58. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/AccessibleTidal.json +25 -25
  59. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Bloom.json +138 -138
  60. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/CityPark.json +39 -39
  61. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Classroom.json +39 -39
  62. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/ColorblindSafe.json +47 -47
  63. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/CopilotDefault.json +1860 -1860
  64. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Divergent.json +126 -126
  65. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Electric.json +47 -47
  66. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Frontier.json +135 -135
  67. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/HighContrast.json +39 -39
  68. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Highrise.json +40 -40
  69. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Innovate.json +226 -226
  70. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/NewExecutive.json +40 -40
  71. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Solar.json +32 -32
  72. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Storm.json +24 -24
  73. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Sunset.json +47 -47
  74. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Temperature.json +32 -32
  75. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Tidal.json +99 -99
  76. package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Twilight.json +39 -39
  77. package/templates/base-template/base-template.Report/definition/bookmarks/1d40d43c7ade66e8603c.bookmark.json +2296 -2296
  78. package/templates/base-template/base-template.Report/definition/bookmarks/af068ff51c0ca3089ea7.bookmark.json +2299 -2299
  79. package/templates/base-template/base-template.Report/definition/bookmarks/bookmarks.json +10 -10
  80. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/page.json +129 -129
  81. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/0352fd80d074693a65db/mobile.json +10 -10
  82. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/0352fd80d074693a65db/visual.json +668 -668
  83. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/1c5a14bf493697344b68/mobile.json +10 -10
  84. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/1c5a14bf493697344b68/visual.json +722 -722
  85. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/3486cf7624c5b109b4e5/mobile.json +10 -10
  86. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/3486cf7624c5b109b4e5/visual.json +332 -332
  87. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/4d8b989008edc0db28d1/mobile.json +10 -10
  88. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/4d8b989008edc0db28d1/visual.json +108 -108
  89. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/55e10ac7d76a1954f94f/mobile.json +30 -30
  90. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/55e10ac7d76a1954f94f/visual.json +377 -377
  91. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/57f52ecf4490f70e4da1/mobile.json +10 -10
  92. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/57f52ecf4490f70e4da1/visual.json +174 -174
  93. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/5f4d76bbc870118e9840/mobile.json +10 -10
  94. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/5f4d76bbc870118e9840/visual.json +467 -467
  95. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/73629e1abebb7a444b59/mobile.json +10 -10
  96. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/73629e1abebb7a444b59/visual.json +359 -358
  97. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/749cb1388c7e0a88161c/mobile.json +10 -10
  98. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/749cb1388c7e0a88161c/visual.json +689 -689
  99. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/90677f13cea5d1275990/visual.json +15 -16
  100. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/92cf92e3da10493adb78/mobile.json +10 -10
  101. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/92cf92e3da10493adb78/visual.json +467 -467
  102. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/9fe17b1971f68443fc15/mobile.json +9 -9
  103. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/9fe17b1971f68443fc15/visual.json +327 -327
  104. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/a30bd0950630ed94e8a3/mobile.json +10 -10
  105. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/a30bd0950630ed94e8a3/visual.json +577 -577
  106. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/a56e91d9400a835e4814/mobile.json +10 -10
  107. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/a56e91d9400a835e4814/visual.json +431 -431
  108. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/aded24cd205c0b528642/mobile.json +10 -10
  109. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/aded24cd205c0b528642/visual.json +800 -800
  110. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/af34b26f14a8a724c9a9/mobile.json +36 -36
  111. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/af34b26f14a8a724c9a9/visual.json +1317 -1317
  112. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/b529688fe5a226643322/visual.json +208 -208
  113. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/c4c6f332d05e72e2eb06/mobile.json +10 -10
  114. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/c4c6f332d05e72e2eb06/visual.json +173 -173
  115. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/fa81f184e2cb0e8b087c/mobile.json +28 -28
  116. package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/fa81f184e2cb0e8b087c/visual.json +240 -240
  117. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/page.json +129 -129
  118. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/07e9c4302e29029c5462/mobile.json +10 -10
  119. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/07e9c4302e29029c5462/visual.json +689 -689
  120. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/109ceede4bc015b0c006/mobile.json +10 -10
  121. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/109ceede4bc015b0c006/visual.json +467 -467
  122. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/118257e006d472277e10/mobile.json +10 -10
  123. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/118257e006d472277e10/visual.json +358 -358
  124. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/2caf02e0137c4a1280cc/mobile.json +10 -10
  125. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/2caf02e0137c4a1280cc/visual.json +668 -668
  126. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/311e76fe3c9edad68204/mobile.json +10 -10
  127. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/311e76fe3c9edad68204/visual.json +108 -108
  128. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/31c21f8cbeb3b208940a/visual.json +208 -208
  129. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/3ab72c25062437149b03/visual.json +16 -16
  130. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/5959867442abcb0ce2b3/mobile.json +10 -10
  131. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/5959867442abcb0ce2b3/visual.json +787 -787
  132. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/5b96e0f88d192b044a13/mobile.json +10 -10
  133. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/5b96e0f88d192b044a13/visual.json +591 -591
  134. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/64e749a63d0786000e22/mobile.json +10 -10
  135. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/64e749a63d0786000e22/visual.json +467 -467
  136. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/7ae1ca604edac6586ad0/mobile.json +10 -10
  137. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/7ae1ca604edac6586ad0/visual.json +1309 -1309
  138. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/840300733885141a6603/mobile.json +10 -10
  139. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/840300733885141a6603/visual.json +174 -174
  140. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/a38448cdb203279273d2/mobile.json +10 -10
  141. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/a38448cdb203279273d2/visual.json +515 -515
  142. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/d1e86f213a3841d12e20/visual.json +327 -327
  143. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/d4a484c1bcc8ee3075e2/mobile.json +10 -10
  144. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/d4a484c1bcc8ee3075e2/visual.json +431 -431
  145. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/d87cb5cf06acca19bbb5/mobile.json +10 -10
  146. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/d87cb5cf06acca19bbb5/visual.json +240 -240
  147. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/e243da2677209ed69408/mobile.json +10 -10
  148. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/e243da2677209ed69408/visual.json +173 -173
  149. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/f3aaf24f5b22b67573b0/mobile.json +10 -10
  150. package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/f3aaf24f5b22b67573b0/visual.json +332 -332
  151. package/templates/base-template/base-template.Report/definition/pages/pages.json +7 -7
  152. package/templates/base-template/base-template.Report/definition/report.json +88 -88
  153. package/templates/base-template/base-template.Report/definition/version.json +3 -3
  154. package/templates/base-template/base-template.Report/definition.pbir +8 -8
  155. package/templates/base-template/base-template.SemanticModel/.platform +10 -10
  156. package/templates/base-template/base-template.SemanticModel/definition/cultures/es-AR.tmdl +11185 -11185
  157. package/templates/base-template/base-template.SemanticModel/definition/database.tmdl +5 -3
  158. package/templates/base-template/base-template.SemanticModel/definition/expressions.tmdl +340 -234
  159. package/templates/base-template/base-template.SemanticModel/definition/functions.tmdl +637 -637
  160. package/templates/base-template/base-template.SemanticModel/definition/model.tmdl +85 -82
  161. package/templates/base-template/base-template.SemanticModel/definition/relationships.tmdl +263 -271
  162. package/templates/base-template/base-template.SemanticModel/definition/tables/Calendario.tmdl +200 -200
  163. package/templates/base-template/base-template.SemanticModel/definition/tables/Campa/303/261as.tmdl +75 -75
  164. package/templates/base-template/base-template.SemanticModel/definition/tables/Canales.tmdl +84 -84
  165. package/templates/base-template/base-template.SemanticModel/definition/tables/Clientes.tmdl +126 -143
  166. package/templates/base-template/base-template.SemanticModel/definition/tables/Devoluciones.tmdl +64 -95
  167. package/templates/base-template/base-template.SemanticModel/definition/tables/Ejecuci/303/263n proyectos.tmdl" +98 -130
  168. package/templates/base-template/base-template.SemanticModel/definition/tables/Entregas.tmdl +77 -122
  169. package/templates/base-template/base-template.SemanticModel/definition/tables/Equipos m/303/251tricas.tmdl" +40 -40
  170. package/templates/base-template/base-template.SemanticModel/definition/tables/Equipos.tmdl +47 -73
  171. package/templates/base-template/base-template.SemanticModel/definition/tables/Horas.tmdl +91 -122
  172. package/templates/base-template/base-template.SemanticModel/definition/tables/Interacciones clientes.tmdl +95 -146
  173. package/templates/base-template/base-template.SemanticModel/definition/tables/Leads.tmdl +90 -119
  174. package/templates/base-template/base-template.SemanticModel/definition/tables/Monedas.tmdl +44 -44
  175. package/templates/base-template/base-template.SemanticModel/definition/tables/Movimientos financieros.tmdl +146 -145
  176. package/templates/base-template/base-template.SemanticModel/definition/tables/M/303/251tricas.tmdl +1324 -1294
  177. package/templates/base-template/base-template.SemanticModel/definition/tables/N/303/263mina.tmdl +82 -110
  178. package/templates/base-template/base-template.SemanticModel/definition/tables/Oportunidades.tmdl +83 -135
  179. package/templates/base-template/base-template.SemanticModel/definition/tables/Presupuesto.tmdl +101 -125
  180. package/templates/base-template/base-template.SemanticModel/definition/tables/Productos.tmdl +98 -98
  181. package/templates/base-template/base-template.SemanticModel/definition/tables/Proyectos.tmdl +77 -77
  182. package/templates/base-template/base-template.SemanticModel/definition/tables/Servicios.tmdl +75 -75
  183. package/templates/base-template/base-template.SemanticModel/definition/tables/Tareas proyecto.tmdl +73 -102
  184. package/templates/base-template/base-template.SemanticModel/definition/tables/Tipo de cambio.tmdl +67 -67
  185. package/templates/base-template/base-template.SemanticModel/definition/tables/Ventas.tmdl +142 -180
  186. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux An/303/241lisis dimensiones.tmdl" +38 -38
  187. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Comparaciones.tmdl +227 -227
  188. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Compatibilidad m/303/251trica-dimensi/303/263n.tmdl" +68 -68
  189. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Modelo configuraci/303/263n.tmdl" +44 -44
  190. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Modo fechas.tmdl +36 -36
  191. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux M/303/251trica-Equipo.tmdl" +101 -102
  192. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Overrides m/303/251trica-dimensi/303/263n.tmdl" +57 -54
  193. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Per/303/255odos.tmdl" +182 -182
  194. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Rango fechas modo.tmdl +36 -36
  195. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Rango fechas.tmdl +27 -27
  196. package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Vista de calendario.tmdl +30 -30
  197. package/templates/base-template/base-template.SemanticModel/definition/tables/_GC C/303/241lculo.tmdl" +70 -70
  198. package/templates/base-template/base-template.SemanticModel/definition/tables/_GC Eje X.tmdl +63 -63
  199. package/templates/base-template/base-template.SemanticModel/definition/tables/_GC M/303/251trica.tmdl" +374 -374
  200. package/templates/base-template/base-template.SemanticModel/definition/tables/_GC Tipo c/303/241lculo.tmdl" +223 -223
  201. package/templates/base-template/base-template.SemanticModel/definition/tables/_PC Dimensi/303/263n.tmdl" +98 -98
  202. package/templates/base-template/base-template.SemanticModel/definition/tables/_PC Eje X.tmdl +68 -68
  203. package/templates/base-template/base-template.SemanticModel/definition/tables//303/223rdenes servicio.tmdl" +121 -151
  204. package/templates/base-template/base-template.SemanticModel/definition.pbism +4 -4
  205. package/templates/base-template/base-template.SemanticModel/diagramLayout.json +567 -567
  206. package/templates/base-template/base-template.pbip +13 -13
  207. package/theme/BISuperpowers.json +3887 -3887
@@ -40,6 +40,15 @@ const os = require('os');
40
40
  const readline = require('readline');
41
41
  const { AGENTS, UNIVERSAL_DIR } = require('../lib/agents');
42
42
  const { removeMcpConfigForAgent } = require('../lib/mcp-config');
43
+ const {
44
+ readInstallManifest,
45
+ removeInstallManifest,
46
+ getAgentSkillRecord,
47
+ isManifestOwnedSymlink,
48
+ samePath,
49
+ packageKey,
50
+ } = require('../lib/install-manifest');
51
+ const { disableAutoupdate } = require('./autoupdate');
43
52
 
44
53
  const LEGACY_SESSION_DIR = '.bi-superpowers';
45
54
 
@@ -281,7 +290,7 @@ function removeIfEmpty(dir) {
281
290
  *
282
291
  * @returns {{ action: string, dir?: string, count?: number }}
283
292
  */
284
- function removeAgentSkills(baseDir, agentId, skillNames, dryRun, removeEmpty) {
293
+ function removeAgentSkills(baseDir, agentId, skillNames, dryRun, removeEmpty, manifest = null) {
285
294
  const agent = AGENTS[agentId];
286
295
  if (agent.dir === UNIVERSAL_DIR) {
287
296
  return { action: 'universal' };
@@ -294,14 +303,23 @@ function removeAgentSkills(baseDir, agentId, skillNames, dryRun, removeEmpty) {
294
303
  }
295
304
 
296
305
  if (st.isSymbolicLink()) {
306
+ const universalTarget = path.join(baseDir, UNIVERSAL_DIR);
307
+ if (!isManifestOwnedSymlink(manifest, agentId, dir, universalTarget)) {
308
+ return { action: 'preserved-symlink', dir: agent.dir };
309
+ }
297
310
  if (!dryRun) fs.unlinkSync(dir);
298
311
  if (removeEmpty && !dryRun) removeIfEmpty(path.dirname(dir));
299
312
  return { action: 'unlinked', dir: agent.dir };
300
313
  }
301
314
 
302
315
  // Real directory (copy fallback or user-managed): remove only our subdirs.
316
+ const record = getAgentSkillRecord(manifest, agentId);
317
+ if (!record || record.method !== 'copied' || !samePath(record.path, dir)) {
318
+ return { action: 'unmanaged-directory', dir: agent.dir, count: 0 };
319
+ }
303
320
  let count = 0;
304
- for (const name of skillNames) {
321
+ const ownedSkills = Array.isArray(record.skills) ? record.skills : skillNames;
322
+ for (const name of ownedSkills) {
305
323
  const sub = path.join(dir, name);
306
324
  if (fs.existsSync(sub)) {
307
325
  if (!dryRun) fs.rmSync(sub, { recursive: true, force: true });
@@ -321,7 +339,7 @@ function removeAgentSkills(baseDir, agentId, skillNames, dryRun, removeEmpty) {
321
339
  *
322
340
  * @returns {{ action: string, count?: number }}
323
341
  */
324
- function removeUniversalSkills(baseDir, skillNames, dryRun, removeEmpty) {
342
+ function removeUniversalSkills(baseDir, skillNames, dryRun, removeEmpty, manifest = null) {
325
343
  const universal = path.join(baseDir, UNIVERSAL_DIR);
326
344
  const st = fs.lstatSync(universal, { throwIfNoEntry: false });
327
345
  if (!st) {
@@ -329,12 +347,17 @@ function removeUniversalSkills(baseDir, skillNames, dryRun, removeEmpty) {
329
347
  }
330
348
 
331
349
  if (st.isSymbolicLink()) {
332
- if (!dryRun) fs.unlinkSync(universal);
333
- return { action: 'unlinked' };
350
+ return { action: 'preserved-symlink' };
351
+ }
352
+
353
+ const record = manifest && manifest.skills && manifest.skills.universal;
354
+ if (!record || !samePath(record.path, universal)) {
355
+ return { action: 'unmanaged-directory', count: 0 };
334
356
  }
335
357
 
336
358
  let count = 0;
337
- for (const name of skillNames) {
359
+ const ownedSkills = Array.isArray(record.skills) ? record.skills : skillNames;
360
+ for (const name of ownedSkills) {
338
361
  const sub = path.join(universal, name);
339
362
  if (fs.existsSync(sub)) {
340
363
  if (!dryRun) fs.rmSync(sub, { recursive: true, force: true });
@@ -374,13 +397,23 @@ function universalIsFullyReleased(baseDir, selectedAgents) {
374
397
  *
375
398
  * @returns {{ existed: boolean, path: string }}
376
399
  */
377
- function cleanLegacySessionDir(baseDir, dryRun) {
400
+ function cleanLegacySessionDir(baseDir, dryRun, packageName = 'bi-superpowers') {
378
401
  const dir = path.join(baseDir, LEGACY_SESSION_DIR);
379
- const existed = fs.existsSync(dir);
380
- if (existed && !dryRun) {
381
- fs.rmSync(dir, { recursive: true, force: true });
382
- }
383
- return { existed, path: dir };
402
+ const candidates = [
403
+ 'auth.json',
404
+ 'session.json',
405
+ 'update-state.json',
406
+ 'autoupdate-state.json',
407
+ `update-state-${packageKey(packageName)}.json`,
408
+ `autoupdate-state-${packageKey(packageName)}.json`,
409
+ `install-manifest-${packageKey(packageName)}.json`,
410
+ ].map((name) => path.join(dir, name));
411
+ const existing = candidates.filter((file) => fs.existsSync(file));
412
+ if (!dryRun) {
413
+ for (const file of existing) fs.rmSync(file, { force: true });
414
+ removeIfEmpty(dir);
415
+ }
416
+ return { existed: existing.length > 0, path: dir, removed: existing };
384
417
  }
385
418
 
386
419
  /**
@@ -395,7 +428,12 @@ async function uninstallCommand(args, config) {
395
428
  const opts = parseArgs(args);
396
429
  const baseDir = os.homedir();
397
430
  const packageDir = config.packageDir || path.dirname(path.dirname(__dirname));
398
- const skillNames = getManagedSkillNames(packageDir);
431
+ const packageName = config.packageName || 'bi-superpowers';
432
+ const manifest = readInstallManifest(baseDir, packageName);
433
+ const skillNames =
434
+ manifest && manifest.skills && Array.isArray(manifest.skills.names)
435
+ ? manifest.skills.names
436
+ : getManagedSkillNames(packageDir);
399
437
 
400
438
  console.log(
401
439
  boxen(
@@ -448,12 +486,29 @@ async function uninstallCommand(args, config) {
448
486
  )
449
487
  );
450
488
 
489
+ // Uninstall always disables the Claude Code auto-update hook so a removed
490
+ // package cannot keep reinstalling itself in future sessions.
491
+ try {
492
+ if (dry) {
493
+ console.log(chalk.gray(' · Claude Code auto-update hook would be disabled'));
494
+ } else {
495
+ const hook = disableAutoupdate();
496
+ console.log(
497
+ hook.removed
498
+ ? chalk.green(' ✓ Claude Code auto-update hook disabled')
499
+ : chalk.gray(' – Claude Code auto-update hook was not enabled')
500
+ );
501
+ }
502
+ } catch (err) {
503
+ console.log(chalk.red(` ✗ Could not disable Claude Code auto-update: ${err.message}`));
504
+ }
505
+
451
506
  // Phase 1: skills.
452
507
  const skillResults = [];
453
508
  if (doSkills) {
454
509
  for (const agentId of selectedAgents) {
455
510
  try {
456
- const result = removeAgentSkills(baseDir, agentId, skillNames, dry, true);
511
+ const result = removeAgentSkills(baseDir, agentId, skillNames, dry, true, manifest);
457
512
  skillResults.push({ agent: AGENTS[agentId].name, ...result });
458
513
  } catch (err) {
459
514
  skillResults.push({ agent: AGENTS[agentId].name, action: 'error', error: err.message });
@@ -462,7 +517,7 @@ async function uninstallCommand(args, config) {
462
517
 
463
518
  if (releaseUniversal) {
464
519
  try {
465
- const uni = removeUniversalSkills(baseDir, skillNames, dry, true);
520
+ const uni = removeUniversalSkills(baseDir, skillNames, dry, true, manifest);
466
521
  skillResults.push({ agent: `${UNIVERSAL_DIR}`, ...uni });
467
522
  } catch (err) {
468
523
  skillResults.push({ agent: UNIVERSAL_DIR, action: 'error', error: err.message });
@@ -480,6 +535,10 @@ async function uninstallCommand(args, config) {
480
535
  console.log(chalk.green(` ${dry ? '·' : '✓'} ${r.agent}/ (${r.count} skills removed)`));
481
536
  } else if (r.action === 'absent') {
482
537
  console.log(chalk.gray(` – ${r.agent}: no managed skills found`));
538
+ } else if (r.action === 'preserved-symlink') {
539
+ console.log(chalk.yellow(` ⚠ ${r.dir || r.agent}: foreign symlink preserved`));
540
+ } else if (r.action === 'unmanaged-directory') {
541
+ console.log(chalk.yellow(` ⚠ ${r.dir || r.agent}: unowned directory preserved`));
483
542
  } else if (r.action === 'error') {
484
543
  console.log(chalk.red(` ✗ ${r.agent}: ${r.error}`));
485
544
  }
@@ -522,7 +581,7 @@ async function uninstallCommand(args, config) {
522
581
  // Phase 3: legacy session dir.
523
582
  let legacy = { existed: false };
524
583
  if (doExtras) {
525
- legacy = cleanLegacySessionDir(baseDir, dry);
584
+ legacy = cleanLegacySessionDir(baseDir, dry, packageName);
526
585
  if (legacy.existed) {
527
586
  console.log(
528
587
  chalk.green(
@@ -530,6 +589,7 @@ async function uninstallCommand(args, config) {
530
589
  )
531
590
  );
532
591
  }
592
+ if (!dry) removeInstallManifest(baseDir, packageName);
533
593
  }
534
594
 
535
595
  // Summary.
@@ -49,6 +49,12 @@ const HTTPS_TIMEOUT_MS = 5000;
49
49
  // `bin/commands/`, it stays null and we fall back to package.json.
50
50
  const BUNDLED_INSTALLED_VERSION = null;
51
51
 
52
+ // Keep this helper dependency-free because the generator copies this script
53
+ // into every installed skill as a standalone update checker.
54
+ function packageKey(packageName) {
55
+ return Buffer.from(String(packageName || PACKAGE_NAME), 'utf8').toString('base64url');
56
+ }
57
+
52
58
  // ---------------------------------------------------------------------------
53
59
  // Argument parsing
54
60
  // ---------------------------------------------------------------------------
@@ -173,12 +179,12 @@ function defaultStateDir() {
173
179
  return path.join(os.homedir(), '.bi-superpowers');
174
180
  }
175
181
 
176
- function stateFilePath(stateDir) {
177
- return path.join(stateDir, 'update-state.json');
182
+ function stateFilePath(stateDir, packageName = PACKAGE_NAME) {
183
+ return path.join(stateDir, `update-state-${packageKey(packageName)}.json`);
178
184
  }
179
185
 
180
- function readState(stateDir) {
181
- const filePath = stateFilePath(stateDir);
186
+ function readState(stateDir, packageName = PACKAGE_NAME) {
187
+ const filePath = stateFilePath(stateDir, packageName);
182
188
  if (!fs.existsSync(filePath)) return null;
183
189
  try {
184
190
  return JSON.parse(fs.readFileSync(filePath, 'utf8'));
@@ -188,13 +194,13 @@ function readState(stateDir) {
188
194
  }
189
195
  }
190
196
 
191
- function writeState(stateDir, state) {
197
+ function writeState(stateDir, state, packageName = PACKAGE_NAME) {
192
198
  fs.mkdirSync(stateDir, { recursive: true });
193
- fs.writeFileSync(stateFilePath(stateDir), JSON.stringify(state, null, 2) + '\n');
199
+ fs.writeFileSync(stateFilePath(stateDir, packageName), JSON.stringify(state, null, 2) + '\n');
194
200
  }
195
201
 
196
- function resetState(stateDir) {
197
- const filePath = stateFilePath(stateDir);
202
+ function resetState(stateDir, packageName = PACKAGE_NAME) {
203
+ const filePath = stateFilePath(stateDir, packageName);
198
204
  if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
199
205
  }
200
206
 
@@ -335,22 +341,26 @@ async function main() {
335
341
  const packageName = args.packageName || PACKAGE_NAME;
336
342
 
337
343
  if (args.reset) {
338
- resetState(stateDir);
344
+ resetState(stateDir, packageName);
339
345
  return;
340
346
  }
341
347
 
342
348
  if (args.snooze) {
343
349
  const now = Date.now();
344
- const prior = readState(stateDir) || {};
350
+ const prior = readState(stateDir, packageName) || {};
345
351
  const parsed = parseSnoozeArg(args.snooze, now, prior.snoozeLevel || 0);
346
352
  if (parsed.clear) {
347
- writeState(stateDir, { ...prior, snoozeUntil: null, snoozeLevel: 0 });
353
+ writeState(stateDir, { ...prior, snoozeUntil: null, snoozeLevel: 0 }, packageName);
348
354
  } else {
349
- writeState(stateDir, {
350
- ...prior,
351
- snoozeUntil: parsed.until,
352
- snoozeLevel: parsed.level,
353
- });
355
+ writeState(
356
+ stateDir,
357
+ {
358
+ ...prior,
359
+ snoozeUntil: parsed.until,
360
+ snoozeLevel: parsed.level,
361
+ },
362
+ packageName
363
+ );
354
364
  }
355
365
  return;
356
366
  }
@@ -362,7 +372,7 @@ async function main() {
362
372
  }
363
373
 
364
374
  const now = Date.now();
365
- let state = readState(stateDir);
375
+ let state = readState(stateDir, packageName);
366
376
 
367
377
  // Snooze short-circuits everything except --force.
368
378
  if (!args.force && isSnoozed(state, now)) {
@@ -383,7 +393,7 @@ async function main() {
383
393
  snoozeUntil: (state && state.snoozeUntil) || null,
384
394
  snoozeLevel: (state && state.snoozeLevel) || 0,
385
395
  };
386
- writeState(stateDir, nextState);
396
+ writeState(stateDir, nextState, packageName);
387
397
  state = nextState;
388
398
  }
389
399
  // If fetched is null (network fail), we keep using the previous cache
@@ -412,6 +422,7 @@ module.exports = {
412
422
  readState,
413
423
  writeState,
414
424
  resetState,
425
+ stateFilePath,
415
426
  fetchLatestVersion,
416
427
  readInstalledVersion,
417
428
  CACHE_TTL_MS,
@@ -27,6 +27,8 @@ const SMOKE_QUERY_PATTERN = /## Smoke Query\s+```DAX\s+([\s\S]*?)```/i;
27
27
  const DAY_MS = 24 * 60 * 60 * 1000;
28
28
  const WEEK_MS = 7 * DAY_MS;
29
29
  const DEMO_CUSTOMER_COUNT = 210;
30
+ const DEMO_INITIAL_CUSTOMER_COUNT = 90;
31
+ const DEFAULT_START_DATE = new Date(2023, 0, 1);
30
32
 
31
33
  function extractSmokeQuery(caseMarkdown) {
32
34
  const match = caseMarkdown.match(SMOKE_QUERY_PATTERN);
@@ -44,12 +46,6 @@ function formatLocalDate(date) {
44
46
  return `${year}-${month}-${day}`;
45
47
  }
46
48
 
47
- function addLocalDays(date, days) {
48
- const result = new Date(date.getFullYear(), date.getMonth(), date.getDate());
49
- result.setDate(result.getDate() + days);
50
- return result;
51
- }
52
-
53
49
  function startOfMondayWeek(date) {
54
50
  const result = new Date(date.getFullYear(), date.getMonth(), date.getDate());
55
51
  const mondayOffset = (result.getDay() + 6) % 7;
@@ -63,11 +59,25 @@ function countMondayWeeks(startDate, endDate) {
63
59
  return Math.floor((lastWeek.getTime() - firstWeek.getTime()) / WEEK_MS) + 1;
64
60
  }
65
61
 
66
- function estimateVentasRows(weekCount, customerCount = DEMO_CUSTOMER_COUNT) {
62
+ function estimateVentasRows(
63
+ weekCount,
64
+ customerCount = DEMO_CUSTOMER_COUNT,
65
+ initialCustomerCount = DEMO_INITIAL_CUSTOMER_COUNT
66
+ ) {
67
+ const initial = Math.min(initialCustomerCount, Math.max(0, customerCount - 1));
68
+ const remaining = customerCount - initial;
67
69
  let rows = 0;
68
70
 
69
- for (let weekNumber = 1; weekNumber <= weekCount; weekNumber++) {
70
- rows += Math.min(customerCount, weekNumber * 2);
71
+ for (let weekIndex = 0; weekIndex < weekCount; weekIndex++) {
72
+ let active = initial;
73
+ for (let customerIndex = 0; customerIndex < remaining; customerIndex++) {
74
+ const acquisitionWeek =
75
+ remaining <= 1 || weekCount <= 1
76
+ ? 0
77
+ : Math.floor((customerIndex * (weekCount - 1)) / (remaining - 1));
78
+ if (acquisitionWeek <= weekIndex) active++;
79
+ }
80
+ rows += active;
71
81
  }
72
82
 
73
83
  return rows;
@@ -79,10 +89,13 @@ function getExpectedSmokeEvidence(referenceDate = new Date()) {
79
89
  referenceDate.getMonth(),
80
90
  referenceDate.getDate()
81
91
  );
82
- const minDate = addLocalDays(currentDate, -729);
83
- const validationStartDate = addLocalDays(currentDate, -719);
84
- const ventasWeeks = countMondayWeeks(minDate, currentDate);
85
- const validationWeeks = countMondayWeeks(validationStartDate, currentDate);
92
+ const minDate = new Date(
93
+ DEFAULT_START_DATE.getFullYear(),
94
+ DEFAULT_START_DATE.getMonth(),
95
+ DEFAULT_START_DATE.getDate()
96
+ );
97
+ const maxFactDate = startOfMondayWeek(currentDate);
98
+ const validationWeeks = countMondayWeeks(minDate, currentDate);
86
99
 
87
100
  return {
88
101
  Metricas: '56',
@@ -93,11 +106,19 @@ function getExpectedSmokeEvidence(referenceDate = new Date()) {
93
106
  WindowWeeks: String(validationWeeks),
94
107
  FactTablesWithFullWeeks: '13',
95
108
  MetricCoverageFailures: '0',
96
- MetricDimensionPairs: '379',
109
+ MetricDimensionPairs: '415',
97
110
  MetricDimensionFailures: '0',
98
- VentasRows: String(estimateVentasRows(ventasWeeks)),
111
+ VisibleDimensionFailures: '0',
112
+ MaxVisibleDimensionValues: '13',
113
+ BusinessInvariantFailures: '0',
114
+ YearRangeFailures: '0',
115
+ TeamRangeFailures: '0',
116
+ Last365RangeFailures: '0',
117
+ VentasRows: String(estimateVentasRows(validationWeeks)),
99
118
  MinFecha: formatLocalDate(minDate),
100
- MaxFecha: formatLocalDate(currentDate),
119
+ MaxFecha: formatLocalDate(maxFactDate),
120
+ MaxCalendario: formatLocalDate(currentDate),
121
+ UltimoClienteNuevo: formatLocalDate(maxFactDate),
101
122
  };
102
123
  }
103
124
 
@@ -196,15 +217,22 @@ async function runBaseTemplateLiveSmokeTest(options = {}) {
196
217
  const expected = getExpectedSmokeEvidence(referenceDate);
197
218
  const session = sessionFactory(serverPath);
198
219
  const attempts = [];
220
+ const connectionTimeoutMs = options.connectionTimeoutMs || 30_000;
221
+ const daxTimeoutMs = options.daxTimeoutMs || 210_000;
222
+ const signal = options.signal;
199
223
 
200
224
  try {
201
- await session.initialize(options.clientInfo);
202
-
203
- const localInstancesResult = await session.callTool('connection_operations', {
204
- request: {
205
- operation: 'ListLocalInstances',
225
+ await session.initialize(options.clientInfo, { timeoutMs: connectionTimeoutMs, signal });
226
+
227
+ const localInstancesResult = await session.callTool(
228
+ 'connection_operations',
229
+ {
230
+ request: {
231
+ operation: 'ListLocalInstances',
232
+ },
206
233
  },
207
- });
234
+ { timeoutMs: connectionTimeoutMs, signal }
235
+ );
208
236
  const localInstances = normalizeMcpJsonResult(localInstancesResult).data || [];
209
237
 
210
238
  for (const instance of localInstances) {
@@ -218,31 +246,43 @@ async function runBaseTemplateLiveSmokeTest(options = {}) {
218
246
  };
219
247
 
220
248
  try {
221
- await session.callTool('connection_operations', {
222
- request: {
223
- operation: 'Connect',
224
- connectionString: `Provider=MSOLAP;Data Source=localhost:${instance.port};Application Name=MCP-PBIModeling`,
249
+ await session.callTool(
250
+ 'connection_operations',
251
+ {
252
+ request: {
253
+ operation: 'Connect',
254
+ connectionString: `Provider=MSOLAP;Data Source=localhost:${instance.port};Application Name=MCP-PBIModeling`,
255
+ },
225
256
  },
226
- });
227
-
228
- const connectionsResult = await session.callTool('connection_operations', {
229
- request: {
230
- operation: 'ListConnections',
257
+ { timeoutMs: connectionTimeoutMs, signal }
258
+ );
259
+
260
+ const connectionsResult = await session.callTool(
261
+ 'connection_operations',
262
+ {
263
+ request: {
264
+ operation: 'ListConnections',
265
+ },
231
266
  },
232
- });
267
+ { timeoutMs: connectionTimeoutMs, signal }
268
+ );
233
269
  const connections = normalizeMcpJsonResult(connectionsResult).data || [];
234
270
  const selectedConnection = selectConnectionForPort(connections, instance.port);
235
271
  attempt.connectionName = selectedConnection ? selectedConnection.connectionName : null;
236
272
 
237
- const daxResult = await session.callTool('dax_query_operations', {
238
- request: {
239
- operation: 'Execute',
240
- connectionName: attempt.connectionName || undefined,
241
- timeoutSeconds: 180,
242
- maxRows: 1,
243
- query: smokeQuery,
273
+ const daxResult = await session.callTool(
274
+ 'dax_query_operations',
275
+ {
276
+ request: {
277
+ operation: 'Execute',
278
+ connectionName: attempt.connectionName || undefined,
279
+ timeoutSeconds: 180,
280
+ maxRows: 1,
281
+ query: smokeQuery,
282
+ },
244
283
  },
245
- });
284
+ { timeoutMs: daxTimeoutMs, signal }
285
+ );
246
286
 
247
287
  const observed = parseSingleRowCsv(extractCsvText(daxResult));
248
288
  attempt.observed = observed;
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Install Ownership Manifest
3
+ * ==========================
4
+ *
5
+ * Records the user-level files and links created by `super install`.
6
+ * Uninstall and future upgrades use this file as proof of ownership instead
7
+ * of assuming that every similarly named path belongs to bi-superpowers.
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ const MANIFEST_SCHEMA_VERSION = 1;
14
+ const STATE_DIR = '.bi-superpowers';
15
+
16
+ function packageKey(packageName) {
17
+ return Buffer.from(String(packageName || 'bi-superpowers'), 'utf8').toString('base64url');
18
+ }
19
+
20
+ function manifestFilePath(homeDir, packageName = 'bi-superpowers') {
21
+ return path.join(homeDir, STATE_DIR, `install-manifest-${packageKey(packageName)}.json`);
22
+ }
23
+
24
+ function readInstallManifest(homeDir, packageName = 'bi-superpowers') {
25
+ const file = manifestFilePath(homeDir, packageName);
26
+ if (!fs.existsSync(file)) return null;
27
+ try {
28
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
29
+ if (
30
+ !parsed ||
31
+ parsed.schemaVersion !== MANIFEST_SCHEMA_VERSION ||
32
+ parsed.packageName !== packageName
33
+ ) {
34
+ return null;
35
+ }
36
+ return parsed;
37
+ } catch (_) {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ function writeInstallManifest(homeDir, manifest) {
43
+ const packageName = manifest.packageName || 'bi-superpowers';
44
+ const file = manifestFilePath(homeDir, packageName);
45
+ const parent = path.dirname(file);
46
+ fs.mkdirSync(parent, { recursive: true });
47
+
48
+ const leaf = fs.lstatSync(file, { throwIfNoEntry: false });
49
+ if (leaf && leaf.isSymbolicLink()) {
50
+ throw new Error(`Refusing to overwrite symlinked install manifest: ${file}`);
51
+ }
52
+
53
+ const temp = `${file}.tmp`;
54
+ const tempLeaf = fs.lstatSync(temp, { throwIfNoEntry: false });
55
+ if (tempLeaf && tempLeaf.isSymbolicLink()) {
56
+ throw new Error(`Refusing to overwrite symlinked temporary manifest: ${temp}`);
57
+ }
58
+
59
+ fs.writeFileSync(temp, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
60
+ try {
61
+ fs.renameSync(temp, file);
62
+ } catch (error) {
63
+ // Windows cannot always atomically replace an existing file. The content
64
+ // is already complete in the sibling temp file, so replace in two steps.
65
+ if (!['EEXIST', 'EPERM'].includes(error.code)) throw error;
66
+ fs.rmSync(file, { force: true });
67
+ fs.renameSync(temp, file);
68
+ }
69
+ return file;
70
+ }
71
+
72
+ function removeInstallManifest(homeDir, packageName = 'bi-superpowers') {
73
+ const file = manifestFilePath(homeDir, packageName);
74
+ if (fs.existsSync(file)) fs.unlinkSync(file);
75
+ }
76
+
77
+ function normalizeForComparison(value) {
78
+ const resolved = path.resolve(String(value));
79
+ return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
80
+ }
81
+
82
+ function samePath(left, right) {
83
+ if (!left || !right) return false;
84
+ return normalizeForComparison(left) === normalizeForComparison(right);
85
+ }
86
+
87
+ function resolveSymlinkTarget(linkPath) {
88
+ const stat = fs.lstatSync(linkPath, { throwIfNoEntry: false });
89
+ if (!stat || !stat.isSymbolicLink()) return null;
90
+ try {
91
+ const rawTarget = fs.readlinkSync(linkPath);
92
+ return path.resolve(path.dirname(linkPath), rawTarget);
93
+ } catch (_) {
94
+ return null;
95
+ }
96
+ }
97
+
98
+ function getAgentSkillRecord(manifest, agentId) {
99
+ return manifest && manifest.skills && manifest.skills.agents
100
+ ? manifest.skills.agents[agentId] || null
101
+ : null;
102
+ }
103
+
104
+ function isManifestOwnedSymlink(manifest, agentId, linkPath, expectedTarget) {
105
+ const record = getAgentSkillRecord(manifest, agentId);
106
+ if (!record || record.method !== 'symlinked') return false;
107
+ const actualTarget = resolveSymlinkTarget(linkPath);
108
+ return (
109
+ samePath(record.path, linkPath) &&
110
+ samePath(record.target, expectedTarget) &&
111
+ samePath(actualTarget, expectedTarget)
112
+ );
113
+ }
114
+
115
+ /**
116
+ * One-time migration proof for installs created before manifests existed.
117
+ * The link must point exactly at the universal directory and that directory
118
+ * must contain every skill shipped by the package being installed.
119
+ */
120
+ function isVerifiedLegacyManagedSymlink(linkPath, universalTarget, skillNames) {
121
+ const actualTarget = resolveSymlinkTarget(linkPath);
122
+ if (!samePath(actualTarget, universalTarget) || skillNames.length === 0) {
123
+ return false;
124
+ }
125
+ return skillNames.every((skillName) => {
126
+ const skillFile = path.join(universalTarget, skillName, 'SKILL.md');
127
+ if (!fs.existsSync(skillFile)) return false;
128
+ try {
129
+ return fs.readFileSync(skillFile, 'utf8').includes(`name: ${skillName}`);
130
+ } catch (_) {
131
+ return false;
132
+ }
133
+ });
134
+ }
135
+
136
+ function createInstallManifest({
137
+ packageName,
138
+ packageVersion,
139
+ skillNames,
140
+ universalTarget,
141
+ agentResults,
142
+ mcpResults,
143
+ autoUpdateEnabled,
144
+ }) {
145
+ const agents = {};
146
+ for (const result of agentResults) {
147
+ if (!result.agentId || result.method === 'preserved-foreign') continue;
148
+ agents[result.agentId] = {
149
+ method: result.method,
150
+ path: result.path,
151
+ target: result.target || null,
152
+ skills: [...skillNames],
153
+ };
154
+ }
155
+
156
+ return {
157
+ schemaVersion: MANIFEST_SCHEMA_VERSION,
158
+ packageName,
159
+ packageVersion,
160
+ installedAt: new Date().toISOString(),
161
+ skills: {
162
+ names: [...skillNames],
163
+ universal: {
164
+ path: universalTarget,
165
+ skills: [...skillNames],
166
+ },
167
+ agents,
168
+ },
169
+ mcp: {
170
+ servers: ['powerbi-modeling-mcp', 'microsoft-learn'],
171
+ agents: mcpResults
172
+ .filter((result) => result.success && result.configPath)
173
+ .map((result) => ({
174
+ agentId: result.agentId,
175
+ configPath: result.configPath,
176
+ })),
177
+ },
178
+ autoUpdate: {
179
+ enabled: Boolean(autoUpdateEnabled),
180
+ },
181
+ };
182
+ }
183
+
184
+ module.exports = {
185
+ MANIFEST_SCHEMA_VERSION,
186
+ STATE_DIR,
187
+ packageKey,
188
+ manifestFilePath,
189
+ readInstallManifest,
190
+ writeInstallManifest,
191
+ removeInstallManifest,
192
+ samePath,
193
+ resolveSymlinkTarget,
194
+ getAgentSkillRecord,
195
+ isManifestOwnedSymlink,
196
+ isVerifiedLegacyManagedSymlink,
197
+ createInstallManifest,
198
+ };