@luquimbo/bi-superpowers 3.1.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/.claude-plugin/marketplace.json +5 -3
  2. package/.claude-plugin/plugin.json +28 -2
  3. package/.claude-plugin/skill-manifest.json +22 -6
  4. package/.plugin/plugin.json +1 -1
  5. package/AGENTS.md +52 -36
  6. package/CHANGELOG.md +295 -0
  7. package/README.md +75 -26
  8. package/bin/build-plugin.js +17 -10
  9. package/bin/cli.js +278 -322
  10. package/bin/commands/build-desktop.js +35 -16
  11. package/bin/commands/diff.js +31 -13
  12. package/bin/commands/install.js +93 -72
  13. package/bin/commands/lint.js +40 -26
  14. package/bin/commands/mcp-setup.js +3 -10
  15. package/bin/commands/update-check.js +389 -0
  16. package/bin/lib/agents.js +19 -0
  17. package/bin/lib/generators/claude-plugin.js +144 -6
  18. package/bin/lib/generators/shared.js +29 -33
  19. package/bin/lib/mcp-config.js +191 -16
  20. package/bin/lib/skills.js +115 -27
  21. package/bin/postinstall.js +4 -2
  22. package/bin/utils/mcp-detect.js +2 -2
  23. package/commands/bi-start.md +218 -0
  24. package/commands/pbi-connect.md +43 -65
  25. package/commands/project-kickoff.md +393 -673
  26. package/commands/report-design.md +403 -0
  27. package/desktop-extension/manifest.json +5 -12
  28. package/desktop-extension/server.js +34 -25
  29. package/package.json +6 -10
  30. package/skills/bi-start/SKILL.md +220 -0
  31. package/skills/bi-start/scripts/update-check.js +389 -0
  32. package/skills/pbi-connect/SKILL.md +45 -67
  33. package/skills/pbi-connect/scripts/update-check.js +389 -0
  34. package/skills/project-kickoff/SKILL.md +395 -675
  35. package/skills/project-kickoff/scripts/update-check.js +389 -0
  36. package/skills/report-design/SKILL.md +405 -0
  37. package/skills/report-design/references/cli-commands.md +184 -0
  38. package/skills/report-design/references/cli-setup.md +101 -0
  39. package/skills/report-design/references/close-write-open-pattern.md +80 -0
  40. package/skills/report-design/references/layouts/finance.md +65 -0
  41. package/skills/report-design/references/layouts/generic.md +46 -0
  42. package/skills/report-design/references/layouts/hr.md +48 -0
  43. package/skills/report-design/references/layouts/marketing.md +45 -0
  44. package/skills/report-design/references/layouts/operations.md +44 -0
  45. package/skills/report-design/references/layouts/sales.md +50 -0
  46. package/skills/report-design/references/native-visuals.md +341 -0
  47. package/skills/report-design/references/pbi-desktop-installation.md +87 -0
  48. package/skills/report-design/references/pbir-preview-activation.md +40 -0
  49. package/skills/report-design/references/slicer.md +89 -0
  50. package/skills/report-design/references/textbox.md +101 -0
  51. package/skills/report-design/references/themes/BISuperpowers.json +915 -0
  52. package/skills/report-design/references/troubleshooting.md +135 -0
  53. package/skills/report-design/references/visual-types.md +78 -0
  54. package/skills/report-design/scripts/apply-theme.js +243 -0
  55. package/skills/report-design/scripts/create-visual.js +878 -0
  56. package/skills/report-design/scripts/ensure-pbi-cli.sh +41 -0
  57. package/skills/report-design/scripts/update-check.js +389 -0
  58. package/skills/report-design/scripts/validate-pbir.js +322 -0
  59. package/src/content/base.md +12 -68
  60. package/src/content/mcp-requirements.json +0 -25
  61. package/src/content/routing.md +19 -74
  62. package/src/content/skills/bi-start.md +191 -0
  63. package/src/content/skills/pbi-connect.md +22 -65
  64. package/src/content/skills/project-kickoff.md +372 -673
  65. package/src/content/skills/report-design/SKILL.md +376 -0
  66. package/src/content/skills/report-design/references/cli-commands.md +184 -0
  67. package/src/content/skills/report-design/references/cli-setup.md +101 -0
  68. package/src/content/skills/report-design/references/close-write-open-pattern.md +80 -0
  69. package/src/content/skills/report-design/references/layouts/finance.md +65 -0
  70. package/src/content/skills/report-design/references/layouts/generic.md +46 -0
  71. package/src/content/skills/report-design/references/layouts/hr.md +48 -0
  72. package/src/content/skills/report-design/references/layouts/marketing.md +45 -0
  73. package/src/content/skills/report-design/references/layouts/operations.md +44 -0
  74. package/src/content/skills/report-design/references/layouts/sales.md +50 -0
  75. package/src/content/skills/report-design/references/native-visuals.md +341 -0
  76. package/src/content/skills/report-design/references/pbi-desktop-installation.md +87 -0
  77. package/src/content/skills/report-design/references/pbir-preview-activation.md +40 -0
  78. package/src/content/skills/report-design/references/slicer.md +89 -0
  79. package/src/content/skills/report-design/references/textbox.md +101 -0
  80. package/src/content/skills/report-design/references/themes/BISuperpowers.json +915 -0
  81. package/src/content/skills/report-design/references/troubleshooting.md +135 -0
  82. package/src/content/skills/report-design/references/visual-types.md +78 -0
  83. package/src/content/skills/report-design/scripts/apply-theme.js +243 -0
  84. package/src/content/skills/report-design/scripts/create-visual.js +878 -0
  85. package/src/content/skills/report-design/scripts/ensure-pbi-cli.sh +41 -0
  86. package/src/content/skills/report-design/scripts/validate-pbir.js +322 -0
  87. package/bin/commands/add.js +0 -533
  88. package/bin/commands/add.test.js +0 -77
  89. package/bin/commands/changelog.js +0 -443
  90. package/bin/commands/install.test.js +0 -289
  91. package/bin/commands/lint.test.js +0 -103
  92. package/bin/commands/pull.js +0 -287
  93. package/bin/commands/pull.test.js +0 -36
  94. package/bin/commands/push.js +0 -231
  95. package/bin/commands/push.test.js +0 -14
  96. package/bin/commands/search.js +0 -344
  97. package/bin/commands/search.test.js +0 -115
  98. package/bin/commands/setup.js +0 -545
  99. package/bin/commands/setup.test.js +0 -46
  100. package/bin/commands/sync-profile.js +0 -405
  101. package/bin/commands/sync-profile.test.js +0 -14
  102. package/bin/commands/sync-source.js +0 -418
  103. package/bin/commands/sync-source.test.js +0 -14
  104. package/bin/lib/generators/claude-plugin.test.js +0 -111
  105. package/bin/lib/mcp-config.test.js +0 -310
  106. package/bin/lib/microsoft-mcp.test.js +0 -115
  107. package/bin/utils/errors.js +0 -159
  108. package/bin/utils/git.js +0 -298
  109. package/bin/utils/logger.js +0 -142
  110. package/bin/utils/mcp-detect.test.js +0 -81
  111. package/bin/utils/pbix.js +0 -305
  112. package/bin/utils/pbix.test.js +0 -37
  113. package/bin/utils/profiles.js +0 -312
  114. package/bin/utils/projects.js +0 -169
  115. package/bin/utils/readline.js +0 -206
  116. package/bin/utils/readline.test.js +0 -47
  117. package/bin/utils/tui.test.js +0 -127
  118. package/docs/openrouter-free-models.md +0 -92
  119. package/library/examples/README.md +0 -151
  120. package/library/examples/finance-reporting/README.md +0 -351
  121. package/library/examples/finance-reporting/data-model.md +0 -267
  122. package/library/examples/finance-reporting/measures.dax +0 -557
  123. package/library/examples/hr-analytics/README.md +0 -371
  124. package/library/examples/hr-analytics/data-model.md +0 -315
  125. package/library/examples/hr-analytics/measures.dax +0 -460
  126. package/library/examples/marketing-analytics/README.md +0 -37
  127. package/library/examples/marketing-analytics/data-model.md +0 -62
  128. package/library/examples/marketing-analytics/measures.dax +0 -110
  129. package/library/examples/retail-analytics/README.md +0 -439
  130. package/library/examples/retail-analytics/data-model.md +0 -288
  131. package/library/examples/retail-analytics/measures.dax +0 -481
  132. package/library/examples/supply-chain/README.md +0 -37
  133. package/library/examples/supply-chain/data-model.md +0 -69
  134. package/library/examples/supply-chain/measures.dax +0 -77
  135. package/library/examples/udf-library/README.md +0 -228
  136. package/library/examples/udf-library/functions.dax +0 -571
  137. package/library/snippets/dax/README.md +0 -292
  138. package/library/snippets/dax/business-domains.md +0 -576
  139. package/library/snippets/dax/calculate-patterns.md +0 -276
  140. package/library/snippets/dax/calculation-groups.md +0 -489
  141. package/library/snippets/dax/error-handling.md +0 -495
  142. package/library/snippets/dax/iterators-and-aggregations.md +0 -474
  143. package/library/snippets/dax/kpis-and-metrics.md +0 -293
  144. package/library/snippets/dax/rankings-and-topn.md +0 -235
  145. package/library/snippets/dax/security-patterns.md +0 -413
  146. package/library/snippets/dax/text-and-formatting.md +0 -316
  147. package/library/snippets/dax/time-intelligence.md +0 -196
  148. package/library/snippets/dax/user-defined-functions.md +0 -477
  149. package/library/snippets/dax/virtual-tables.md +0 -546
  150. package/library/snippets/excel-formulas/README.md +0 -84
  151. package/library/snippets/excel-formulas/aggregations.md +0 -330
  152. package/library/snippets/excel-formulas/dates-and-times.md +0 -361
  153. package/library/snippets/excel-formulas/dynamic-arrays.md +0 -314
  154. package/library/snippets/excel-formulas/lookups.md +0 -169
  155. package/library/snippets/excel-formulas/text-functions.md +0 -363
  156. package/library/snippets/governance/naming-conventions.md +0 -97
  157. package/library/snippets/governance/review-checklists.md +0 -107
  158. package/library/snippets/power-query/README.md +0 -389
  159. package/library/snippets/power-query/api-integration.md +0 -707
  160. package/library/snippets/power-query/connections.md +0 -434
  161. package/library/snippets/power-query/data-cleaning.md +0 -298
  162. package/library/snippets/power-query/error-handling.md +0 -526
  163. package/library/snippets/power-query/parameters.md +0 -350
  164. package/library/snippets/power-query/performance.md +0 -506
  165. package/library/snippets/power-query/transformations.md +0 -330
  166. package/library/snippets/report-design/accessibility.md +0 -78
  167. package/library/snippets/report-design/chart-selection.md +0 -54
  168. package/library/snippets/report-design/layout-patterns.md +0 -87
  169. package/library/templates/data-models/README.md +0 -93
  170. package/library/templates/data-models/finance-model.md +0 -627
  171. package/library/templates/data-models/retail-star-schema.md +0 -473
  172. package/library/templates/excel/README.md +0 -83
  173. package/library/templates/excel/budget-tracker.md +0 -432
  174. package/library/templates/excel/data-entry-form.md +0 -533
  175. package/library/templates/power-bi/README.md +0 -72
  176. package/library/templates/power-bi/finance-report.md +0 -449
  177. package/library/templates/power-bi/kpi-scorecard.md +0 -461
  178. package/library/templates/power-bi/sales-dashboard.md +0 -281
  179. package/library/themes/excel/README.md +0 -436
  180. package/library/themes/power-bi/README.md +0 -271
  181. package/library/themes/power-bi/accessible.json +0 -307
  182. package/library/themes/power-bi/bi-superpowers-default.json +0 -858
  183. package/library/themes/power-bi/corporate-blue.json +0 -291
  184. package/library/themes/power-bi/dark-mode.json +0 -291
  185. package/library/themes/power-bi/minimal.json +0 -292
  186. package/library/themes/power-bi/print-friendly.json +0 -309
