@luquimbo/bi-superpowers 3.1.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/.claude-plugin/marketplace.json +5 -3
  2. package/.claude-plugin/plugin.json +28 -2
  3. package/.claude-plugin/skill-manifest.json +22 -6
  4. package/.plugin/plugin.json +1 -1
  5. package/AGENTS.md +52 -36
  6. package/CHANGELOG.md +295 -0
  7. package/README.md +75 -26
  8. package/bin/build-plugin.js +17 -10
  9. package/bin/cli.js +278 -322
  10. package/bin/commands/build-desktop.js +35 -16
  11. package/bin/commands/diff.js +31 -13
  12. package/bin/commands/install.js +93 -72
  13. package/bin/commands/lint.js +40 -26
  14. package/bin/commands/mcp-setup.js +3 -10
  15. package/bin/commands/update-check.js +389 -0
  16. package/bin/lib/agents.js +19 -0
  17. package/bin/lib/generators/claude-plugin.js +144 -6
  18. package/bin/lib/generators/shared.js +29 -33
  19. package/bin/lib/mcp-config.js +191 -16
  20. package/bin/lib/skills.js +115 -27
  21. package/bin/postinstall.js +4 -2
  22. package/bin/utils/mcp-detect.js +2 -2
  23. package/commands/bi-start.md +218 -0
  24. package/commands/pbi-connect.md +43 -65
  25. package/commands/project-kickoff.md +393 -673
  26. package/commands/report-design.md +403 -0
  27. package/desktop-extension/manifest.json +5 -12
  28. package/desktop-extension/server.js +34 -25
  29. package/package.json +6 -10
  30. package/skills/bi-start/SKILL.md +220 -0
  31. package/skills/bi-start/scripts/update-check.js +389 -0
  32. package/skills/pbi-connect/SKILL.md +45 -67
  33. package/skills/pbi-connect/scripts/update-check.js +389 -0
  34. package/skills/project-kickoff/SKILL.md +395 -675
  35. package/skills/project-kickoff/scripts/update-check.js +389 -0
  36. package/skills/report-design/SKILL.md +405 -0
  37. package/skills/report-design/references/cli-commands.md +184 -0
  38. package/skills/report-design/references/cli-setup.md +101 -0
  39. package/skills/report-design/references/close-write-open-pattern.md +80 -0
  40. package/skills/report-design/references/layouts/finance.md +65 -0
  41. package/skills/report-design/references/layouts/generic.md +46 -0
  42. package/skills/report-design/references/layouts/hr.md +48 -0
  43. package/skills/report-design/references/layouts/marketing.md +45 -0
  44. package/skills/report-design/references/layouts/operations.md +44 -0
  45. package/skills/report-design/references/layouts/sales.md +50 -0
  46. package/skills/report-design/references/native-visuals.md +341 -0
  47. package/skills/report-design/references/pbi-desktop-installation.md +87 -0
  48. package/skills/report-design/references/pbir-preview-activation.md +40 -0
  49. package/skills/report-design/references/slicer.md +89 -0
  50. package/skills/report-design/references/textbox.md +101 -0
  51. package/skills/report-design/references/themes/BISuperpowers.json +915 -0
  52. package/skills/report-design/references/troubleshooting.md +135 -0
  53. package/skills/report-design/references/visual-types.md +78 -0
  54. package/skills/report-design/scripts/apply-theme.js +243 -0
  55. package/skills/report-design/scripts/create-visual.js +878 -0
  56. package/skills/report-design/scripts/ensure-pbi-cli.sh +41 -0
  57. package/skills/report-design/scripts/update-check.js +389 -0
  58. package/skills/report-design/scripts/validate-pbir.js +322 -0
  59. package/src/content/base.md +12 -68
  60. package/src/content/mcp-requirements.json +0 -25
  61. package/src/content/routing.md +19 -74
  62. package/src/content/skills/bi-start.md +191 -0
  63. package/src/content/skills/pbi-connect.md +22 -65
  64. package/src/content/skills/project-kickoff.md +372 -673
  65. package/src/content/skills/report-design/SKILL.md +376 -0
  66. package/src/content/skills/report-design/references/cli-commands.md +184 -0
  67. package/src/content/skills/report-design/references/cli-setup.md +101 -0
  68. package/src/content/skills/report-design/references/close-write-open-pattern.md +80 -0
  69. package/src/content/skills/report-design/references/layouts/finance.md +65 -0
  70. package/src/content/skills/report-design/references/layouts/generic.md +46 -0
  71. package/src/content/skills/report-design/references/layouts/hr.md +48 -0
  72. package/src/content/skills/report-design/references/layouts/marketing.md +45 -0
  73. package/src/content/skills/report-design/references/layouts/operations.md +44 -0
  74. package/src/content/skills/report-design/references/layouts/sales.md +50 -0
  75. package/src/content/skills/report-design/references/native-visuals.md +341 -0
  76. package/src/content/skills/report-design/references/pbi-desktop-installation.md +87 -0
  77. package/src/content/skills/report-design/references/pbir-preview-activation.md +40 -0
  78. package/src/content/skills/report-design/references/slicer.md +89 -0
  79. package/src/content/skills/report-design/references/textbox.md +101 -0
  80. package/src/content/skills/report-design/references/themes/BISuperpowers.json +915 -0
  81. package/src/content/skills/report-design/references/troubleshooting.md +135 -0
  82. package/src/content/skills/report-design/references/visual-types.md +78 -0
  83. package/src/content/skills/report-design/scripts/apply-theme.js +243 -0
  84. package/src/content/skills/report-design/scripts/create-visual.js +878 -0
  85. package/src/content/skills/report-design/scripts/ensure-pbi-cli.sh +41 -0
  86. package/src/content/skills/report-design/scripts/validate-pbir.js +322 -0
  87. package/bin/commands/add.js +0 -533
  88. package/bin/commands/add.test.js +0 -77
  89. package/bin/commands/changelog.js +0 -443
  90. package/bin/commands/install.test.js +0 -289
  91. package/bin/commands/lint.test.js +0 -103
  92. package/bin/commands/pull.js +0 -287
  93. package/bin/commands/pull.test.js +0 -36
  94. package/bin/commands/push.js +0 -231
  95. package/bin/commands/push.test.js +0 -14
  96. package/bin/commands/search.js +0 -344
  97. package/bin/commands/search.test.js +0 -115
  98. package/bin/commands/setup.js +0 -545
  99. package/bin/commands/setup.test.js +0 -46
  100. package/bin/commands/sync-profile.js +0 -405
  101. package/bin/commands/sync-profile.test.js +0 -14
  102. package/bin/commands/sync-source.js +0 -418
  103. package/bin/commands/sync-source.test.js +0 -14
  104. package/bin/lib/generators/claude-plugin.test.js +0 -111
  105. package/bin/lib/mcp-config.test.js +0 -310
  106. package/bin/lib/microsoft-mcp.test.js +0 -115
  107. package/bin/utils/errors.js +0 -159
  108. package/bin/utils/git.js +0 -298
  109. package/bin/utils/logger.js +0 -142
  110. package/bin/utils/mcp-detect.test.js +0 -81
  111. package/bin/utils/pbix.js +0 -305
  112. package/bin/utils/pbix.test.js +0 -37
  113. package/bin/utils/profiles.js +0 -312
  114. package/bin/utils/projects.js +0 -169
  115. package/bin/utils/readline.js +0 -206
  116. package/bin/utils/readline.test.js +0 -47
  117. package/bin/utils/tui.test.js +0 -127
  118. package/docs/openrouter-free-models.md +0 -92
  119. package/library/examples/README.md +0 -151
  120. package/library/examples/finance-reporting/README.md +0 -351
  121. package/library/examples/finance-reporting/data-model.md +0 -267
  122. package/library/examples/finance-reporting/measures.dax +0 -557
  123. package/library/examples/hr-analytics/README.md +0 -371
  124. package/library/examples/hr-analytics/data-model.md +0 -315
  125. package/library/examples/hr-analytics/measures.dax +0 -460
  126. package/library/examples/marketing-analytics/README.md +0 -37
  127. package/library/examples/marketing-analytics/data-model.md +0 -62
  128. package/library/examples/marketing-analytics/measures.dax +0 -110
  129. package/library/examples/retail-analytics/README.md +0 -439
  130. package/library/examples/retail-analytics/data-model.md +0 -288
  131. package/library/examples/retail-analytics/measures.dax +0 -481
  132. package/library/examples/supply-chain/README.md +0 -37
  133. package/library/examples/supply-chain/data-model.md +0 -69
  134. package/library/examples/supply-chain/measures.dax +0 -77
  135. package/library/examples/udf-library/README.md +0 -228
  136. package/library/examples/udf-library/functions.dax +0 -571
  137. package/library/snippets/dax/README.md +0 -292
  138. package/library/snippets/dax/business-domains.md +0 -576
  139. package/library/snippets/dax/calculate-patterns.md +0 -276
  140. package/library/snippets/dax/calculation-groups.md +0 -489
  141. package/library/snippets/dax/error-handling.md +0 -495
  142. package/library/snippets/dax/iterators-and-aggregations.md +0 -474
  143. package/library/snippets/dax/kpis-and-metrics.md +0 -293
  144. package/library/snippets/dax/rankings-and-topn.md +0 -235
  145. package/library/snippets/dax/security-patterns.md +0 -413
  146. package/library/snippets/dax/text-and-formatting.md +0 -316
  147. package/library/snippets/dax/time-intelligence.md +0 -196
  148. package/library/snippets/dax/user-defined-functions.md +0 -477
  149. package/library/snippets/dax/virtual-tables.md +0 -546
  150. package/library/snippets/excel-formulas/README.md +0 -84
  151. package/library/snippets/excel-formulas/aggregations.md +0 -330
  152. package/library/snippets/excel-formulas/dates-and-times.md +0 -361
  153. package/library/snippets/excel-formulas/dynamic-arrays.md +0 -314
  154. package/library/snippets/excel-formulas/lookups.md +0 -169
  155. package/library/snippets/excel-formulas/text-functions.md +0 -363
  156. package/library/snippets/governance/naming-conventions.md +0 -97
  157. package/library/snippets/governance/review-checklists.md +0 -107
  158. package/library/snippets/power-query/README.md +0 -389
  159. package/library/snippets/power-query/api-integration.md +0 -707
  160. package/library/snippets/power-query/connections.md +0 -434
  161. package/library/snippets/power-query/data-cleaning.md +0 -298
  162. package/library/snippets/power-query/error-handling.md +0 -526
  163. package/library/snippets/power-query/parameters.md +0 -350
  164. package/library/snippets/power-query/performance.md +0 -506
  165. package/library/snippets/power-query/transformations.md +0 -330
  166. package/library/snippets/report-design/accessibility.md +0 -78
  167. package/library/snippets/report-design/chart-selection.md +0 -54
  168. package/library/snippets/report-design/layout-patterns.md +0 -87
  169. package/library/templates/data-models/README.md +0 -93
  170. package/library/templates/data-models/finance-model.md +0 -627
  171. package/library/templates/data-models/retail-star-schema.md +0 -473
  172. package/library/templates/excel/README.md +0 -83
  173. package/library/templates/excel/budget-tracker.md +0 -432
  174. package/library/templates/excel/data-entry-form.md +0 -533
  175. package/library/templates/power-bi/README.md +0 -72
  176. package/library/templates/power-bi/finance-report.md +0 -449
  177. package/library/templates/power-bi/kpi-scorecard.md +0 -461
  178. package/library/templates/power-bi/sales-dashboard.md +0 -281
  179. package/library/themes/excel/README.md +0 -436
  180. package/library/themes/power-bi/README.md +0 -271
  181. package/library/themes/power-bi/accessible.json +0 -307
  182. package/library/themes/power-bi/bi-superpowers-default.json +0 -858
  183. package/library/themes/power-bi/corporate-blue.json +0 -291
  184. package/library/themes/power-bi/dark-mode.json +0 -291
  185. package/library/themes/power-bi/minimal.json +0 -292
  186. package/library/themes/power-bi/print-friendly.json +0 -309
