@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.
- package/.claude-plugin/marketplace.json +5 -3
- package/.claude-plugin/plugin.json +28 -2
- package/.claude-plugin/skill-manifest.json +22 -6
- package/.plugin/plugin.json +1 -1
- package/AGENTS.md +52 -36
- package/CHANGELOG.md +295 -0
- package/README.md +75 -26
- package/bin/build-plugin.js +17 -10
- package/bin/cli.js +278 -322
- package/bin/commands/build-desktop.js +35 -16
- package/bin/commands/diff.js +31 -13
- package/bin/commands/install.js +93 -72
- package/bin/commands/lint.js +40 -26
- package/bin/commands/mcp-setup.js +3 -10
- package/bin/commands/update-check.js +389 -0
- package/bin/lib/agents.js +19 -0
- package/bin/lib/generators/claude-plugin.js +144 -6
- package/bin/lib/generators/shared.js +29 -33
- package/bin/lib/mcp-config.js +191 -16
- package/bin/lib/skills.js +115 -27
- package/bin/postinstall.js +4 -2
- package/bin/utils/mcp-detect.js +2 -2
- package/commands/bi-start.md +218 -0
- package/commands/pbi-connect.md +43 -65
- package/commands/project-kickoff.md +393 -673
- package/commands/report-design.md +403 -0
- package/desktop-extension/manifest.json +5 -12
- package/desktop-extension/server.js +34 -25
- package/package.json +6 -10
- package/skills/bi-start/SKILL.md +220 -0
- package/skills/bi-start/scripts/update-check.js +389 -0
- package/skills/pbi-connect/SKILL.md +45 -67
- package/skills/pbi-connect/scripts/update-check.js +389 -0
- package/skills/project-kickoff/SKILL.md +395 -675
- package/skills/project-kickoff/scripts/update-check.js +389 -0
- package/skills/report-design/SKILL.md +405 -0
- package/skills/report-design/references/cli-commands.md +184 -0
- package/skills/report-design/references/cli-setup.md +101 -0
- package/skills/report-design/references/close-write-open-pattern.md +80 -0
- package/skills/report-design/references/layouts/finance.md +65 -0
- package/skills/report-design/references/layouts/generic.md +46 -0
- package/skills/report-design/references/layouts/hr.md +48 -0
- package/skills/report-design/references/layouts/marketing.md +45 -0
- package/skills/report-design/references/layouts/operations.md +44 -0
- package/skills/report-design/references/layouts/sales.md +50 -0
- package/skills/report-design/references/native-visuals.md +341 -0
- package/skills/report-design/references/pbi-desktop-installation.md +87 -0
- package/skills/report-design/references/pbir-preview-activation.md +40 -0
- package/skills/report-design/references/slicer.md +89 -0
- package/skills/report-design/references/textbox.md +101 -0
- package/skills/report-design/references/themes/BISuperpowers.json +915 -0
- package/skills/report-design/references/troubleshooting.md +135 -0
- package/skills/report-design/references/visual-types.md +78 -0
- package/skills/report-design/scripts/apply-theme.js +243 -0
- package/skills/report-design/scripts/create-visual.js +878 -0
- package/skills/report-design/scripts/ensure-pbi-cli.sh +41 -0
- package/skills/report-design/scripts/update-check.js +389 -0
- package/skills/report-design/scripts/validate-pbir.js +322 -0
- package/src/content/base.md +12 -68
- package/src/content/mcp-requirements.json +0 -25
- package/src/content/routing.md +19 -74
- package/src/content/skills/bi-start.md +191 -0
- package/src/content/skills/pbi-connect.md +22 -65
- package/src/content/skills/project-kickoff.md +372 -673
- package/src/content/skills/report-design/SKILL.md +376 -0
- package/src/content/skills/report-design/references/cli-commands.md +184 -0
- package/src/content/skills/report-design/references/cli-setup.md +101 -0
- package/src/content/skills/report-design/references/close-write-open-pattern.md +80 -0
- package/src/content/skills/report-design/references/layouts/finance.md +65 -0
- package/src/content/skills/report-design/references/layouts/generic.md +46 -0
- package/src/content/skills/report-design/references/layouts/hr.md +48 -0
- package/src/content/skills/report-design/references/layouts/marketing.md +45 -0
- package/src/content/skills/report-design/references/layouts/operations.md +44 -0
- package/src/content/skills/report-design/references/layouts/sales.md +50 -0
- package/src/content/skills/report-design/references/native-visuals.md +341 -0
- package/src/content/skills/report-design/references/pbi-desktop-installation.md +87 -0
- package/src/content/skills/report-design/references/pbir-preview-activation.md +40 -0
- package/src/content/skills/report-design/references/slicer.md +89 -0
- package/src/content/skills/report-design/references/textbox.md +101 -0
- package/src/content/skills/report-design/references/themes/BISuperpowers.json +915 -0
- package/src/content/skills/report-design/references/troubleshooting.md +135 -0
- package/src/content/skills/report-design/references/visual-types.md +78 -0
- package/src/content/skills/report-design/scripts/apply-theme.js +243 -0
- package/src/content/skills/report-design/scripts/create-visual.js +878 -0
- package/src/content/skills/report-design/scripts/ensure-pbi-cli.sh +41 -0
- package/src/content/skills/report-design/scripts/validate-pbir.js +322 -0
- 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/install.test.js +0 -289
- package/bin/commands/lint.test.js +0 -103
- 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/lib/generators/claude-plugin.test.js +0 -111
- package/bin/lib/mcp-config.test.js +0 -310
- package/bin/lib/microsoft-mcp.test.js +0 -115
- package/bin/utils/errors.js +0 -159
- package/bin/utils/git.js +0 -298
- package/bin/utils/logger.js +0 -142
- package/bin/utils/mcp-detect.test.js +0 -81
- 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/bin/utils/tui.test.js +0 -127
- 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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Builds a .mcpb extension for Claude Desktop by:
|
|
6
6
|
* 1. Copying desktop-extension/ template to a temp directory
|
|
7
|
-
* 2. Copying
|
|
7
|
+
* 2. Copying every skill source (flat + folder-based) into temp/skills/
|
|
8
8
|
* 3. Installing dependencies
|
|
9
9
|
* 4. Running `npx @anthropic-ai/mcpb pack` to create the .mcpb file
|
|
10
10
|
* 5. Moving the result to the current directory
|
|
@@ -18,6 +18,7 @@ const fs = require('fs');
|
|
|
18
18
|
const path = require('path');
|
|
19
19
|
const os = require('os');
|
|
20
20
|
const { execSync } = require('child_process');
|
|
21
|
+
const { loadSkills } = require('../lib/skills');
|
|
21
22
|
|
|
22
23
|
/** Directory where the npm package is installed */
|
|
23
24
|
const PACKAGE_DIR = path.dirname(path.dirname(__dirname));
|
|
@@ -25,8 +26,27 @@ const PACKAGE_DIR = path.dirname(path.dirname(__dirname));
|
|
|
25
26
|
/** Desktop extension template source */
|
|
26
27
|
const DESKTOP_TEMPLATE_DIR = path.join(PACKAGE_DIR, 'desktop-extension');
|
|
27
28
|
|
|
28
|
-
/**
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Bundle skills into the Desktop extension's skills/ directory.
|
|
31
|
+
*
|
|
32
|
+
* Handles both flat `src/content/skills/<name>.md` and folder-based
|
|
33
|
+
* `src/content/skills/<name>/SKILL.md` layouts by flattening each skill
|
|
34
|
+
* to a single `<name>.md` file inside `skillsOutDir`. The Claude Desktop
|
|
35
|
+
* MCP server loads those flat files as prompts — `references/` and
|
|
36
|
+
* `scripts/` are distributed through `super install`, not bundled here.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} skillsOutDir - Destination directory (created if missing)
|
|
39
|
+
* @param {string} packageDir - npm package root containing src/content/skills/
|
|
40
|
+
* @returns {string[]} Names of skills bundled into skillsOutDir
|
|
41
|
+
*/
|
|
42
|
+
function bundleSkills(skillsOutDir, packageDir) {
|
|
43
|
+
fs.mkdirSync(skillsOutDir, { recursive: true });
|
|
44
|
+
const skills = loadSkills({ packageDir });
|
|
45
|
+
for (const skill of skills) {
|
|
46
|
+
fs.writeFileSync(path.join(skillsOutDir, `${skill.name}.md`), skill.content);
|
|
47
|
+
}
|
|
48
|
+
return skills.map((s) => s.name);
|
|
49
|
+
}
|
|
30
50
|
|
|
31
51
|
/**
|
|
32
52
|
* Build the .mcpb extension for Claude Desktop.
|
|
@@ -34,7 +54,7 @@ const SKILLS_SOURCE_DIR = path.join(PACKAGE_DIR, 'src', 'content', 'skills');
|
|
|
34
54
|
* @param {string[]} args - CLI arguments
|
|
35
55
|
* @param {Object} config - Command config from CLI
|
|
36
56
|
*/
|
|
37
|
-
|
|
57
|
+
function buildDesktop(_args, _config) {
|
|
38
58
|
const outputDir = process.cwd();
|
|
39
59
|
|
|
40
60
|
console.log(`
|
|
@@ -50,7 +70,8 @@ BI Agent Superpowers — Build Desktop Extension
|
|
|
50
70
|
}
|
|
51
71
|
|
|
52
72
|
// Verify skills exist
|
|
53
|
-
|
|
73
|
+
const skillsSourceDir = path.join(PACKAGE_DIR, 'src', 'content', 'skills');
|
|
74
|
+
if (!fs.existsSync(skillsSourceDir)) {
|
|
54
75
|
console.error(
|
|
55
76
|
'Error: skill sources not found. Try reinstalling: npm install -g @luquimbo/bi-superpowers'
|
|
56
77
|
);
|
|
@@ -71,15 +92,10 @@ BI Agent Superpowers — Build Desktop Extension
|
|
|
71
92
|
}
|
|
72
93
|
}
|
|
73
94
|
|
|
74
|
-
//
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const skillFiles = fs.readdirSync(SKILLS_SOURCE_DIR).filter((f) => f.endsWith('.md'));
|
|
79
|
-
for (const file of skillFiles) {
|
|
80
|
-
fs.copyFileSync(path.join(SKILLS_SOURCE_DIR, file), path.join(skillsOutDir, file));
|
|
81
|
-
}
|
|
82
|
-
console.log(` Bundled ${skillFiles.length} skills`);
|
|
95
|
+
// Bundle every skill (flat + folder-based) into temp/skills/
|
|
96
|
+
const bundledNames = bundleSkills(path.join(tmpDir, 'skills'), PACKAGE_DIR);
|
|
97
|
+
const skillFiles = bundledNames.map((n) => `${n}.md`);
|
|
98
|
+
console.log(` Bundled ${bundledNames.length} skills`);
|
|
83
99
|
|
|
84
100
|
// Patch template placeholder versions with the real package version
|
|
85
101
|
const pkgVersion = require(path.join(PACKAGE_DIR, 'package.json')).version;
|
|
@@ -149,7 +165,7 @@ BI Agent Superpowers — Build Desktop Extension
|
|
|
149
165
|
2. Go to Settings > Extensions > Install Extension
|
|
150
166
|
|
|
151
167
|
The extension adds ${skillFiles.length} BI skills as MCP prompts.
|
|
152
|
-
Use the "setup-mcp" prompt for Power BI
|
|
168
|
+
Use the "setup-mcp" prompt for Power BI Modeling MCP configuration.
|
|
153
169
|
`);
|
|
154
170
|
} else {
|
|
155
171
|
console.log('\n Build complete. Check current directory for .mcpb file.');
|
|
@@ -165,4 +181,7 @@ BI Agent Superpowers — Build Desktop Extension
|
|
|
165
181
|
// Ignore cleanup errors
|
|
166
182
|
}
|
|
167
183
|
}
|
|
168
|
-
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = buildDesktop;
|
|
187
|
+
module.exports.bundleSkills = bundleSkills;
|
package/bin/commands/diff.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const tui = require('../utils/tui');
|
|
15
|
+
const { readSkillDirectory, normalizeSkillName } = require('../lib/skills');
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Generate a simple diff between two strings
|
|
@@ -122,7 +123,12 @@ function compareFiles(skillPath, generatedPath) {
|
|
|
122
123
|
function getGeneratedPath(skillName, tool, targetDir) {
|
|
123
124
|
switch (tool) {
|
|
124
125
|
case 'claude-code':
|
|
125
|
-
|
|
126
|
+
// The Claude Code plugin generator (bin/lib/generators/claude-plugin.js)
|
|
127
|
+
// writes slash commands to `<targetDir>/commands/<skill>.md` — NOT
|
|
128
|
+
// `.claude/commands/...`. An earlier path here pointed at the old
|
|
129
|
+
// location and silently made `super scan --tool claude-code`
|
|
130
|
+
// report every skill as "not-generated".
|
|
131
|
+
return path.join(targetDir, 'commands', `${skillName}.md`);
|
|
126
132
|
case 'cursor':
|
|
127
133
|
// Cursor uses a single file, can't diff per-skill
|
|
128
134
|
return null;
|
|
@@ -249,23 +255,27 @@ function diffCommand(args, config) {
|
|
|
249
255
|
return;
|
|
250
256
|
}
|
|
251
257
|
|
|
252
|
-
// Get skill files to compare
|
|
258
|
+
// Get skill files to compare. Use the shared skill loader so both
|
|
259
|
+
// flat (`<name>.md`) and folder-based (`<name>/SKILL.md`) skills
|
|
260
|
+
// are picked up — a previous version filtered with
|
|
261
|
+
// `f.endsWith('.md')` and silently dropped every folder-based skill.
|
|
262
|
+
const allSkills = readSkillDirectory(skillsDir);
|
|
263
|
+
const skillsByName = new Map(allSkills.map((s) => [s.name, s]));
|
|
264
|
+
const skillNameByPath = new Map(allSkills.map((s) => [s.path, s.name]));
|
|
253
265
|
let skillFiles = [];
|
|
254
266
|
|
|
255
267
|
if (options.skill) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
268
|
+
// normalizeSkillName accepts `dax`, `dax.md`, `report-design`, or
|
|
269
|
+
// `report-design/SKILL.md` and returns the canonical skill name.
|
|
270
|
+
const skill = skillsByName.get(normalizeSkillName(options.skill));
|
|
271
|
+
if (skill) {
|
|
272
|
+
skillFiles = [skill.path];
|
|
260
273
|
} else {
|
|
261
274
|
tui.error(`Skill not found: ${options.skill}`);
|
|
262
275
|
process.exit(1);
|
|
263
276
|
}
|
|
264
277
|
} else {
|
|
265
|
-
skillFiles =
|
|
266
|
-
.readdirSync(skillsDir)
|
|
267
|
-
.filter((f) => f.endsWith('.md'))
|
|
268
|
-
.map((f) => path.join(skillsDir, f));
|
|
278
|
+
skillFiles = allSkills.map((s) => s.path);
|
|
269
279
|
}
|
|
270
280
|
|
|
271
281
|
if (skillFiles.length === 0) {
|
|
@@ -276,9 +286,11 @@ function diffCommand(args, config) {
|
|
|
276
286
|
tui.info(`Comparing ${skillFiles.length} skill(s) for ${options.tool}...`);
|
|
277
287
|
tui.section('Changes');
|
|
278
288
|
|
|
279
|
-
// Compare each skill
|
|
289
|
+
// Compare each skill. Use the loader-built map to derive the canonical
|
|
290
|
+
// skill name — for folder-based skills the path is `<folder>/SKILL.md`
|
|
291
|
+
// and `path.basename(...)` would return `SKILL`, breaking the diff.
|
|
280
292
|
const results = skillFiles.map((skillPath) => {
|
|
281
|
-
const skillName = path.basename(skillPath, '.md');
|
|
293
|
+
const skillName = skillNameByPath.get(skillPath) || path.basename(skillPath, '.md');
|
|
282
294
|
const generatedPath = getGeneratedPath(skillName, options.tool, targetDir);
|
|
283
295
|
|
|
284
296
|
if (!generatedPath) {
|
|
@@ -324,4 +336,10 @@ function diffCommand(args, config) {
|
|
|
324
336
|
}
|
|
325
337
|
}
|
|
326
338
|
|
|
327
|
-
|
|
339
|
+
// Expose primitives for tests (B3 — replace fake tests with real ones).
|
|
340
|
+
module.exports = Object.assign(diffCommand, {
|
|
341
|
+
generateDiff,
|
|
342
|
+
compareFiles,
|
|
343
|
+
getGeneratedPath,
|
|
344
|
+
parseArgs,
|
|
345
|
+
});
|
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);
|
|
@@ -453,11 +470,15 @@ async function installCommand(args, config) {
|
|
|
453
470
|
'\n' +
|
|
454
471
|
chalk.gray('Reiniciá tu agente AI para que tome los MCPs nuevos.') +
|
|
455
472
|
'\n\n' +
|
|
456
|
-
chalk.gray('Los
|
|
473
|
+
chalk.gray('Los 4 skills disponibles:') +
|
|
457
474
|
'\n' +
|
|
458
|
-
chalk.gray(' /
|
|
475
|
+
chalk.gray(' /bi-start — Arrancar una sesión: menú + update + conexión') +
|
|
459
476
|
'\n' +
|
|
460
|
-
chalk.gray(' /
|
|
477
|
+
chalk.gray(' /project-kickoff — Analizá tu proyecto BI (proyecto nuevo)') +
|
|
478
|
+
'\n' +
|
|
479
|
+
chalk.gray(' /pbi-connect — Conectá tu agente a Power BI Desktop') +
|
|
480
|
+
'\n' +
|
|
481
|
+
chalk.gray(' /report-design — Generá reportes PBIR para Power BI Desktop (Windows)'),
|
|
461
482
|
{
|
|
462
483
|
padding: 1,
|
|
463
484
|
margin: { top: 1 },
|
|
@@ -468,14 +489,14 @@ async function installCommand(args, config) {
|
|
|
468
489
|
);
|
|
469
490
|
|
|
470
491
|
if (hasFailures) {
|
|
471
|
-
// Non-zero exit so CI/scripts know something went wrong
|
|
472
|
-
//
|
|
473
|
-
// failure (exit 1).
|
|
492
|
+
// Non-zero exit so CI/scripts know something went wrong. Exit code 2
|
|
493
|
+
// distinguishes partial failure (skills ok, some MCPs failed) from
|
|
494
|
+
// total failure (exit code 1).
|
|
474
495
|
process.exitCode = 2;
|
|
475
496
|
}
|
|
476
497
|
}
|
|
477
498
|
|
|
478
|
-
//
|
|
499
|
+
// Internal exports for testing.
|
|
479
500
|
module.exports = installCommand;
|
|
480
501
|
module.exports.parseArgs = parseArgs;
|
|
481
502
|
module.exports.detectAgents = detectAgents;
|