@luquimbo/bi-superpowers 3.1.0 → 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/README.md +2 -2
- package/bin/build-plugin.js +6 -6
- package/bin/cli.js +169 -310
- package/bin/commands/install.js +87 -70
- package/bin/commands/install.test.js +2 -2
- package/bin/lib/agents.js +21 -2
- package/bin/lib/mcp-config.js +27 -5
- package/bin/lib/mcp-config.test.js +1 -1
- 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;
|
|
@@ -110,7 +110,7 @@ describe('install command - detectAgents', () => {
|
|
|
110
110
|
|
|
111
111
|
test('detects multiple agents', () => {
|
|
112
112
|
fs.mkdirSync(path.join(tempDir, '.claude'), { recursive: true });
|
|
113
|
-
fs.mkdirSync(path.join(tempDir, '.
|
|
113
|
+
fs.mkdirSync(path.join(tempDir, '.copilot'), { recursive: true });
|
|
114
114
|
fs.mkdirSync(path.join(tempDir, '.gemini'), { recursive: true });
|
|
115
115
|
const detected = detectAgents(tempDir);
|
|
116
116
|
assert.ok(detected.includes('claude-code'));
|
|
@@ -270,7 +270,7 @@ describe('install command - integration: --all --yes', () => {
|
|
|
270
270
|
path.join(tempHome, '.copilot', 'mcp-config.json'),
|
|
271
271
|
path.join(tempHome, '.codex', 'config.toml'),
|
|
272
272
|
path.join(tempHome, '.gemini', 'settings.json'),
|
|
273
|
-
path.join(tempHome, '.
|
|
273
|
+
path.join(tempHome, '.kilo', 'mcp_settings.json'),
|
|
274
274
|
];
|
|
275
275
|
for (const filePath of expectedMcpFiles) {
|
|
276
276
|
assert.ok(fs.existsSync(filePath), `expected MCP config at ${filePath}`);
|
package/bin/lib/agents.js
CHANGED
|
@@ -22,13 +22,32 @@
|
|
|
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
|
-
'github-copilot': { name: 'GitHub Copilot', dir: '.
|
|
46
|
+
'github-copilot': { name: 'GitHub Copilot', dir: '.copilot/skills' },
|
|
28
47
|
'claude-code': { name: 'Claude Code', dir: '.claude/skills' },
|
|
29
48
|
codex: { name: 'Codex (OpenAI)', dir: '.agents/skills' },
|
|
30
49
|
'gemini-cli': { name: 'Gemini CLI', dir: '.gemini/skills' },
|
|
31
|
-
kilo: { name: 'Kilo Code', dir: '.
|
|
50
|
+
kilo: { name: 'Kilo Code', dir: '.kilo/skills' },
|
|
32
51
|
};
|
|
33
52
|
|
|
34
53
|
/**
|
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,8 +166,13 @@ const JSON_AGENT_CONFIGS = {
|
|
|
154
166
|
httpField: 'httpUrl',
|
|
155
167
|
},
|
|
156
168
|
kilo: {
|
|
157
|
-
//
|
|
158
|
-
|
|
169
|
+
// Source: https://kilo.ai/docs/automate/mcp/using-in-kilo-code
|
|
170
|
+
// Kilo Code uses ~/.kilo/ as the user config root (consistent with
|
|
171
|
+
// ~/.kilo/skills/). The global MCP settings live at the natural
|
|
172
|
+
// neighbor path — mcp_settings.json — which is what the Kilo VS Code
|
|
173
|
+
// extension and CLI both read. (NOT ~/.kilocode/ — that path is
|
|
174
|
+
// project-level only.)
|
|
175
|
+
configPath: () => path.join(os.homedir(), '.kilo', 'mcp_settings.json'),
|
|
159
176
|
wrapperKey: 'mcpServers',
|
|
160
177
|
stdioIncludesType: false,
|
|
161
178
|
httpIncludesType: false,
|
|
@@ -223,6 +240,11 @@ function makeJsonWriter(agentId) {
|
|
|
223
240
|
// ============================================
|
|
224
241
|
// CODEX (OpenAI) — TOML format
|
|
225
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
|
+
//
|
|
226
248
|
// Writes ~/.codex/config.toml, appending [mcp_servers.*] sections.
|
|
227
249
|
// Preserves existing content by removing only our own sections before
|
|
228
250
|
// appending the fresh ones.
|
|
@@ -154,7 +154,7 @@ describe('MCP writers — JSON agents', () => {
|
|
|
154
154
|
test('kilo writer uses url (standard)', () => {
|
|
155
155
|
const configPath = MCP_WRITERS.kilo(fakePkgDir);
|
|
156
156
|
|
|
157
|
-
assert.strictEqual(configPath, path.join(tempHome, '.
|
|
157
|
+
assert.strictEqual(configPath, path.join(tempHome, '.kilo', 'mcp_settings.json'));
|
|
158
158
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
159
159
|
assert.ok(config.mcpServers);
|
|
160
160
|
assert.strictEqual(config.mcpServers[LEARN_SERVER_NAME].url, MICROSOFT_LEARN_URL);
|
|
@@ -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"
|