@@ -1,310 +0,0 @@
1
- /**
2
- * Tests for the multi-agent MCP config writer.
3
- */
4
-
5
- const { test, describe, beforeEach, afterEach } = require('node:test');
6
- const assert = require('node:assert');
7
- const fs = require('node:fs');
8
- const os = require('node:os');
9
- const path = require('node:path');
10
-
11
- const {
12
- MCP_WRITERS,
13
- MODELING_SERVER_NAME,
14
- LEARN_SERVER_NAME,
15
- MICROSOFT_LEARN_URL,
16
- tomlEscape,
17
- escapeRegex,
18
- assertNotSymlink,
19
- getLauncherAbsolutePath,
20
- } = require('./mcp-config');
21
-
22
- describe('mcp-config helpers', () => {
23
- test('getLauncherAbsolutePath returns absolute launcher path', () => {
24
- const pkgDir = '/tmp/fake-pkg';
25
- const launcher = getLauncherAbsolutePath(pkgDir);
26
- assert.strictEqual(launcher, path.join(pkgDir, 'bin', 'mcp', 'powerbi-modeling-launcher.js'));
27
- });
28
-
29
- test('tomlEscape escapes backslashes and quotes', () => {
30
- assert.strictEqual(tomlEscape('plain'), 'plain');
31
- assert.strictEqual(tomlEscape('C:\\path\\to\\file'), 'C:\\\\path\\\\to\\\\file');
32
- assert.strictEqual(tomlEscape('say "hello"'), 'say \\"hello\\"');
33
- });
34
-
35
- test('tomlEscape escapes TOML control characters', () => {
36
- assert.strictEqual(tomlEscape('line1\nline2'), 'line1\\nline2');
37
- assert.strictEqual(tomlEscape('tab\there'), 'tab\\there');
38
- assert.strictEqual(tomlEscape('cr\rhere'), 'cr\\rhere');
39
- // \x08 = backspace, \f = form feed
40
- assert.strictEqual(tomlEscape('\x08\f'), '\\b\\f');
41
- });
42
-
43
- test('escapeRegex escapes all regex metacharacters', () => {
44
- assert.strictEqual(escapeRegex('plain'), 'plain');
45
- assert.strictEqual(escapeRegex('a.b'), 'a\\.b');
46
- assert.strictEqual(escapeRegex('a[b]c'), 'a\\[b\\]c');
47
- assert.strictEqual(escapeRegex('a+b*c?'), 'a\\+b\\*c\\?');
48
- assert.strictEqual(escapeRegex('(hello)'), '\\(hello\\)');
49
- });
50
- });
51
-
52
- describe('assertNotSymlink', () => {
53
- let tempDir;
54
-
55
- beforeEach(() => {
56
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bi-mcp-symlink-'));
57
- });
58
-
59
- afterEach(() => {
60
- fs.rmSync(tempDir, { recursive: true, force: true });
61
- });
62
-
63
- test('allows non-existent paths', () => {
64
- assert.doesNotThrow(() => {
65
- assertNotSymlink(path.join(tempDir, 'nope.json'));
66
- });
67
- });
68
-
69
- test('allows regular files', () => {
70
- const file = path.join(tempDir, 'regular.json');
71
- fs.writeFileSync(file, '{}');
72
- assert.doesNotThrow(() => assertNotSymlink(file));
73
- });
74
-
75
- test('throws on symbolic links', () => {
76
- const target = path.join(tempDir, 'target.json');
77
- const link = path.join(tempDir, 'link.json');
78
- fs.writeFileSync(target, '{}');
79
- fs.symlinkSync(target, link);
80
-
81
- assert.throws(() => assertNotSymlink(link), /is a symbolic link/);
82
- });
83
- });
84
-
85
- describe('MCP_WRITERS registry', () => {
86
- test('covers all 5 supported agents', () => {
87
- const ids = Object.keys(MCP_WRITERS).sort();
88
- assert.deepStrictEqual(ids, ['claude-code', 'codex', 'gemini-cli', 'github-copilot', 'kilo']);
89
- });
90
-
91
- test('each writer is a function', () => {
92
- for (const [id, writer] of Object.entries(MCP_WRITERS)) {
93
- assert.strictEqual(typeof writer, 'function', `${id} writer should be a function`);
94
- }
95
- });
96
- });
97
-
98
- describe('MCP writers — JSON agents', () => {
99
- let tempHome;
100
- let origHome;
101
- const fakePkgDir = '/tmp/fake-bi-superpowers';
102
-
103
- beforeEach(() => {
104
- tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'bi-mcp-home-'));
105
- origHome = os.homedir;
106
- os.homedir = () => tempHome;
107
- });
108
-
109
- afterEach(() => {
110
- os.homedir = origHome;
111
- fs.rmSync(tempHome, { recursive: true, force: true });
112
- });
113
-
114
- test('claude-code writer produces mcpServers JSON at ~/.claude.json', () => {
115
- const configPath = MCP_WRITERS['claude-code'](fakePkgDir);
116
-
117
- assert.strictEqual(configPath, path.join(tempHome, '.claude.json'));
118
- assert.ok(fs.existsSync(configPath));
119
-
120
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
121
- assert.ok(config.mcpServers);
122
- assert.ok(config.mcpServers[MODELING_SERVER_NAME]);
123
- assert.strictEqual(config.mcpServers[MODELING_SERVER_NAME].command, 'node');
124
- assert.ok(
125
- config.mcpServers[MODELING_SERVER_NAME].args[0].includes('powerbi-modeling-launcher.js')
126
- );
127
- assert.deepStrictEqual(config.mcpServers[LEARN_SERVER_NAME], {
128
- type: 'http',
129
- url: MICROSOFT_LEARN_URL,
130
- });
131
- });
132
-
133
- test('github-copilot writer uses "servers" key (not mcpServers)', () => {
134
- const configPath = MCP_WRITERS['github-copilot'](fakePkgDir);
135
-
136
- assert.strictEqual(configPath, path.join(tempHome, '.copilot', 'mcp-config.json'));
137
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
138
- assert.ok(config.servers);
139
- assert.strictEqual(config.mcpServers, undefined);
140
- assert.strictEqual(config.servers[MODELING_SERVER_NAME].type, 'stdio');
141
- assert.strictEqual(config.servers[LEARN_SERVER_NAME].type, 'http');
142
- });
143
-
144
- test('gemini-cli writer uses httpUrl (not url)', () => {
145
- const configPath = MCP_WRITERS['gemini-cli'](fakePkgDir);
146
-
147
- assert.strictEqual(configPath, path.join(tempHome, '.gemini', 'settings.json'));
148
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
149
- assert.ok(config.mcpServers);
150
- assert.strictEqual(config.mcpServers[LEARN_SERVER_NAME].httpUrl, MICROSOFT_LEARN_URL);
151
- assert.strictEqual(config.mcpServers[LEARN_SERVER_NAME].url, undefined);
152
- });
153
-
154
- test('kilo writer uses url (standard)', () => {
155
- const configPath = MCP_WRITERS.kilo(fakePkgDir);
156
-
157
- assert.strictEqual(configPath, path.join(tempHome, '.kilo', 'mcp_settings.json'));
158
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
159
- assert.ok(config.mcpServers);
160
- assert.strictEqual(config.mcpServers[LEARN_SERVER_NAME].url, MICROSOFT_LEARN_URL);
161
- });
162
-
163
- test('claude-code writer preserves existing config', () => {
164
- const configPath = path.join(tempHome, '.claude.json');
165
- fs.writeFileSync(
166
- configPath,
167
- JSON.stringify({
168
- otherSetting: 'value',
169
- mcpServers: { 'existing-mcp': { command: 'echo' } },
170
- })
171
- );
172
-
173
- MCP_WRITERS['claude-code'](fakePkgDir);
174
-
175
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
176
- assert.strictEqual(config.otherSetting, 'value');
177
- assert.ok(config.mcpServers['existing-mcp']);
178
- assert.ok(config.mcpServers[MODELING_SERVER_NAME]);
179
- assert.ok(config.mcpServers[LEARN_SERVER_NAME]);
180
- });
181
-
182
- // Re-install de-duplication for JSON writers — each should replace its
183
- // own entries cleanly without duplicating them on repeated runs.
184
- for (const agentId of ['claude-code', 'github-copilot', 'gemini-cli', 'kilo']) {
185
- test(`${agentId} re-install replaces entries without duplicating`, () => {
186
- // First install
187
- MCP_WRITERS[agentId](fakePkgDir);
188
- // Second install (same args)
189
- const configPath = MCP_WRITERS[agentId](fakePkgDir);
190
-
191
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
192
- const serversKey = agentId === 'github-copilot' ? 'servers' : 'mcpServers';
193
- const servers = config[serversKey];
194
-
195
- // Our 2 servers should each appear exactly once (via object keys)
196
- assert.ok(servers[MODELING_SERVER_NAME], 'modeling server missing after re-install');
197
- assert.ok(servers[LEARN_SERVER_NAME], 'learn server missing after re-install');
198
-
199
- // And no stray duplicates of our keys
200
- const ourKeys = Object.keys(servers).filter(
201
- (k) => k === MODELING_SERVER_NAME || k === LEARN_SERVER_NAME
202
- );
203
- assert.strictEqual(ourKeys.length, 2, `expected 2 of our keys, found ${ourKeys.length}`);
204
- });
205
- }
206
-
207
- // Cross-agent sanity check: verifies each JSON agent writes the HTTP
208
- // URL under the correct key per its spec. Catches copy-paste errors
209
- // where someone might accidentally use `url` for Gemini (which expects
210
- // `httpUrl`) or vice versa.
211
- test('each JSON agent uses the correct HTTP field name', () => {
212
- const expectations = {
213
- 'claude-code': { key: 'url', wrapperKey: 'mcpServers' },
214
- 'github-copilot': { key: 'url', wrapperKey: 'servers' },
215
- 'gemini-cli': { key: 'httpUrl', wrapperKey: 'mcpServers' },
216
- kilo: { key: 'url', wrapperKey: 'mcpServers' },
217
- };
218
-
219
- for (const [agentId, expected] of Object.entries(expectations)) {
220
- // Reset home for each agent so configs don't leak between writes.
221
- fs.rmSync(tempHome, { recursive: true, force: true });
222
- fs.mkdirSync(tempHome, { recursive: true });
223
-
224
- const configPath = MCP_WRITERS[agentId](fakePkgDir);
225
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
226
- const learnEntry = config[expected.wrapperKey][LEARN_SERVER_NAME];
227
-
228
- assert.strictEqual(
229
- learnEntry[expected.key],
230
- MICROSOFT_LEARN_URL,
231
- `${agentId} should use "${expected.key}" for HTTP URL`
232
- );
233
-
234
- // And it should NOT use the wrong key (defensive check)
235
- const wrongKey = expected.key === 'url' ? 'httpUrl' : 'url';
236
- assert.strictEqual(
237
- learnEntry[wrongKey],
238
- undefined,
239
- `${agentId} should NOT have "${wrongKey}"`
240
- );
241
- }
242
- });
243
-
244
- test('writeJson refuses to overwrite symlinked target', () => {
245
- const realFile = path.join(tempHome, 'real-claude.json');
246
- const linkFile = path.join(tempHome, '.claude.json');
247
- fs.writeFileSync(realFile, '{"mcpServers":{}}');
248
- fs.symlinkSync(realFile, linkFile);
249
-
250
- assert.throws(() => MCP_WRITERS['claude-code'](fakePkgDir), /symbolic link/);
251
-
252
- // The real file content must not have been touched.
253
- const content = JSON.parse(fs.readFileSync(realFile, 'utf8'));
254
- assert.deepStrictEqual(content, { mcpServers: {} });
255
- });
256
- });
257
-
258
- describe('codex writer — TOML', () => {
259
- let tempHome;
260
- let origHome;
261
-
262
- beforeEach(() => {
263
- tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'bi-mcp-codex-'));
264
- origHome = os.homedir;
265
- os.homedir = () => tempHome;
266
- });
267
-
268
- afterEach(() => {
269
- os.homedir = origHome;
270
- fs.rmSync(tempHome, { recursive: true, force: true });
271
- });
272
-
273
- test('writes TOML with [mcp_servers.*] sections', () => {
274
- const configPath = MCP_WRITERS.codex('/tmp/fake-pkg');
275
-
276
- assert.strictEqual(configPath, path.join(tempHome, '.codex', 'config.toml'));
277
- const content = fs.readFileSync(configPath, 'utf8');
278
-
279
- assert.ok(content.includes('[mcp_servers.powerbi-modeling]'));
280
- assert.ok(content.includes('command = "node"'));
281
- assert.ok(content.includes('[mcp_servers.microsoft-learn]'));
282
- assert.ok(content.includes(`url = "${MICROSOFT_LEARN_URL}"`));
283
- });
284
-
285
- test('preserves existing non-MCP TOML content', () => {
286
- const configPath = path.join(tempHome, '.codex', 'config.toml');
287
- fs.mkdirSync(path.dirname(configPath), { recursive: true });
288
- fs.writeFileSync(configPath, '[profile]\nname = "default"\n');
289
-
290
- MCP_WRITERS.codex('/tmp/fake-pkg');
291
-
292
- const content = fs.readFileSync(configPath, 'utf8');
293
- assert.ok(content.includes('[profile]'));
294
- assert.ok(content.includes('name = "default"'));
295
- assert.ok(content.includes('[mcp_servers.powerbi-modeling]'));
296
- });
297
-
298
- test('re-running install replaces MCP sections, does not duplicate', () => {
299
- MCP_WRITERS.codex('/tmp/fake-pkg');
300
- MCP_WRITERS.codex('/tmp/fake-pkg');
301
-
302
- const content = fs.readFileSync(path.join(tempHome, '.codex', 'config.toml'), 'utf8');
303
-
304
- // Each section name should appear exactly once
305
- const modelingCount = (content.match(/\[mcp_servers\.powerbi-modeling\]/g) || []).length;
306
- const learnCount = (content.match(/\[mcp_servers\.microsoft-learn\]/g) || []).length;
307
- assert.strictEqual(modelingCount, 1);
308
- assert.strictEqual(learnCount, 1);
309
- });
310
- });
@@ -1,115 +0,0 @@
1
- /**
2
- * Tests for official Microsoft MCP config helpers.
3
- */
4
-
5
- const { test, describe } = require('node:test');
6
- const assert = require('node:assert');
7
- const path = require('node:path');
8
-
9
- const {
10
- ABSOLUTE_LAUNCHER_MODE,
11
- PLUGIN_ROOT_LAUNCHER_MODE,
12
- MICROSOFT_LEARN_URL,
13
- createPluginMcpConfig,
14
- createMcpConfigForFormat,
15
- mergeMcpConfig,
16
- resolveModelingLauncherPath,
17
- } = require('./microsoft-mcp');
18
-
19
- describe('resolveModelingLauncherPath', () => {
20
- test('uses plugin root placeholder for plugin builds', () => {
21
- const launcherPath = resolveModelingLauncherPath({
22
- launcherMode: PLUGIN_ROOT_LAUNCHER_MODE,
23
- });
24
-
25
- assert.strictEqual(launcherPath, '${CLAUDE_PLUGIN_ROOT}/bin/mcp/powerbi-modeling-launcher.js');
26
- });
27
-
28
- test('uses absolute package path for generated project configs', () => {
29
- const launcherPath = resolveModelingLauncherPath({
30
- packageDir: '/tmp/bi-superpowers',
31
- launcherMode: ABSOLUTE_LAUNCHER_MODE,
32
- });
33
-
34
- assert.strictEqual(
35
- launcherPath,
36
- path.join('/tmp/bi-superpowers', 'bin', 'mcp', 'powerbi-modeling-launcher.js')
37
- );
38
- });
39
- });
40
-
41
- describe('createPluginMcpConfig', () => {
42
- test('returns the 2 official Microsoft MCP defaults', () => {
43
- const config = createPluginMcpConfig({
44
- packageDir: '/tmp/bi-superpowers',
45
- launcherMode: ABSOLUTE_LAUNCHER_MODE,
46
- });
47
-
48
- // Should have exactly 2 servers
49
- assert.strictEqual(Object.keys(config).length, 2);
50
-
51
- // powerbi-modeling-mcp: local stdio launcher
52
- assert.strictEqual(config['powerbi-modeling-mcp'].type, 'stdio');
53
- assert.strictEqual(config['powerbi-modeling-mcp'].command, 'node');
54
- assert.ok(config['powerbi-modeling-mcp'].args[0].endsWith('powerbi-modeling-launcher.js'));
55
-
56
- // microsoft-learn: HTTP MCP
57
- assert.deepStrictEqual(config['microsoft-learn'], {
58
- type: 'http',
59
- url: MICROSOFT_LEARN_URL,
60
- });
61
- });
62
-
63
- test('does not include the old removed servers', () => {
64
- const config = createPluginMcpConfig({
65
- packageDir: '/tmp/bi-superpowers',
66
- launcherMode: ABSOLUTE_LAUNCHER_MODE,
67
- });
68
-
69
- assert.strictEqual(config['powerbi-remote'], undefined);
70
- assert.strictEqual(config['fabric-mcp-server'], undefined);
71
- });
72
- });
73
-
74
- describe('createMcpConfigForFormat', () => {
75
- test('returns flat plugin config for plugin/cursor outputs', () => {
76
- const pluginConfig = createMcpConfigForFormat('plugin');
77
- const cursorConfig = createMcpConfigForFormat('cursor');
78
-
79
- assert.ok(pluginConfig['powerbi-modeling-mcp']);
80
- assert.ok(cursorConfig['microsoft-learn']);
81
- assert.strictEqual(pluginConfig['microsoft-learn'].type, 'http');
82
- });
83
-
84
- test('returns nested mcpServers config for claude-like outputs', () => {
85
- const config = createMcpConfigForFormat('claude');
86
-
87
- assert.ok(config.mcpServers);
88
- assert.ok(config.mcpServers['powerbi-modeling-mcp']);
89
- assert.ok(config.mcpServers['microsoft-learn']);
90
- });
91
- });
92
-
93
- describe('mergeMcpConfig', () => {
94
- test('merges plugin configs without dropping existing entries', () => {
95
- const merged = mergeMcpConfig(
96
- { existing: { type: 'http', url: 'https://example.com' } },
97
- { 'microsoft-learn': { type: 'http', url: MICROSOFT_LEARN_URL } },
98
- 'plugin'
99
- );
100
-
101
- assert.ok(merged.existing);
102
- assert.ok(merged['microsoft-learn']);
103
- });
104
-
105
- test('merges claude configs inside mcpServers', () => {
106
- const merged = mergeMcpConfig(
107
- { mcpServers: { existing: { type: 'http', url: 'https://example.com' } } },
108
- { mcpServers: { 'microsoft-learn': { type: 'http', url: MICROSOFT_LEARN_URL } } },
109
- 'claude'
110
- );
111
-
112
- assert.ok(merged.mcpServers.existing);
113
- assert.ok(merged.mcpServers['microsoft-learn']);
114
- });
115
- });
@@ -1,159 +0,0 @@
1
- /**
2
- * Error Handling Utilities
3
- * ========================
4
- *
5
- * Standardized error handling for the CLI.
6
- * Provides consistent error messages and exit codes.
7
- *
8
- * @module utils/errors
9
- */
10
-
11
- /**
12
- * Custom CLI Error class
13
- * Extends Error with exit code and optional suggestion
14
- */
15
- class CLIError extends Error {
16
- /**
17
- * Create a CLI error
18
- * @param {string} message - Error message
19
- * @param {number} code - Exit code (default: 1)
20
- * @param {string|null} suggestion - Optional suggestion for fixing the error
21
- */
22
- constructor(message, code = 1, suggestion = null) {
23
- super(message);
24
- this.name = 'CLIError';
25
- this.code = code;
26
- this.suggestion = suggestion;
27
- }
28
- }
29
-
30
- /**
31
- * Error codes for common scenarios
32
- */
33
- const ErrorCodes = {
34
- GENERAL: 1,
35
- INVALID_INPUT: 2,
36
- FILE_NOT_FOUND: 3,
37
- PERMISSION_DENIED: 4,
38
- NETWORK_ERROR: 5,
39
- CONFIG_ERROR: 6,
40
- GIT_ERROR: 7,
41
- VALIDATION_ERROR: 8,
42
- };
43
-
44
- /**
45
- * Handle an error with consistent formatting
46
- * @param {Error} error - The error to handle
47
- * @param {Object} options - Options for handling
48
- * @param {boolean} options.exit - Whether to exit the process (default: true)
49
- * @param {boolean} options.verbose - Show stack trace (default: false)
50
- */
51
- function handleError(error, options = {}) {
52
- const { exit = true, verbose = process.env.DEBUG === 'true' } = options;
53
-
54
- if (error instanceof CLIError) {
55
- console.error(`Error: ${error.message}`);
56
- if (error.suggestion) {
57
- console.error(`\nSuggestion: ${error.suggestion}`);
58
- }
59
- if (exit) {
60
- process.exit(error.code);
61
- }
62
- } else if (error instanceof Error) {
63
- console.error(`Error: ${error.message}`);
64
- if (verbose && error.stack) {
65
- console.error(`\nStack trace:\n${error.stack}`);
66
- }
67
- if (exit) {
68
- process.exit(ErrorCodes.GENERAL);
69
- }
70
- } else {
71
- console.error(`Error: ${String(error)}`);
72
- if (exit) {
73
- process.exit(ErrorCodes.GENERAL);
74
- }
75
- }
76
- }
77
-
78
- /**
79
- * Wrap an async function with error handling
80
- * @param {Function} fn - Async function to wrap
81
- * @param {Object} options - Error handling options
82
- * @returns {Function} Wrapped function
83
- */
84
- function withErrorHandling(fn, options = {}) {
85
- return async (...args) => {
86
- try {
87
- return await fn(...args);
88
- } catch (error) {
89
- handleError(error, options);
90
- }
91
- };
92
- }
93
-
94
- /**
95
- * Log a warning without exiting
96
- * @param {string} message - Warning message
97
- * @param {Object} context - Additional context (optional)
98
- */
99
- function logWarning(message, context = null) {
100
- console.warn(`Warning: ${message}`);
101
- if (context && process.env.DEBUG === 'true') {
102
- console.warn('Context:', context);
103
- }
104
- }
105
-
106
- /**
107
- * Create a CLIError for common scenarios
108
- */
109
- const createError = {
110
- fileNotFound: (path) =>
111
- new CLIError(
112
- `File not found: ${path}`,
113
- ErrorCodes.FILE_NOT_FOUND,
114
- 'Check that the file path is correct.'
115
- ),
116
-
117
- invalidInput: (message, suggestion = null) =>
118
- new CLIError(message, ErrorCodes.INVALID_INPUT, suggestion),
119
-
120
- configError: (message) =>
121
- new CLIError(
122
- message,
123
- ErrorCodes.CONFIG_ERROR,
124
- 'Run "bi-superpowers setup" to configure the repository.'
125
- ),
126
-
127
- gitError: (message) =>
128
- new CLIError(
129
- message,
130
- ErrorCodes.GIT_ERROR,
131
- 'Make sure Git is installed and you are in a Git repository.'
132
- ),
133
-
134
- networkError: (message) =>
135
- new CLIError(
136
- message,
137
- ErrorCodes.NETWORK_ERROR,
138
- 'Check your internet connection and try again.'
139
- ),
140
-
141
- permissionDenied: (path) =>
142
- new CLIError(
143
- `Permission denied: ${path}`,
144
- ErrorCodes.PERMISSION_DENIED,
145
- 'Check file permissions.'
146
- ),
147
-
148
- validationError: (message, suggestion = null) =>
149
- new CLIError(message, ErrorCodes.VALIDATION_ERROR, suggestion),
150
- };
151
-
152
- module.exports = {
153
- CLIError,
154
- ErrorCodes,
155
- handleError,
156
- withErrorHandling,
157
- logWarning,
158
- createError,
159
- };