@@ -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 all 24 skill sources into temp/skills/
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
- /** Skill source files directory */
29
- const SKILLS_SOURCE_DIR = path.join(PACKAGE_DIR, 'src', 'content', 'skills');
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
- module.exports = function buildDesktop(_args, _config) {
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
- if (!fs.existsSync(SKILLS_SOURCE_DIR)) {
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
- // Copy all skill .md files into temp/skills/
75
- const skillsOutDir = path.join(tmpDir, 'skills');
76
- fs.mkdirSync(skillsOutDir, { recursive: true });
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/Fabric MCP configuration.
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;
@@ -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
- return path.join(targetDir, '.claude', 'commands', `${skillName}.md`);
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
- const skillName = options.skill.replace('.md', '');
257
- const skillPath = path.join(skillsDir, `${skillName}.md`);
258
- if (fs.existsSync(skillPath)) {
259
- skillFiles = [skillPath];
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 = fs
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
- module.exports = diffCommand;
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
+ });
@@ -1,18 +1,28 @@
1
1
  /**
2
- * Install Command - Multi-agent skill installer
3
- * ===============================================
2
+ * Install Command Multi-agent skill + MCP installer
3
+ * =====================================================
4
4
  *
5
- * Instala los skills de BI Agent Superpowers en los directorios
6
- * correctos para cada agente AI. Inspirado en el CLI `npx skills` de
7
- * Vercel Labs.
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
- * Los skills siempre se instalan a nivel de usuario (~/) para no
10
- * contaminar el repo del proyecto. El usuario los obtiene una vez
11
- * y los comparte con todos sus proyectos.
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
- * Open source: todo el contenido ship gratis con el paquete npm.
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
- * Uso:
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
- * Detecta qué agentes están instalados revisando sus directorios de config.
33
- * @param {string} baseDir - Directorio base (home del usuario)
34
- * @returns {string[]} IDs de agentes detectados
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
- * Crea una interface de readline para prompts interactivos.
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
- * Envuelve una pregunta de readline en una Promise.
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
- * Muestra una lista numerada y deja al usuario elegir múltiples items.
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 preseleccionados
72
- * @returns {Promise<string[]>} IDs seleccionados
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(' Ingresa números separados por comas (ej: 1,2,3)');
81
- console.log(' Presiona Enter para los detectados, o "a" para todos');
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
- * Copia un directorio de skill recursivamente.
103
- * Los errores de fs se propagan al caller para manejo centralizado.
104
- * @param {string} srcDir - Directorio de skill fuente (contiene SKILL.md)
105
- * @param {string} destDir - Directorio destino
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
- * Formatea un error de filesystem con hint útil según el código.
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. Revisa los permisos del directorio.',
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
- * Parsea las flags de CLI y devuelve un objeto de opciones.
142
- * Valida que cada --agent/-a tenga un valor asociado.
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
- // Falta valor para --agent; avisamos y seguimos sin ese flag
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
- * Resuelve qué agentes instalar según las flags y el modo interactivo.
169
- * @returns {Promise<string[]>} IDs de agentes seleccionados
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
- // Modo interactivo
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
- * Instala los skills en los directorios seleccionados.
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
- // Siempre instalamos primero en el path universal
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; // Ya manejado por el universal
240
+ if (agent.dir === UNIVERSAL_DIR) continue; // Already handled above.
227
241
 
228
242
  const agentTarget = path.join(baseDir, agent.dir);
229
243
 
230
- // Si ya existe y no es symlink, copiamos ahí directamente
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
- // Eliminar symlink existente si hay uno (para poder recrearlo)
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
- /* no existe, seguimos */
264
+ /* target didn't exist after all — that's fine */
250
265
  }
