@luquimbo/bi-superpowers 3.1.1 → 3.2.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 +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.claude-plugin/skill-manifest.json +1 -1
- package/.plugin/plugin.json +1 -1
- package/bin/build-plugin.js +6 -6
- package/bin/cli.js +169 -310
- package/bin/commands/install.js +87 -70
- package/bin/lib/agents.js +19 -0
- package/bin/lib/mcp-config.js +23 -4
- package/desktop-extension/manifest.json +4 -11
- package/desktop-extension/server.js +34 -25
- package/package.json +3 -9
- package/skills/pbi-connect/SKILL.md +1 -1
- package/skills/project-kickoff/SKILL.md +1 -1
- package/bin/commands/add.js +0 -533
- package/bin/commands/add.test.js +0 -77
- package/bin/commands/changelog.js +0 -443
- package/bin/commands/pull.js +0 -287
- package/bin/commands/pull.test.js +0 -36
- package/bin/commands/push.js +0 -231
- package/bin/commands/push.test.js +0 -14
- package/bin/commands/search.js +0 -344
- package/bin/commands/search.test.js +0 -115
- package/bin/commands/setup.js +0 -545
- package/bin/commands/setup.test.js +0 -46
- package/bin/commands/sync-profile.js +0 -405
- package/bin/commands/sync-profile.test.js +0 -14
- package/bin/commands/sync-source.js +0 -418
- package/bin/commands/sync-source.test.js +0 -14
- package/bin/utils/errors.js +0 -159
- package/bin/utils/git.js +0 -298
- package/bin/utils/logger.js +0 -142
- package/bin/utils/pbix.js +0 -305
- package/bin/utils/pbix.test.js +0 -37
- package/bin/utils/profiles.js +0 -312
- package/bin/utils/projects.js +0 -169
- package/bin/utils/readline.js +0 -206
- package/bin/utils/readline.test.js +0 -47
- package/docs/openrouter-free-models.md +0 -92
- package/library/examples/README.md +0 -151
- package/library/examples/finance-reporting/README.md +0 -351
- package/library/examples/finance-reporting/data-model.md +0 -267
- package/library/examples/finance-reporting/measures.dax +0 -557
- package/library/examples/hr-analytics/README.md +0 -371
- package/library/examples/hr-analytics/data-model.md +0 -315
- package/library/examples/hr-analytics/measures.dax +0 -460
- package/library/examples/marketing-analytics/README.md +0 -37
- package/library/examples/marketing-analytics/data-model.md +0 -62
- package/library/examples/marketing-analytics/measures.dax +0 -110
- package/library/examples/retail-analytics/README.md +0 -439
- package/library/examples/retail-analytics/data-model.md +0 -288
- package/library/examples/retail-analytics/measures.dax +0 -481
- package/library/examples/supply-chain/README.md +0 -37
- package/library/examples/supply-chain/data-model.md +0 -69
- package/library/examples/supply-chain/measures.dax +0 -77
- package/library/examples/udf-library/README.md +0 -228
- package/library/examples/udf-library/functions.dax +0 -571
- package/library/snippets/dax/README.md +0 -292
- package/library/snippets/dax/business-domains.md +0 -576
- package/library/snippets/dax/calculate-patterns.md +0 -276
- package/library/snippets/dax/calculation-groups.md +0 -489
- package/library/snippets/dax/error-handling.md +0 -495
- package/library/snippets/dax/iterators-and-aggregations.md +0 -474
- package/library/snippets/dax/kpis-and-metrics.md +0 -293
- package/library/snippets/dax/rankings-and-topn.md +0 -235
- package/library/snippets/dax/security-patterns.md +0 -413
- package/library/snippets/dax/text-and-formatting.md +0 -316
- package/library/snippets/dax/time-intelligence.md +0 -196
- package/library/snippets/dax/user-defined-functions.md +0 -477
- package/library/snippets/dax/virtual-tables.md +0 -546
- package/library/snippets/excel-formulas/README.md +0 -84
- package/library/snippets/excel-formulas/aggregations.md +0 -330
- package/library/snippets/excel-formulas/dates-and-times.md +0 -361
- package/library/snippets/excel-formulas/dynamic-arrays.md +0 -314
- package/library/snippets/excel-formulas/lookups.md +0 -169
- package/library/snippets/excel-formulas/text-functions.md +0 -363
- package/library/snippets/governance/naming-conventions.md +0 -97
- package/library/snippets/governance/review-checklists.md +0 -107
- package/library/snippets/power-query/README.md +0 -389
- package/library/snippets/power-query/api-integration.md +0 -707
- package/library/snippets/power-query/connections.md +0 -434
- package/library/snippets/power-query/data-cleaning.md +0 -298
- package/library/snippets/power-query/error-handling.md +0 -526
- package/library/snippets/power-query/parameters.md +0 -350
- package/library/snippets/power-query/performance.md +0 -506
- package/library/snippets/power-query/transformations.md +0 -330
- package/library/snippets/report-design/accessibility.md +0 -78
- package/library/snippets/report-design/chart-selection.md +0 -54
- package/library/snippets/report-design/layout-patterns.md +0 -87
- package/library/templates/data-models/README.md +0 -93
- package/library/templates/data-models/finance-model.md +0 -627
- package/library/templates/data-models/retail-star-schema.md +0 -473
- package/library/templates/excel/README.md +0 -83
- package/library/templates/excel/budget-tracker.md +0 -432
- package/library/templates/excel/data-entry-form.md +0 -533
- package/library/templates/power-bi/README.md +0 -72
- package/library/templates/power-bi/finance-report.md +0 -449
- package/library/templates/power-bi/kpi-scorecard.md +0 -461
- package/library/templates/power-bi/sales-dashboard.md +0 -281
- package/library/themes/excel/README.md +0 -436
- package/library/themes/power-bi/README.md +0 -271
- package/library/themes/power-bi/accessible.json +0 -307
- package/library/themes/power-bi/bi-superpowers-default.json +0 -858
- package/library/themes/power-bi/corporate-blue.json +0 -291
- package/library/themes/power-bi/dark-mode.json +0 -291
- package/library/themes/power-bi/minimal.json +0 -292
- package/library/themes/power-bi/print-friendly.json +0 -309
package/bin/commands/install.js
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Install Command
|
|
3
|
-
*
|
|
2
|
+
* Install Command — Multi-agent skill + MCP installer
|
|
3
|
+
* =====================================================
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Installs the bi-superpowers skills and Microsoft MCP servers into
|
|
6
|
+
* every supported AI coding agent:
|
|
7
|
+
* - Claude Code
|
|
8
|
+
* - GitHub Copilot
|
|
9
|
+
* - Codex (OpenAI)
|
|
10
|
+
* - Gemini CLI
|
|
11
|
+
* - Kilo Code
|
|
8
12
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
13
|
+
* Everything is installed at the user level (~/) so it applies across
|
|
14
|
+
* all projects without polluting any specific repo. Skills land in
|
|
15
|
+
* ~/.agents/skills/ (universal path) and each agent's own skill dir is
|
|
16
|
+
* symlinked to that universal copy. MCPs are written to each agent's
|
|
17
|
+
* expected config file in the format that agent requires (JSON for
|
|
18
|
+
* most, TOML for Codex) — see `lib/mcp-config.js` for details.
|
|
12
19
|
*
|
|
13
|
-
*
|
|
20
|
+
* Fully open source (MIT). The user-facing messages are in Spanish to
|
|
21
|
+
* match the primary Spanish-speaking audience of the project; code
|
|
22
|
+
* comments and JSDoc stay in English so contributors from any language
|
|
23
|
+
* can work on the source.
|
|
14
24
|
*
|
|
15
|
-
*
|
|
25
|
+
* Usage:
|
|
16
26
|
* npx @luquimbo/bi-superpowers install
|
|
17
27
|
* super install
|
|
18
28
|
* super install --agent claude-code --agent codex
|
|
@@ -29,9 +39,10 @@ const { AGENTS, UNIVERSAL_DIR } = require('../lib/agents');
|
|
|
29
39
|
const { writeMcpConfigForAgent } = require('../lib/mcp-config');
|
|
30
40
|
|
|
31
41
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* @
|
|
42
|
+
* Detect which agents are installed by checking their config directories.
|
|
43
|
+
* Used in the interactive installer to pre-select the detected agents.
|
|
44
|
+
* @param {string} baseDir - Base directory to scan (typically the user's home)
|
|
45
|
+
* @returns {string[]} IDs of detected agents
|
|
35
46
|
*/
|
|
36
47
|
function detectAgents(baseDir) {
|
|
37
48
|
const detected = [];
|
|
@@ -46,7 +57,7 @@ function detectAgents(baseDir) {
|
|
|
46
57
|
}
|
|
47
58
|
|
|
48
59
|
/**
|
|
49
|
-
*
|
|
60
|
+
* Create a readline interface for interactive prompts.
|
|
50
61
|
*/
|
|
51
62
|
function createReadline() {
|
|
52
63
|
return readline.createInterface({
|
|
@@ -56,7 +67,7 @@ function createReadline() {
|
|
|
56
67
|
}
|
|
57
68
|
|
|
58
69
|
/**
|
|
59
|
-
*
|
|
70
|
+
* Promise-wrapped readline question.
|
|
60
71
|
*/
|
|
61
72
|
function prompt(rl, question) {
|
|
62
73
|
return new Promise((resolve) => {
|
|
@@ -65,11 +76,11 @@ function prompt(rl, question) {
|
|
|
65
76
|
}
|
|
66
77
|
|
|
67
78
|
/**
|
|
68
|
-
*
|
|
79
|
+
* Render a numbered multi-select list and wait for the user's choice.
|
|
69
80
|
* @param {readline.Interface} rl
|
|
70
81
|
* @param {Array<{id: string, name: string}>} items
|
|
71
|
-
* @param {string[]} preselected - IDs
|
|
72
|
-
* @returns {Promise<string[]>} IDs
|
|
82
|
+
* @param {string[]} preselected - IDs that should default to selected
|
|
83
|
+
* @returns {Promise<string[]>} Selected IDs
|
|
73
84
|
*/
|
|
74
85
|
async function selectMultiple(rl, items, preselected = []) {
|
|
75
86
|
items.forEach((item, i) => {
|
|
@@ -77,8 +88,8 @@ async function selectMultiple(rl, items, preselected = []) {
|
|
|
77
88
|
console.log(` ${i + 1}) ${marker} ${item.name}`);
|
|
78
89
|
});
|
|
79
90
|
console.log();
|
|
80
|
-
console.log('
|
|
81
|
-
console.log('
|
|
91
|
+
console.log(' Ingresá números separados por comas (ej: 1,2,3)');
|
|
92
|
+
console.log(' Presioná Enter para los detectados, o "a" para todos');
|
|
82
93
|
|
|
83
94
|
const answer = await prompt(rl, '\n > ');
|
|
84
95
|
|
|
@@ -99,10 +110,10 @@ async function selectMultiple(rl, items, preselected = []) {
|
|
|
99
110
|
}
|
|
100
111
|
|
|
101
112
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* @param {string} srcDir -
|
|
105
|
-
* @param {string} destDir -
|
|
113
|
+
* Recursively copy a skill directory (SKILL.md + references/ + scripts/).
|
|
114
|
+
* Filesystem errors propagate to the caller for centralized handling.
|
|
115
|
+
* @param {string} srcDir - Source skill directory
|
|
116
|
+
* @param {string} destDir - Destination directory
|
|
106
117
|
*/
|
|
107
118
|
function copySkillDir(srcDir, destDir) {
|
|
108
119
|
if (!fs.existsSync(destDir)) {
|
|
@@ -123,11 +134,12 @@ function copySkillDir(srcDir, destDir) {
|
|
|
123
134
|
}
|
|
124
135
|
|
|
125
136
|
/**
|
|
126
|
-
*
|
|
137
|
+
* Format a filesystem error with a user-friendly hint based on the code.
|
|
138
|
+
* Keeps the underlying error message so debugging is still possible.
|
|
127
139
|
*/
|
|
128
140
|
function formatFsError(err, context) {
|
|
129
141
|
const codeHints = {
|
|
130
|
-
EACCES: 'Permiso denegado.
|
|
142
|
+
EACCES: 'Permiso denegado. Revisá los permisos del directorio.',
|
|
131
143
|
EPERM: 'Operación no permitida. En Windows, probá ejecutar como Administrador.',
|
|
132
144
|
ENOSPC: 'No hay espacio en disco.',
|
|
133
145
|
ENOENT: 'Archivo o directorio no existe.',
|
|
@@ -138,8 +150,9 @@ function formatFsError(err, context) {
|
|
|
138
150
|
}
|
|
139
151
|
|
|
140
152
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
153
|
+
* Parse CLI arguments into an options object.
|
|
154
|
+
* Validates that each --agent / -a flag is followed by a real value
|
|
155
|
+
* so we don't silently accept dangling flags.
|
|
143
156
|
*/
|
|
144
157
|
function parseArgs(args) {
|
|
145
158
|
const opts = {
|
|
@@ -152,7 +165,7 @@ function parseArgs(args) {
|
|
|
152
165
|
if (args[i] === '--agent' || args[i] === '-a') {
|
|
153
166
|
const next = args[i + 1];
|
|
154
167
|
if (next === undefined || next.startsWith('-')) {
|
|
155
|
-
//
|
|
168
|
+
// Missing value — warn and skip this flag instead of crashing.
|
|
156
169
|
console.warn(`⚠ Flag ${args[i]} sin valor. Uso: ${args[i]} <agente-id>. Ignorando.`);
|
|
157
170
|
continue;
|
|
158
171
|
}
|
|
@@ -165,8 +178,8 @@ function parseArgs(args) {
|
|
|
165
178
|
}
|
|
166
179
|
|
|
167
180
|
/**
|
|
168
|
-
*
|
|
169
|
-
* @returns {Promise<string[]>} IDs
|
|
181
|
+
* Resolve which agents to install based on flags or interactive prompt.
|
|
182
|
+
* @returns {Promise<string[]>} IDs of the selected agents
|
|
170
183
|
*/
|
|
171
184
|
async function resolveSelectedAgents(opts, baseDir, chalk) {
|
|
172
185
|
if (opts.isAll) {
|
|
@@ -187,7 +200,7 @@ async function resolveSelectedAgents(opts, baseDir, chalk) {
|
|
|
187
200
|
return Object.keys(AGENTS);
|
|
188
201
|
}
|
|
189
202
|
|
|
190
|
-
//
|
|
203
|
+
// Interactive mode — detect installed agents and prompt the user.
|
|
191
204
|
const detected = detectAgents(baseDir);
|
|
192
205
|
console.log(chalk.cyan(' Seleccioná los agentes donde querés instalar:\n'));
|
|
193
206
|
|
|
@@ -205,11 +218,13 @@ async function resolveSelectedAgents(opts, baseDir, chalk) {
|
|
|
205
218
|
}
|
|
206
219
|
|
|
207
220
|
/**
|
|
208
|
-
*
|
|
221
|
+
* Copy skills into the universal path first, then either symlink or copy
|
|
222
|
+
* each selected agent's skill directory to point at the universal copy.
|
|
209
223
|
* @returns {{agentResults: Array, copyFallbacks: number}}
|
|
210
224
|
*/
|
|
211
225
|
function performInstall(skillsSourceDir, skillDirs, selectedAgents, baseDir) {
|
|
212
|
-
//
|
|
226
|
+
// Always install to the universal path first. This is the source of
|
|
227
|
+
// truth that all other agent dirs symlink to.
|
|
213
228
|
const universalTarget = path.join(baseDir, UNIVERSAL_DIR);
|
|
214
229
|
for (const skill of skillDirs) {
|
|
215
230
|
const src = path.join(skillsSourceDir, skill);
|
|
@@ -217,17 +232,17 @@ function performInstall(skillsSourceDir, skillDirs, selectedAgents, baseDir) {
|
|
|
217
232
|
copySkillDir(src, dest);
|
|
218
233
|
}
|
|
219
234
|
|
|
220
|
-
// Symlinks o copias para los directorios específicos de agentes
|
|
221
235
|
const agentResults = [];
|
|
222
236
|
let copyFallbacks = 0;
|
|
223
237
|
|
|
224
238
|
for (const agentId of selectedAgents) {
|
|
225
239
|
const agent = AGENTS[agentId];
|
|
226
|
-
if (agent.dir === UNIVERSAL_DIR) continue; //
|
|
240
|
+
if (agent.dir === UNIVERSAL_DIR) continue; // Already handled above.
|
|
227
241
|
|
|
228
242
|
const agentTarget = path.join(baseDir, agent.dir);
|
|
229
243
|
|
|
230
|
-
//
|
|
244
|
+
// If a real directory already exists (not a symlink), copy into it
|
|
245
|
+
// directly so we don't destroy user data.
|
|
231
246
|
if (fs.existsSync(agentTarget) && !fs.lstatSync(agentTarget).isSymbolicLink()) {
|
|
232
247
|
for (const skill of skillDirs) {
|
|
233
248
|
copySkillDir(path.join(skillsSourceDir, skill), path.join(agentTarget, skill));
|
|
@@ -241,22 +256,23 @@ function performInstall(skillsSourceDir, skillDirs, selectedAgents, baseDir) {
|
|
|
241
256
|
fs.mkdirSync(parentDir, { recursive: true });
|
|
242
257
|
}
|
|
243
258
|
|
|
244
|
-
//
|
|
259
|
+
// Remove any pre-existing symlink so we can recreate it cleanly.
|
|
245
260
|
if (fs.existsSync(agentTarget) || fs.lstatSync(agentTarget, { throwIfNoEntry: false })) {
|
|
246
261
|
try {
|
|
247
262
|
fs.unlinkSync(agentTarget);
|
|
248
263
|
} catch (_) {
|
|
249
|
-
/*
|
|
264
|
+
/* target didn't exist after all — that's fine */
|
|
250
265
|
}
|
|
251
266
|
}
|
|
252
267
|
|
|
253
268
|
try {
|
|
254
|
-
//
|
|
269
|
+
// Relative symlink from the agent's dir to the universal dir.
|
|
255
270
|
const relPath = path.relative(parentDir, universalTarget);
|
|
256
271
|
fs.symlinkSync(relPath, agentTarget);
|
|
257
272
|
agentResults.push({ agent: agent.name, method: 'symlinked', dir: agent.dir });
|
|
258
273
|
} catch (symlinkErr) {
|
|
259
|
-
//
|
|
274
|
+
// Windows without admin can't create symlinks. Fall back to copy
|
|
275
|
+
// and remember so we can warn the user at the end.
|
|
260
276
|
copyFallbacks++;
|
|
261
277
|
if (!fs.existsSync(agentTarget)) {
|
|
262
278
|
fs.mkdirSync(agentTarget, { recursive: true });
|
|
@@ -277,18 +293,18 @@ function performInstall(skillsSourceDir, skillDirs, selectedAgents, baseDir) {
|
|
|
277
293
|
}
|
|
278
294
|
|
|
279
295
|
/**
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
296
|
+
* Configure the 2 MCP servers (powerbi-modeling + microsoft-learn)
|
|
297
|
+
* for each selected agent by writing the config file in the path and
|
|
298
|
+
* format that agent expects.
|
|
283
299
|
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
300
|
+
* Per-agent errors don't abort the whole flow: they're collected in the
|
|
301
|
+
* results array so the caller can display them and decide on the exit
|
|
302
|
+
* code.
|
|
287
303
|
*
|
|
288
|
-
* @param {string[]} selectedAgents - IDs
|
|
289
|
-
* @param {string} packageDir - Absolute path
|
|
290
|
-
* @param {string} baseDir -
|
|
291
|
-
* @param {Object} chalk - chalk instance
|
|
304
|
+
* @param {string[]} selectedAgents - Agent IDs to configure
|
|
305
|
+
* @param {string} packageDir - Absolute path to the installed package
|
|
306
|
+
* @param {string} baseDir - User's home directory (for display)
|
|
307
|
+
* @param {Object} chalk - chalk instance for colored output
|
|
292
308
|
* @returns {Array<{agent: string, success: boolean, configPath?: string, error?: string}>}
|
|
293
309
|
*/
|
|
294
310
|
function configureMcpsForAgents(selectedAgents, packageDir, baseDir, chalk) {
|
|
@@ -315,9 +331,9 @@ function configureMcpsForAgents(selectedAgents, packageDir, baseDir, chalk) {
|
|
|
315
331
|
}
|
|
316
332
|
|
|
317
333
|
/**
|
|
318
|
-
*
|
|
319
|
-
* @param {string[]} args -
|
|
320
|
-
* @param {Object} config -
|
|
334
|
+
* Main handler for the `super install` command.
|
|
335
|
+
* @param {string[]} args - CLI arguments
|
|
336
|
+
* @param {Object} config - Command config from the CLI (packageDir, version)
|
|
321
337
|
*/
|
|
322
338
|
async function installCommand(args, config) {
|
|
323
339
|
const chalk = require('chalk');
|
|
@@ -325,10 +341,11 @@ async function installCommand(args, config) {
|
|
|
325
341
|
|
|
326
342
|
const opts = parseArgs(args);
|
|
327
343
|
|
|
328
|
-
//
|
|
344
|
+
// Always install at the user level (home directory) — skills and MCPs
|
|
345
|
+
// apply across all projects without polluting any specific repo.
|
|
329
346
|
const baseDir = os.homedir();
|
|
330
347
|
|
|
331
|
-
//
|
|
348
|
+
// Locate the skills inside the installed package.
|
|
332
349
|
const packageDir = config.packageDir || path.dirname(path.dirname(__dirname));
|
|
333
350
|
const skillsSourceDir = path.join(packageDir, 'skills');
|
|
334
351
|
|
|
@@ -341,7 +358,7 @@ async function installCommand(args, config) {
|
|
|
341
358
|
process.exit(1);
|
|
342
359
|
}
|
|
343
360
|
|
|
344
|
-
//
|
|
361
|
+
// Read the available skill directories from the package.
|
|
345
362
|
let skillDirs;
|
|
346
363
|
try {
|
|
347
364
|
skillDirs = fs
|
|
@@ -355,7 +372,7 @@ async function installCommand(args, config) {
|
|
|
355
372
|
process.exit(1);
|
|
356
373
|
}
|
|
357
374
|
|
|
358
|
-
// Header
|
|
375
|
+
// Header box shown at the top of every install run.
|
|
359
376
|
console.log(
|
|
360
377
|
boxen(
|
|
361
378
|
chalk.bold.cyan('BI Agent Superpowers') +
|
|
@@ -373,7 +390,7 @@ async function installCommand(args, config) {
|
|
|
373
390
|
console.log(chalk.gray(` Ruta de instalación: ~/${UNIVERSAL_DIR}/`));
|
|
374
391
|
console.log(chalk.gray(` Skills: ${skillDirs.length} disponibles\n`));
|
|
375
392
|
|
|
376
|
-
//
|
|
393
|
+
// Resolve which agents to configure.
|
|
377
394
|
const selectedAgents = await resolveSelectedAgents(opts, baseDir, chalk);
|
|
378
395
|
|
|
379
396
|
if (selectedAgents.length === 0) {
|
|
@@ -387,7 +404,7 @@ async function installCommand(args, config) {
|
|
|
387
404
|
)
|
|
388
405
|
);
|
|
389
406
|
|
|
390
|
-
//
|
|
407
|
+
// Phase 1: copy skills and create symlinks per agent.
|
|
391
408
|
let agentResults;
|
|
392
409
|
let copyFallbacks;
|
|
393
410
|
try {
|
|
@@ -399,20 +416,20 @@ async function installCommand(args, config) {
|
|
|
399
416
|
process.exit(1);
|
|
400
417
|
}
|
|
401
418
|
|
|
402
|
-
//
|
|
419
|
+
// Report on the universal skills path.
|
|
403
420
|
const universalAgents = selectedAgents
|
|
404
421
|
.filter((id) => AGENTS[id] && AGENTS[id].dir === UNIVERSAL_DIR)
|
|
405
422
|
.map((id) => AGENTS[id].name);
|
|
406
423
|
const universalSuffix = universalAgents.length > 0 ? ` — ${universalAgents.join(', ')}` : '';
|
|
407
424
|
console.log(chalk.green(` ✓ ${UNIVERSAL_DIR}/ (${skillDirs.length} skills)${universalSuffix}`));
|
|
408
425
|
|
|
409
|
-
//
|
|
426
|
+
// Report per agent.
|
|
410
427
|
for (const result of agentResults) {
|
|
411
428
|
const icon = result.method === 'symlinked' ? '→' : '✓';
|
|
412
429
|
console.log(chalk.green(` ${icon} ${result.dir}/ (${result.method}) — ${result.agent}`));
|
|
413
430
|
}
|
|
414
431
|
|
|
415
|
-
//
|
|
432
|
+
// Warn if any agent fell back from symlink to copy.
|
|
416
433
|
if (copyFallbacks > 0) {
|
|
417
434
|
console.log(
|
|
418
435
|
chalk.yellow(
|
|
@@ -423,10 +440,10 @@ async function installCommand(args, config) {
|
|
|
423
440
|
);
|
|
424
441
|
}
|
|
425
442
|
|
|
426
|
-
//
|
|
443
|
+
// Phase 2: write the 2 MCP configs per agent.
|
|
427
444
|
const mcpResults = configureMcpsForAgents(selectedAgents, packageDir, baseDir, chalk);
|
|
428
445
|
|
|
429
|
-
//
|
|
446
|
+
// Build the final summary box.
|
|
430
447
|
const totalAgents = agentResults.length + (universalAgents.length > 0 ? 1 : 0);
|
|
431
448
|
const mcpSuccess = mcpResults.filter((r) => r.success).length;
|
|
432
449
|
const mcpFailures = mcpResults.filter((r) => !r.success);
|
|
@@ -457,7 +474,7 @@ async function installCommand(args, config) {
|
|
|
457
474
|
'\n' +
|
|
458
475
|
chalk.gray(' /project-kickoff — Analizá tu proyecto BI') +
|
|
459
476
|
'\n' +
|
|
460
|
-
chalk.gray(' /pbi-connect — Conectá
|
|
477
|
+
chalk.gray(' /pbi-connect — Conectá tu agente a Power BI Desktop'),
|
|
461
478
|
{
|
|
462
479
|
padding: 1,
|
|
463
480
|
margin: { top: 1 },
|
|
@@ -468,14 +485,14 @@ async function installCommand(args, config) {
|
|
|
468
485
|
);
|
|
469
486
|
|
|
470
487
|
if (hasFailures) {
|
|
471
|
-
// Non-zero exit so CI/scripts know something went wrong
|
|
472
|
-
//
|
|
473
|
-
// failure (exit 1).
|
|
488
|
+
// Non-zero exit so CI/scripts know something went wrong. Exit code 2
|
|
489
|
+
// distinguishes partial failure (skills ok, some MCPs failed) from
|
|
490
|
+
// total failure (exit code 1).
|
|
474
491
|
process.exitCode = 2;
|
|
475
492
|
}
|
|
476
493
|
}
|
|
477
494
|
|
|
478
|
-
//
|
|
495
|
+
// Internal exports for testing.
|
|
479
496
|
module.exports = installCommand;
|
|
480
497
|
module.exports.parseArgs = parseArgs;
|
|
481
498
|
module.exports.detectAgents = detectAgents;
|
package/bin/lib/agents.js
CHANGED
|
@@ -22,6 +22,25 @@
|
|
|
22
22
|
/**
|
|
23
23
|
* Supported agents, in the order they appear in the interactive installer.
|
|
24
24
|
* Object insertion order matters — the installer renders the list in this order.
|
|
25
|
+
*
|
|
26
|
+
* IMPORTANT: the `dir` values are the USER-LEVEL skill directories for each
|
|
27
|
+
* agent. They're relative to the user's home directory (installed with
|
|
28
|
+
* `super install` at ~/). Each path comes from official docs — if you
|
|
29
|
+
* change one, verify it against the linked source first.
|
|
30
|
+
*
|
|
31
|
+
* Source docs:
|
|
32
|
+
* - GitHub Copilot: https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/create-skills
|
|
33
|
+
* "For personal skills, shared across projects, use ~/.copilot/skills"
|
|
34
|
+
* (NOT ~/.github/skills — that's project-level for the cloud agent.)
|
|
35
|
+
* - Claude Code: https://code.claude.com/docs/en/skills
|
|
36
|
+
* "Personal skills go in ~/.claude/skills/"
|
|
37
|
+
* - Codex: https://developers.openai.com/codex/skills
|
|
38
|
+
* "Personal skills go in ~/.codex/skills or ~/.agents/skills"
|
|
39
|
+
* We use the universal ~/.agents/skills path.
|
|
40
|
+
* - Gemini CLI: https://geminicli.com/docs/cli/skills/
|
|
41
|
+
* "User skills in $HOME/.gemini/skills"
|
|
42
|
+
* - Kilo Code: https://kilo.ai/docs/customize/skills
|
|
43
|
+
* "Global skills in ~/.kilo/skills/" (NOT ~/.kilocode/)
|
|
25
44
|
*/
|
|
26
45
|
const AGENTS = {
|
|
27
46
|
'github-copilot': { name: 'GitHub Copilot', dir: '.copilot/skills' },
|
package/bin/lib/mcp-config.js
CHANGED
|
@@ -124,11 +124,19 @@ function escapeRegex(str) {
|
|
|
124
124
|
//
|
|
125
125
|
// We describe each agent in a single table and build the writer functions
|
|
126
126
|
// from it so adding a new JSON agent is a one-line change.
|
|
127
|
+
//
|
|
128
|
+
// IMPORTANT: every entry below has a source URL pointing at the official
|
|
129
|
+
// docs where the path/format comes from. If you change any of these,
|
|
130
|
+
// verify the new value against the linked source first. Silent drift is
|
|
131
|
+
// the hardest class of bug in this file because the writes succeed even
|
|
132
|
+
// when the agent never reads the file.
|
|
127
133
|
|
|
128
134
|
const JSON_AGENT_CONFIGS = {
|
|
129
135
|
'claude-code': {
|
|
130
|
-
//
|
|
131
|
-
//
|
|
136
|
+
// Source: https://code.claude.com/docs/en/mcp
|
|
137
|
+
// "User-scoped servers are stored in ~/.claude.json"
|
|
138
|
+
// Note: ~/.claude/settings.json does NOT work for MCPs (known docs bug).
|
|
139
|
+
// stdio entries omit `type` here; HTTP entries include it.
|
|
132
140
|
configPath: () => path.join(os.homedir(), '.claude.json'),
|
|
133
141
|
wrapperKey: 'mcpServers',
|
|
134
142
|
stdioIncludesType: false,
|
|
@@ -136,7 +144,9 @@ const JSON_AGENT_CONFIGS = {
|
|
|
136
144
|
httpField: 'url',
|
|
137
145
|
},
|
|
138
146
|
'github-copilot': {
|
|
139
|
-
//
|
|
147
|
+
// Source: https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers
|
|
148
|
+
// "MCP servers are saved to ~/.copilot/mcp-config.json"
|
|
149
|
+
// Copilot uses `servers` (NOT mcpServers) and every server needs
|
|
140
150
|
// an explicit `type` field.
|
|
141
151
|
configPath: () => path.join(os.homedir(), '.copilot', 'mcp-config.json'),
|
|
142
152
|
wrapperKey: 'servers',
|
|
@@ -145,6 +155,8 @@ const JSON_AGENT_CONFIGS = {
|
|
|
145
155
|
httpField: 'url',
|
|
146
156
|
},
|
|
147
157
|
'gemini-cli': {
|
|
158
|
+
// Source: https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md
|
|
159
|
+
// "MCP config goes in ~/.gemini/settings.json under mcpServers"
|
|
148
160
|
// Gemini uses `httpUrl` (NOT `url`) for HTTP transports and omits
|
|
149
161
|
// `type` — it's inferred from which key is present.
|
|
150
162
|
configPath: () => path.join(os.homedir(), '.gemini', 'settings.json'),
|
|
@@ -154,10 +166,12 @@ const JSON_AGENT_CONFIGS = {
|
|
|
154
166
|
httpField: 'httpUrl',
|
|
155
167
|
},
|
|
156
168
|
kilo: {
|
|
169
|
+
// Source: https://kilo.ai/docs/automate/mcp/using-in-kilo-code
|
|
157
170
|
// Kilo Code uses ~/.kilo/ as the user config root (consistent with
|
|
158
171
|
// ~/.kilo/skills/). The global MCP settings live at the natural
|
|
159
172
|
// neighbor path — mcp_settings.json — which is what the Kilo VS Code
|
|
160
|
-
// extension and CLI both read.
|
|
173
|
+
// extension and CLI both read. (NOT ~/.kilocode/ — that path is
|
|
174
|
+
// project-level only.)
|
|
161
175
|
configPath: () => path.join(os.homedir(), '.kilo', 'mcp_settings.json'),
|
|
162
176
|
wrapperKey: 'mcpServers',
|
|
163
177
|
stdioIncludesType: false,
|
|
@@ -226,6 +240,11 @@ function makeJsonWriter(agentId) {
|
|
|
226
240
|
// ============================================
|
|
227
241
|
// CODEX (OpenAI) — TOML format
|
|
228
242
|
// ============================================
|
|
243
|
+
// Source: https://developers.openai.com/codex/mcp
|
|
244
|
+
// Also: https://github.com/openai/codex/blob/main/docs/config.md
|
|
245
|
+
// "Codex CLI reads MCP server config from ~/.codex/config.toml, where
|
|
246
|
+
// each server gets its own section with format [mcp_servers.my-server]"
|
|
247
|
+
//
|
|
229
248
|
// Writes ~/.codex/config.toml, appending [mcp_servers.*] sections.
|
|
230
249
|
// Preserves existing content by removing only our own sections before
|
|
231
250
|
// appending the fresh ones.
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"name": "bi-superpowers",
|
|
4
4
|
"display_name": "BI Agent Superpowers",
|
|
5
5
|
"version": "0.0.0-template",
|
|
6
|
-
"description": "
|
|
7
|
-
"long_description": "BI Agent Superpowers brings
|
|
6
|
+
"description": "Open-source Power BI Desktop toolkit for Claude Desktop — 2 skills and 2 official Microsoft MCP servers.",
|
|
7
|
+
"long_description": "BI Agent Superpowers brings focused Power BI Desktop skills to Claude Desktop: project kickoff analysis, direct Power BI Desktop connection, and official Microsoft MCP servers (Power BI Modeling + Microsoft Learn). Built for Power BI developers and data analysts.",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Lucas Sanchez",
|
|
10
10
|
"url": "https://github.com/luquimbo"
|
|
@@ -13,16 +13,9 @@
|
|
|
13
13
|
"type": "git",
|
|
14
14
|
"url": "https://github.com/luquimbo/bi-superpowers"
|
|
15
15
|
},
|
|
16
|
-
"homepage": "https://
|
|
16
|
+
"homepage": "https://github.com/luquimbo/bi-superpowers",
|
|
17
17
|
"license": "MIT",
|
|
18
|
-
"keywords": [
|
|
19
|
-
"power-bi",
|
|
20
|
-
"dax",
|
|
21
|
-
"fabric",
|
|
22
|
-
"excel",
|
|
23
|
-
"data-modeling",
|
|
24
|
-
"business-intelligence"
|
|
25
|
-
],
|
|
18
|
+
"keywords": ["power-bi", "power-bi-desktop", "mcp", "claude", "business-intelligence"],
|
|
26
19
|
"server": {
|
|
27
20
|
"type": "node",
|
|
28
21
|
"entry_point": "server.js"
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* BI Agent Superpowers — MCP Server for Claude Desktop
|
|
5
5
|
*
|
|
6
|
-
* Lightweight MCP server that exposes
|
|
7
|
-
* Bundled into a .mcpb extension via
|
|
6
|
+
* Lightweight MCP server that exposes each bi-superpowers skill as a
|
|
7
|
+
* Claude Desktop prompt. Bundled into a .mcpb extension via
|
|
8
|
+
* `super build-desktop`.
|
|
8
9
|
*
|
|
9
|
-
* Skills are loaded from the ./skills/ directory (copied at build time).
|
|
10
|
+
* Skills are loaded from the ./skills/ directory (copied in at build time).
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
@@ -18,29 +19,25 @@ import { fileURLToPath } from 'url';
|
|
|
18
19
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
20
|
const skillsDir = path.join(__dirname, 'skills');
|
|
20
21
|
|
|
21
|
-
// Create the MCP server instance
|
|
22
|
+
// Create the MCP server instance.
|
|
22
23
|
const server = new McpServer({
|
|
23
24
|
name: 'bi-superpowers',
|
|
24
25
|
version: '0.0.0-template',
|
|
25
26
|
});
|
|
26
27
|
|
|
27
|
-
// Load all skill markdown files from the bundled skills/ directory
|
|
28
|
+
// Load all skill markdown files from the bundled skills/ directory.
|
|
28
29
|
const skillFiles = fs.existsSync(skillsDir)
|
|
29
30
|
? fs.readdirSync(skillsDir).filter((f) => f.endsWith('.md'))
|
|
30
31
|
: [];
|
|
31
32
|
|
|
32
|
-
// Register each skill as an MCP prompt that Claude Desktop can invoke
|
|
33
|
+
// Register each skill as an MCP prompt that Claude Desktop can invoke.
|
|
33
34
|
for (const file of skillFiles) {
|
|
34
35
|
const name = path.basename(file, '.md');
|
|
35
36
|
const content = fs.readFileSync(path.join(skillsDir, file), 'utf8');
|
|
36
37
|
|
|
37
|
-
//
|
|
38
|
-
const firstLine = content
|
|
39
|
-
|
|
40
|
-
.find((l) => l.trim() && !l.startsWith('#'));
|
|
41
|
-
const description = firstLine
|
|
42
|
-
? firstLine.slice(0, 120).trim()
|
|
43
|
-
: `BI Superpowers: ${name}`;
|
|
38
|
+
// Use the first non-header line as a short description for the prompt.
|
|
39
|
+
const firstLine = content.split('\n').find((l) => l.trim() && !l.startsWith('#'));
|
|
40
|
+
const description = firstLine ? firstLine.slice(0, 120).trim() : `BI Superpowers: ${name}`;
|
|
44
41
|
|
|
45
42
|
server.prompt(name, { description }, () => ({
|
|
46
43
|
messages: [
|
|
@@ -52,10 +49,15 @@ for (const file of skillFiles) {
|
|
|
52
49
|
}));
|
|
53
50
|
}
|
|
54
51
|
|
|
55
|
-
// Register a setup-mcp helper prompt
|
|
52
|
+
// Register a setup-mcp helper prompt that explains how to wire the
|
|
53
|
+
// official Microsoft MCP servers (Power BI Modeling + Microsoft Learn)
|
|
54
|
+
// into Claude Desktop.
|
|
56
55
|
server.prompt(
|
|
57
56
|
'setup-mcp',
|
|
58
|
-
{
|
|
57
|
+
{
|
|
58
|
+
description:
|
|
59
|
+
'Instructions to configure the Power BI Modeling and Microsoft Learn MCP servers in Claude Desktop',
|
|
60
|
+
},
|
|
59
61
|
() => ({
|
|
60
62
|
messages: [
|
|
61
63
|
{
|
|
@@ -64,25 +66,32 @@ server.prompt(
|
|
|
64
66
|
type: 'text',
|
|
65
67
|
text: `# Configure MCP Servers for Claude Desktop
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
to your \`claude_desktop_config.json\`
|
|
69
|
+
bi-superpowers ships two official Microsoft MCP servers. To wire them
|
|
70
|
+
into Claude Desktop, add the following to your \`claude_desktop_config.json\`
|
|
71
|
+
(Settings → Developer → Edit Config):
|
|
69
72
|
|
|
70
73
|
\`\`\`json
|
|
71
74
|
{
|
|
72
75
|
"mcpServers": {
|
|
73
|
-
"powerbi-
|
|
74
|
-
"command": "
|
|
75
|
-
"args": ["
|
|
76
|
+
"powerbi-modeling": {
|
|
77
|
+
"command": "node",
|
|
78
|
+
"args": ["<absolute path to powerbi-modeling-launcher.js>"]
|
|
76
79
|
},
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
+
"microsoft-learn": {
|
|
81
|
+
"type": "http",
|
|
82
|
+
"url": "https://learn.microsoft.com/api/mcp"
|
|
80
83
|
}
|
|
81
84
|
}
|
|
82
85
|
}
|
|
83
86
|
\`\`\`
|
|
84
87
|
|
|
85
|
-
|
|
88
|
+
The \`powerbi-modeling\` server requires Power BI Desktop on Windows
|
|
89
|
+
with a model open. If you prefer automated setup, run
|
|
90
|
+
\`super install\` from the bi-superpowers CLI — it configures both
|
|
91
|
+
servers across all 5 supported AI agents.
|
|
92
|
+
|
|
93
|
+
After saving, restart Claude Desktop. The MCP servers will appear in
|
|
94
|
+
your tools.
|
|
86
95
|
`,
|
|
87
96
|
},
|
|
88
97
|
},
|
|
@@ -90,6 +99,6 @@ After saving, restart Claude Desktop. The MCP servers will appear in your tools.
|
|
|
90
99
|
})
|
|
91
100
|
);
|
|
92
101
|
|
|
93
|
-
// Start the server with stdio transport
|
|
102
|
+
// Start the server with stdio transport.
|
|
94
103
|
const transport = new StdioServerTransport();
|
|
95
104
|
await server.connect(transport);
|