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.
- package/.claude-plugin/marketplace.json +31 -0
- package/.claude-plugin/plugin.json +34 -0
- package/.claude-plugin/skill-manifest.json +79 -0
- package/.mcp.json +13 -0
- package/.plugin/plugin.json +14 -0
- package/LICENSE +21 -0
- package/README.md +849 -0
- package/bin/build-plugin.js +97 -0
- package/bin/cli.js +891 -0
- package/bin/commands/autoupdate.js +128 -0
- package/bin/commands/build-desktop.js +368 -0
- package/bin/commands/create-from-template.js +165 -0
- package/bin/commands/diff.js +435 -0
- package/bin/commands/install.js +542 -0
- package/bin/commands/lint.js +441 -0
- package/bin/commands/mcp-setup.js +255 -0
- package/bin/commands/session-update.js +204 -0
- package/bin/commands/smoke-test.js +20 -0
- package/bin/commands/uninstall.js +611 -0
- package/bin/commands/update-check.js +427 -0
- package/bin/commands/validate-cases.js +264 -0
- package/bin/commands/validate-projects.js +426 -0
- package/bin/commands/watch.js +251 -0
- package/bin/lib/agents.js +62 -0
- package/bin/lib/base-template-smoke.js +299 -0
- package/bin/lib/claude-hooks.js +160 -0
- package/bin/lib/generators/claude-plugin.js +529 -0
- package/bin/lib/generators/index.js +116 -0
- package/bin/lib/generators/shared.js +257 -0
- package/bin/lib/mcp-config.js +835 -0
- package/bin/lib/microsoft-mcp.js +206 -0
- package/bin/lib/powerbi-mcp-session.js +140 -0
- package/bin/lib/skills.js +164 -0
- package/bin/lib/template-scaffold.js +366 -0
- package/bin/mcp/powerbi-modeling-launcher.js +42 -0
- package/bin/postinstall.js +50 -0
- package/bin/utils/mcp-detect.js +346 -0
- package/bin/utils/tui.js +314 -0
- package/commands/bi-connect.md +520 -0
- package/commands/bi-dax.md +464 -0
- package/commands/bi-kickoff.md +550 -0
- package/commands/bi-modeling.md +485 -0
- package/commands/bi-performance.md +521 -0
- package/commands/bi-powerquery.md +229 -0
- package/commands/bi-refactor.md +249 -0
- package/commands/bi-scorecard.md +268 -0
- package/commands/bi-start.md +272 -0
- package/config.example.json +23 -0
- package/config.json +23 -0
- package/desktop-extension/manifest.json +30 -0
- package/desktop-extension/package.json +10 -0
- package/desktop-extension/server.js +137 -0
- package/package.json +94 -0
- package/skills/bi-connect/SKILL.md +522 -0
- package/skills/bi-connect/scripts/update-check.js +427 -0
- package/skills/bi-dax/SKILL.md +466 -0
- package/skills/bi-dax/scripts/update-check.js +427 -0
- package/skills/bi-kickoff/SKILL.md +552 -0
- package/skills/bi-kickoff/references/flow.html +78 -0
- package/skills/bi-kickoff/references/flow.md +62 -0
- package/skills/bi-kickoff/scripts/update-check.js +427 -0
- package/skills/bi-modeling/SKILL.md +487 -0
- package/skills/bi-modeling/scripts/update-check.js +427 -0
- package/skills/bi-performance/SKILL.md +523 -0
- package/skills/bi-performance/scripts/install-tabular-editor.ps1 +159 -0
- package/skills/bi-performance/scripts/run-bpa.ps1 +265 -0
- package/skills/bi-performance/scripts/update-check.js +427 -0
- package/skills/bi-powerquery/SKILL.md +231 -0
- package/skills/bi-powerquery/references/base-template-data-contract.md +323 -0
- package/skills/bi-powerquery/references/power-query-standards.md +74 -0
- package/skills/bi-powerquery/scripts/new-powerquery-staging.ps1 +371 -0
- package/skills/bi-powerquery/scripts/test-powerquery-contract.ps1 +225 -0
- package/skills/bi-powerquery/scripts/update-check.js +427 -0
- package/skills/bi-refactor/SKILL.md +251 -0
- package/skills/bi-refactor/references/flow.md +27 -0
- package/skills/bi-refactor/scripts/update-check.js +427 -0
- package/skills/bi-scorecard/SKILL.md +270 -0
- package/skills/bi-scorecard/examples/base-template-scorecard-overlay.json +82 -0
- package/skills/bi-scorecard/scripts/new-scorecard-blueprint-from-base-template.ps1 +124 -0
- package/skills/bi-scorecard/scripts/powerbi-goal-status-rules-api.ps1 +39 -0
- package/skills/bi-scorecard/scripts/powerbi-goal-values-api.ps1 +48 -0
- package/skills/bi-scorecard/scripts/powerbi-goals-api.ps1 +68 -0
- package/skills/bi-scorecard/scripts/powerbi-rest-common.ps1 +197 -0
- package/skills/bi-scorecard/scripts/powerbi-scorecards-api.ps1 +53 -0
- package/skills/bi-scorecard/scripts/update-check.js +427 -0
- package/skills/bi-start/SKILL.md +274 -0
- package/skills/bi-start/scripts/update-check.js +427 -0
- package/src/content/base.md +197 -0
- package/src/content/mcp-requirements.json +57 -0
- package/src/content/routing.md +201 -0
- package/src/content/skills/bi-connect.md +493 -0
- package/src/content/skills/bi-dax.md +437 -0
- package/src/content/skills/bi-kickoff/SKILL.md +523 -0
- package/src/content/skills/bi-kickoff/references/flow.html +78 -0
- package/src/content/skills/bi-kickoff/references/flow.md +62 -0
- package/src/content/skills/bi-modeling.md +458 -0
- package/src/content/skills/bi-performance/SKILL.md +494 -0
- package/src/content/skills/bi-performance/scripts/install-tabular-editor.ps1 +159 -0
- package/src/content/skills/bi-performance/scripts/run-bpa.ps1 +265 -0
- package/src/content/skills/bi-powerquery/SKILL.md +202 -0
- package/src/content/skills/bi-powerquery/references/base-template-data-contract.md +323 -0
- package/src/content/skills/bi-powerquery/references/power-query-standards.md +74 -0
- package/src/content/skills/bi-powerquery/scripts/new-powerquery-staging.ps1 +371 -0
- package/src/content/skills/bi-powerquery/scripts/test-powerquery-contract.ps1 +225 -0
- package/src/content/skills/bi-refactor/SKILL.md +222 -0
- package/src/content/skills/bi-refactor/references/flow.md +27 -0
- package/src/content/skills/bi-scorecard/SKILL.md +241 -0
- package/src/content/skills/bi-scorecard/examples/base-template-scorecard-blueprint.expected.json +105 -0
- package/src/content/skills/bi-scorecard/examples/base-template-scorecard-overlay.json +82 -0
- package/src/content/skills/bi-scorecard/scripts/new-scorecard-blueprint-from-base-template.ps1 +124 -0
- package/src/content/skills/bi-scorecard/scripts/powerbi-goal-status-rules-api.ps1 +39 -0
- package/src/content/skills/bi-scorecard/scripts/powerbi-goal-values-api.ps1 +48 -0
- package/src/content/skills/bi-scorecard/scripts/powerbi-goals-api.ps1 +68 -0
- package/src/content/skills/bi-scorecard/scripts/powerbi-rest-common.ps1 +197 -0
- package/src/content/skills/bi-scorecard/scripts/powerbi-scorecards-api.ps1 +53 -0
- package/src/content/skills/bi-start.md +266 -0
- package/templates/base-template/AGENTS.md +33 -0
- package/templates/base-template/base-template.Report/.platform +11 -0
- package/templates/base-template/base-template.Report/StaticResources/RegisteredResources/BISuperpowers.json +3888 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BaseThemes/CY18SU07.json +177 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BaseThemes/Fluent2-CY26SU03.json +4104 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/AccessibleCityPark.json +26 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/AccessibleDefault.json +26 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/AccessibleNeutral.json +26 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/AccessibleOrchid.json +26 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/AccessibleTidal.json +26 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Bloom.json +139 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/CityPark.json +40 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Classroom.json +40 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/ColorblindSafe.json +48 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/CopilotDefault.json +1861 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Divergent.json +127 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Electric.json +48 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Frontier.json +136 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/HighContrast.json +40 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Highrise.json +41 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Innovate.json +227 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/NewExecutive.json +41 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Solar.json +33 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Storm.json +25 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Sunset.json +48 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Temperature.json +33 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Tidal.json +100 -0
- package/templates/base-template/base-template.Report/StaticResources/SharedResources/BuiltInThemes/Twilight.json +40 -0
- package/templates/base-template/base-template.Report/definition/bookmarks/1d40d43c7ade66e8603c.bookmark.json +2297 -0
- package/templates/base-template/base-template.Report/definition/bookmarks/af068ff51c0ca3089ea7.bookmark.json +2300 -0
- package/templates/base-template/base-template.Report/definition/bookmarks/bookmarks.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/page.json +130 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/0352fd80d074693a65db/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/0352fd80d074693a65db/visual.json +669 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/1c5a14bf493697344b68/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/1c5a14bf493697344b68/visual.json +723 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/3486cf7624c5b109b4e5/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/3486cf7624c5b109b4e5/visual.json +333 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/4d8b989008edc0db28d1/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/4d8b989008edc0db28d1/visual.json +109 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/55e10ac7d76a1954f94f/mobile.json +31 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/55e10ac7d76a1954f94f/visual.json +378 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/57f52ecf4490f70e4da1/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/57f52ecf4490f70e4da1/visual.json +175 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/5f4d76bbc870118e9840/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/5f4d76bbc870118e9840/visual.json +468 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/73629e1abebb7a444b59/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/73629e1abebb7a444b59/visual.json +359 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/749cb1388c7e0a88161c/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/749cb1388c7e0a88161c/visual.json +690 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/90677f13cea5d1275990/visual.json +17 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/92cf92e3da10493adb78/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/92cf92e3da10493adb78/visual.json +468 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/9fe17b1971f68443fc15/mobile.json +10 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/9fe17b1971f68443fc15/visual.json +328 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/a30bd0950630ed94e8a3/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/a30bd0950630ed94e8a3/visual.json +578 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/a56e91d9400a835e4814/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/a56e91d9400a835e4814/visual.json +432 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/aded24cd205c0b528642/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/aded24cd205c0b528642/visual.json +801 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/af34b26f14a8a724c9a9/mobile.json +37 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/af34b26f14a8a724c9a9/visual.json +1318 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/b529688fe5a226643322/visual.json +209 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/c4c6f332d05e72e2eb06/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/c4c6f332d05e72e2eb06/visual.json +174 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/fa81f184e2cb0e8b087c/mobile.json +29 -0
- package/templates/base-template/base-template.Report/definition/pages/6a4808bb8bb9166f49ff/visuals/fa81f184e2cb0e8b087c/visual.json +241 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/page.json +130 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/07e9c4302e29029c5462/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/07e9c4302e29029c5462/visual.json +690 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/109ceede4bc015b0c006/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/109ceede4bc015b0c006/visual.json +468 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/118257e006d472277e10/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/118257e006d472277e10/visual.json +359 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/2caf02e0137c4a1280cc/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/2caf02e0137c4a1280cc/visual.json +669 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/311e76fe3c9edad68204/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/311e76fe3c9edad68204/visual.json +109 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/31c21f8cbeb3b208940a/visual.json +209 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/3ab72c25062437149b03/visual.json +17 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/5959867442abcb0ce2b3/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/5959867442abcb0ce2b3/visual.json +788 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/5b96e0f88d192b044a13/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/5b96e0f88d192b044a13/visual.json +592 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/64e749a63d0786000e22/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/64e749a63d0786000e22/visual.json +468 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/7ae1ca604edac6586ad0/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/7ae1ca604edac6586ad0/visual.json +1310 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/840300733885141a6603/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/840300733885141a6603/visual.json +175 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/a38448cdb203279273d2/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/a38448cdb203279273d2/visual.json +516 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/d1e86f213a3841d12e20/visual.json +328 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/d4a484c1bcc8ee3075e2/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/d4a484c1bcc8ee3075e2/visual.json +432 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/d87cb5cf06acca19bbb5/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/d87cb5cf06acca19bbb5/visual.json +241 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/e243da2677209ed69408/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/e243da2677209ed69408/visual.json +174 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/f3aaf24f5b22b67573b0/mobile.json +11 -0
- package/templates/base-template/base-template.Report/definition/pages/9a5b670b015cab882629/visuals/f3aaf24f5b22b67573b0/visual.json +333 -0
- package/templates/base-template/base-template.Report/definition/pages/pages.json +8 -0
- package/templates/base-template/base-template.Report/definition/report.json +89 -0
- package/templates/base-template/base-template.Report/definition/version.json +4 -0
- package/templates/base-template/base-template.Report/definition.pbir +9 -0
- package/templates/base-template/base-template.SemanticModel/.platform +11 -0
- package/templates/base-template/base-template.SemanticModel/definition/cultures/es-AR.tmdl +11185 -0
- package/templates/base-template/base-template.SemanticModel/definition/database.tmdl +3 -0
- package/templates/base-template/base-template.SemanticModel/definition/expressions.tmdl +234 -0
- package/templates/base-template/base-template.SemanticModel/definition/functions.tmdl +637 -0
- package/templates/base-template/base-template.SemanticModel/definition/model.tmdl +82 -0
- package/templates/base-template/base-template.SemanticModel/definition/relationships.tmdl +271 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Calendario.tmdl +200 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Campa/303/261as.tmdl +75 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Canales.tmdl +84 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Clientes.tmdl +143 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Devoluciones.tmdl +95 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Ejecuci/303/263n proyectos.tmdl" +130 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Entregas.tmdl +122 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Equipos m/303/251tricas.tmdl" +40 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Equipos.tmdl +73 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Horas.tmdl +122 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Interacciones clientes.tmdl +146 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Leads.tmdl +119 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Monedas.tmdl +44 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Movimientos financieros.tmdl +145 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/M/303/251tricas.tmdl +1294 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/N/303/263mina.tmdl +110 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Oportunidades.tmdl +135 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Presupuesto.tmdl +125 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Productos.tmdl +98 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Proyectos.tmdl +77 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Servicios.tmdl +75 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Tareas proyecto.tmdl +102 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Tipo de cambio.tmdl +67 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/Ventas.tmdl +180 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux An/303/241lisis dimensiones.tmdl" +38 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Comparaciones.tmdl +227 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Compatibilidad m/303/251trica-dimensi/303/263n.tmdl" +68 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Modelo configuraci/303/263n.tmdl" +44 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Modo fechas.tmdl +36 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux M/303/251trica-Equipo.tmdl" +102 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Overrides m/303/251trica-dimensi/303/263n.tmdl" +54 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Per/303/255odos.tmdl" +182 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Rango fechas modo.tmdl +36 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Rango fechas.tmdl +27 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/_Aux Vista de calendario.tmdl +30 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/_GC C/303/241lculo.tmdl" +70 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/_GC Eje X.tmdl +63 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/_GC M/303/251trica.tmdl" +374 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/_GC Tipo c/303/241lculo.tmdl" +223 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/_PC Dimensi/303/263n.tmdl" +98 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables/_PC Eje X.tmdl +68 -0
- package/templates/base-template/base-template.SemanticModel/definition/tables//303/223rdenes servicio.tmdl" +151 -0
- package/templates/base-template/base-template.SemanticModel/definition.pbism +5 -0
- package/templates/base-template/base-template.SemanticModel/diagramLayout.json +568 -0
- package/templates/base-template/base-template.pbip +14 -0
- package/templates/base-template/template.manifest.json +41 -0
- 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
|
+
};
|