251
266
  }
252
267
 
253
268
  try {
254
- // Symlink relativo del dir del agente al dir universal
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
- // Fallback a copia si el symlink falla (ej: Windows sin permisos)
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
- * Configura los 2 MCP servers (powerbi-modeling + microsoft-learn)
281
- * para cada agente seleccionado escribiendo el config file en el path
282
- * y formato que cada agente espera.
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
- * Los errores por agente no interrumpen el flujo: se recolectan en los
285
- * resultados para mostrarlos al final y que el caller decida qué hacer
286
- * con el exit code.
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 de agentes seleccionados
289
- * @param {string} packageDir - Absolute path al paquete instalado
290
- * @param {string} baseDir - Home directory del usuario (para display)
291
- * @param {Object} chalk - chalk instance para colorear output
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
- * Handler principal del comando install.
319
- * @param {string[]} args - Argumentos CLI
320
- * @param {Object} config - Config del comando desde el CLI
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
- // Siempre a nivel usuario (home) para proteger contenido licenciado
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
- // Localizar los skills del paquete
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
- // Leer skills disponibles
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
- // Resolver qué agentes instalar
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
- // Instalar skills
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
- // Universal skills path
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
- // Skills por agente
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
- // Aviso si hubo fallbacks de symlink a copia
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
- // Configurar MCPs para cada agente seleccionado
443
+ // Phase 2: write the 2 MCP configs per agent.
427
444
  const mcpResults = configureMcpsForAgents(selectedAgents, packageDir, baseDir, chalk);
428
445
 
429
- // Resumen
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 2 skills disponibles:') +
473
+ chalk.gray('Los 4 skills disponibles:') +
457
474
  '\n' +
458
- chalk.gray(' /project-kickoff Analizá tu proyecto BI') +
475
+ chalk.gray(' /bi-start Arrancar una sesión: menú + update + conexión') +
459
476
  '\n' +
460
- chalk.gray(' /pbi-connect Conectá Claude a Power BI Desktop'),
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, but skills
472
- // still got installed we use exit code 2 to distinguish from total
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
- // Exports internos para testing
499
+ // Internal exports for testing.
479
500
  module.exports = installCommand;
480
501
  module.exports.parseArgs = parseArgs;
481
502
  module.exports.detectAgents = detectAgents;