@luquimbo/bi-superpowers 3.1.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/.claude-plugin/marketplace.json +5 -3
  2. package/.claude-plugin/plugin.json +28 -2
  3. package/.claude-plugin/skill-manifest.json +22 -6
  4. package/.plugin/plugin.json +1 -1
  5. package/AGENTS.md +52 -36
  6. package/CHANGELOG.md +295 -0
  7. package/README.md +75 -26
  8. package/bin/build-plugin.js +17 -10
  9. package/bin/cli.js +278 -322
  10. package/bin/commands/build-desktop.js +35 -16
  11. package/bin/commands/diff.js +31 -13
  12. package/bin/commands/install.js +93 -72
  13. package/bin/commands/lint.js +40 -26
  14. package/bin/commands/mcp-setup.js +3 -10
  15. package/bin/commands/update-check.js +389 -0
  16. package/bin/lib/agents.js +19 -0
  17. package/bin/lib/generators/claude-plugin.js +144 -6
  18. package/bin/lib/generators/shared.js +29 -33
  19. package/bin/lib/mcp-config.js +191 -16
  20. package/bin/lib/skills.js +115 -27
  21. package/bin/postinstall.js +4 -2
  22. package/bin/utils/mcp-detect.js +2 -2
  23. package/commands/bi-start.md +218 -0
  24. package/commands/pbi-connect.md +43 -65
  25. package/commands/project-kickoff.md +393 -673
  26. package/commands/report-design.md +403 -0
  27. package/desktop-extension/manifest.json +5 -12
  28. package/desktop-extension/server.js +34 -25
  29. package/package.json +6 -10
  30. package/skills/bi-start/SKILL.md +220 -0
  31. package/skills/bi-start/scripts/update-check.js +389 -0
  32. package/skills/pbi-connect/SKILL.md +45 -67
  33. package/skills/pbi-connect/scripts/update-check.js +389 -0
  34. package/skills/project-kickoff/SKILL.md +395 -675
  35. package/skills/project-kickoff/scripts/update-check.js +389 -0
  36. package/skills/report-design/SKILL.md +405 -0
  37. package/skills/report-design/references/cli-commands.md +184 -0
  38. package/skills/report-design/references/cli-setup.md +101 -0
  39. package/skills/report-design/references/close-write-open-pattern.md +80 -0
  40. package/skills/report-design/references/layouts/finance.md +65 -0
  41. package/skills/report-design/references/layouts/generic.md +46 -0
  42. package/skills/report-design/references/layouts/hr.md +48 -0
  43. package/skills/report-design/references/layouts/marketing.md +45 -0
  44. package/skills/report-design/references/layouts/operations.md +44 -0
  45. package/skills/report-design/references/layouts/sales.md +50 -0
  46. package/skills/report-design/references/native-visuals.md +341 -0
  47. package/skills/report-design/references/pbi-desktop-installation.md +87 -0
  48. package/skills/report-design/references/pbir-preview-activation.md +40 -0
  49. package/skills/report-design/references/slicer.md +89 -0
  50. package/skills/report-design/references/textbox.md +101 -0
  51. package/skills/report-design/references/themes/BISuperpowers.json +915 -0
  52. package/skills/report-design/references/troubleshooting.md +135 -0
  53. package/skills/report-design/references/visual-types.md +78 -0
  54. package/skills/report-design/scripts/apply-theme.js +243 -0
  55. package/skills/report-design/scripts/create-visual.js +878 -0
  56. package/skills/report-design/scripts/ensure-pbi-cli.sh +41 -0
  57. package/skills/report-design/scripts/update-check.js +389 -0
  58. package/skills/report-design/scripts/validate-pbir.js +322 -0
  59. package/src/content/base.md +12 -68
  60. package/src/content/mcp-requirements.json +0 -25
  61. package/src/content/routing.md +19 -74
  62. package/src/content/skills/bi-start.md +191 -0
  63. package/src/content/skills/pbi-connect.md +22 -65
  64. package/src/content/skills/project-kickoff.md +372 -673
  65. package/src/content/skills/report-design/SKILL.md +376 -0
  66. package/src/content/skills/report-design/references/cli-commands.md +184 -0
  67. package/src/content/skills/report-design/references/cli-setup.md +101 -0
  68. package/src/content/skills/report-design/references/close-write-open-pattern.md +80 -0
  69. package/src/content/skills/report-design/references/layouts/finance.md +65 -0
  70. package/src/content/skills/report-design/references/layouts/generic.md +46 -0
  71. package/src/content/skills/report-design/references/layouts/hr.md +48 -0
  72. package/src/content/skills/report-design/references/layouts/marketing.md +45 -0
  73. package/src/content/skills/report-design/references/layouts/operations.md +44 -0
  74. package/src/content/skills/report-design/references/layouts/sales.md +50 -0
  75. package/src/content/skills/report-design/references/native-visuals.md +341 -0
  76. package/src/content/skills/report-design/references/pbi-desktop-installation.md +87 -0
  77. package/src/content/skills/report-design/references/pbir-preview-activation.md +40 -0
  78. package/src/content/skills/report-design/references/slicer.md +89 -0
  79. package/src/content/skills/report-design/references/textbox.md +101 -0
  80. package/src/content/skills/report-design/references/themes/BISuperpowers.json +915 -0
  81. package/src/content/skills/report-design/references/troubleshooting.md +135 -0
  82. package/src/content/skills/report-design/references/visual-types.md +78 -0
  83. package/src/content/skills/report-design/scripts/apply-theme.js +243 -0
  84. package/src/content/skills/report-design/scripts/create-visual.js +878 -0
  85. package/src/content/skills/report-design/scripts/ensure-pbi-cli.sh +41 -0
  86. package/src/content/skills/report-design/scripts/validate-pbir.js +322 -0
  87. package/bin/commands/add.js +0 -533
  88. package/bin/commands/add.test.js +0 -77
  89. package/bin/commands/changelog.js +0 -443
  90. package/bin/commands/install.test.js +0 -289
  91. package/bin/commands/lint.test.js +0 -103
  92. package/bin/commands/pull.js +0 -287
  93. package/bin/commands/pull.test.js +0 -36
  94. package/bin/commands/push.js +0 -231
  95. package/bin/commands/push.test.js +0 -14
  96. package/bin/commands/search.js +0 -344
  97. package/bin/commands/search.test.js +0 -115
  98. package/bin/commands/setup.js +0 -545
  99. package/bin/commands/setup.test.js +0 -46
  100. package/bin/commands/sync-profile.js +0 -405
  101. package/bin/commands/sync-profile.test.js +0 -14
  102. package/bin/commands/sync-source.js +0 -418
  103. package/bin/commands/sync-source.test.js +0 -14
  104. package/bin/lib/generators/claude-plugin.test.js +0 -111
  105. package/bin/lib/mcp-config.test.js +0 -310
  106. package/bin/lib/microsoft-mcp.test.js +0 -115
  107. package/bin/utils/errors.js +0 -159
  108. package/bin/utils/git.js +0 -298
  109. package/bin/utils/logger.js +0 -142
  110. package/bin/utils/mcp-detect.test.js +0 -81
  111. package/bin/utils/pbix.js +0 -305
  112. package/bin/utils/pbix.test.js +0 -37
  113. package/bin/utils/profiles.js +0 -312
  114. package/bin/utils/projects.js +0 -169
  115. package/bin/utils/readline.js +0 -206
  116. package/bin/utils/readline.test.js +0 -47
  117. package/bin/utils/tui.test.js +0 -127
  118. package/docs/openrouter-free-models.md +0 -92
  119. package/library/examples/README.md +0 -151
  120. package/library/examples/finance-reporting/README.md +0 -351
  121. package/library/examples/finance-reporting/data-model.md +0 -267
  122. package/library/examples/finance-reporting/measures.dax +0 -557
  123. package/library/examples/hr-analytics/README.md +0 -371
  124. package/library/examples/hr-analytics/data-model.md +0 -315
  125. package/library/examples/hr-analytics/measures.dax +0 -460
  126. package/library/examples/marketing-analytics/README.md +0 -37
  127. package/library/examples/marketing-analytics/data-model.md +0 -62
  128. package/library/examples/marketing-analytics/measures.dax +0 -110
  129. package/library/examples/retail-analytics/README.md +0 -439
  130. package/library/examples/retail-analytics/data-model.md +0 -288
  131. package/library/examples/retail-analytics/measures.dax +0 -481
  132. package/library/examples/supply-chain/README.md +0 -37
  133. package/library/examples/supply-chain/data-model.md +0 -69
  134. package/library/examples/supply-chain/measures.dax +0 -77
  135. package/library/examples/udf-library/README.md +0 -228
  136. package/library/examples/udf-library/functions.dax +0 -571
  137. package/library/snippets/dax/README.md +0 -292
  138. package/library/snippets/dax/business-domains.md +0 -576
  139. package/library/snippets/dax/calculate-patterns.md +0 -276
  140. package/library/snippets/dax/calculation-groups.md +0 -489
  141. package/library/snippets/dax/error-handling.md +0 -495
  142. package/library/snippets/dax/iterators-and-aggregations.md +0 -474
  143. package/library/snippets/dax/kpis-and-metrics.md +0 -293
  144. package/library/snippets/dax/rankings-and-topn.md +0 -235
  145. package/library/snippets/dax/security-patterns.md +0 -413
  146. package/library/snippets/dax/text-and-formatting.md +0 -316
  147. package/library/snippets/dax/time-intelligence.md +0 -196
  148. package/library/snippets/dax/user-defined-functions.md +0 -477
  149. package/library/snippets/dax/virtual-tables.md +0 -546
  150. package/library/snippets/excel-formulas/README.md +0 -84
  151. package/library/snippets/excel-formulas/aggregations.md +0 -330
  152. package/library/snippets/excel-formulas/dates-and-times.md +0 -361
  153. package/library/snippets/excel-formulas/dynamic-arrays.md +0 -314
  154. package/library/snippets/excel-formulas/lookups.md +0 -169
  155. package/library/snippets/excel-formulas/text-functions.md +0 -363
  156. package/library/snippets/governance/naming-conventions.md +0 -97
  157. package/library/snippets/governance/review-checklists.md +0 -107
  158. package/library/snippets/power-query/README.md +0 -389
  159. package/library/snippets/power-query/api-integration.md +0 -707
  160. package/library/snippets/power-query/connections.md +0 -434
  161. package/library/snippets/power-query/data-cleaning.md +0 -298
  162. package/library/snippets/power-query/error-handling.md +0 -526
  163. package/library/snippets/power-query/parameters.md +0 -350
  164. package/library/snippets/power-query/performance.md +0 -506
  165. package/library/snippets/power-query/transformations.md +0 -330
  166. package/library/snippets/report-design/accessibility.md +0 -78
  167. package/library/snippets/report-design/chart-selection.md +0 -54
  168. package/library/snippets/report-design/layout-patterns.md +0 -87
  169. package/library/templates/data-models/README.md +0 -93
  170. package/library/templates/data-models/finance-model.md +0 -627
  171. package/library/templates/data-models/retail-star-schema.md +0 -473
  172. package/library/templates/excel/README.md +0 -83
  173. package/library/templates/excel/budget-tracker.md +0 -432
  174. package/library/templates/excel/data-entry-form.md +0 -533
  175. package/library/templates/power-bi/README.md +0 -72
  176. package/library/templates/power-bi/finance-report.md +0 -449
  177. package/library/templates/power-bi/kpi-scorecard.md +0 -461
  178. package/library/templates/power-bi/sales-dashboard.md +0 -281
  179. package/library/themes/excel/README.md +0 -436
  180. package/library/themes/power-bi/README.md +0 -271
  181. package/library/themes/power-bi/accessible.json +0 -307
  182. package/library/themes/power-bi/bi-superpowers-default.json +0 -858
  183. package/library/themes/power-bi/corporate-blue.json +0 -291
  184. package/library/themes/power-bi/dark-mode.json +0 -291
  185. package/library/themes/power-bi/minimal.json +0 -292
  186. package/library/themes/power-bi/print-friendly.json +0 -309
@@ -1,36 +0,0 @@
1
- /**
2
- * Tests for Pull Command
3
- * @module commands/pull.test
4
- */
5
-
6
- const { test, describe } = require('node:test');
7
- const assert = require('node:assert');
8
-
9
- describe('Pull Command', () => {
10
- test('module exports a function', () => {
11
- const pullCommand = require('./pull');
12
- assert.strictEqual(typeof pullCommand, 'function');
13
- });
14
- });
15
-
16
- describe('Pull Command - Directory Comparison', () => {
17
- test('compareDirectories returns diff structure', () => {
18
- const pbix = require('../utils/pbix');
19
-
20
- // Test with non-existent directories (should return empty diff)
21
- const result = pbix.compareDirectories('/non/existent/dir1', '/non/existent/dir2');
22
-
23
- assert.ok(Array.isArray(result.added));
24
- assert.ok(Array.isArray(result.modified));
25
- assert.ok(Array.isArray(result.deleted));
26
- });
27
- });
28
-
29
- describe('Pull Command - File Hashing', () => {
30
- test('getFileHash returns empty string for non-existent file', () => {
31
- const pbix = require('../utils/pbix');
32
- const result = pbix.getFileHash('/non/existent/file.txt');
33
-
34
- assert.strictEqual(result, '');
35
- });
36
- });
@@ -1,231 +0,0 @@
1
- /**
2
- * Push Command - Apply Repo Changes to Original File
3
- * ====================================================
4
- *
5
- * Pushes changes from the repo back to the original BI file.
6
- * This is a Phase 2 feature - currently provides guidance for PBIP workflow.
7
- *
8
- * Usage:
9
- * super push Push current project
10
- * super push <project-name> Push specific project
11
- *
12
- * @module commands/push
13
- */
14
-
15
- const fs = require('fs');
16
- const path = require('path');
17
-
18
- const profiles = require('../utils/profiles');
19
- const pbix = require('../utils/pbix');
20
- const { getAllProjects, getProject, detectCurrentProject } = require('../utils/projects');
21
-
22
- /**
23
- * Parse command line arguments
24
- * @param {string[]} args - CLI arguments
25
- * @returns {Object} Parsed options
26
- */
27
- function parseArgs(args) {
28
- const options = {
29
- projectName: null,
30
- force: false,
31
- help: false,
32
- };
33
-
34
- for (let i = 0; i < args.length; i++) {
35
- const arg = args[i];
36
-
37
- if (arg === '--force' || arg === '-f') {
38
- options.force = true;
39
- } else if (arg === '--help' || arg === '-h') {
40
- options.help = true;
41
- } else if (!arg.startsWith('-') && !options.projectName) {
42
- options.projectName = arg;
43
- }
44
- }
45
-
46
- return options;
47
- }
48
-
49
- /**
50
- * Show help message
51
- */
52
- function showHelp() {
53
- console.log(`
54
- super push - Aplicar cambios del repo al archivo original
55
-
56
- Uso:
57
- super push Push del proyecto actual
58
- super push <nombre-proyecto> Push de un proyecto específico
59
-
60
- Opciones:
61
- --force, -f Forzar push aunque haya advertencias
62
- --help, -h Mostrar esta ayuda
63
-
64
- Nota:
65
- Este comando actualmente soporta proyectos PBIP.
66
- Para archivos .pbix, usa el workflow PBIP (recomendado).
67
-
68
- Ejemplos:
69
- super push sales-q4
70
- super push --force
71
- `);
72
- }
73
-
74
- // Note: getAllProjects, getProject, and detectCurrentProject are imported from ../utils/projects
75
-
76
- /**
77
- * Push changes for a PBIP project
78
- * @param {Object} project - Project config
79
- * @returns {Object} Result object
80
- */
81
- function pushPbipProject(project) {
82
- const result = {
83
- success: false,
84
- message: '',
85
- };
86
-
87
- const sourcePath = project.source?.path;
88
- if (!sourcePath) {
89
- result.message = 'No source path configured';
90
- return result;
91
- }
92
-
93
- const definitionPath = pbix.getPbipDefinitionPath(sourcePath);
94
- if (!definitionPath) {
95
- result.message = 'Could not find PBIP definition folder';
96
- return result;
97
- }
98
-
99
- const repoDefinition = path.join(project.projectPath, 'definition');
100
- if (!fs.existsSync(repoDefinition)) {
101
- result.message = 'No definition folder in repo';
102
- return result;
103
- }
104
-
105
- try {
106
- // Copy files from repo to PBIP project
107
- const files = pbix.copyDirectoryRecursive(repoDefinition, definitionPath);
108
-
109
- // Update project.json with sync time
110
- project.source.lastSync = new Date().toISOString();
111
- const configPath = path.join(project.projectPath, 'project.json');
112
- fs.writeFileSync(configPath, JSON.stringify(project, null, 2));
113
-
114
- result.success = true;
115
- result.message = `Pushed ${files.length} files to PBIP project`;
116
- } catch (e) {
117
- result.message = `Error: ${e.message}`;
118
- }
119
-
120
- return result;
121
- }
122
-
123
- /**
124
- * Main push command handler
125
- */
126
- async function pushCommand(args, _config) {
127
- const options = parseArgs(args);
128
-
129
- if (options.help) {
130
- showHelp();
131
- return;
132
- }
133
-
134
- // Check if repo exists
135
- const repoPath = profiles.getRepoPath();
136
- if (!repoPath || !fs.existsSync(repoPath)) {
137
- console.log(`
138
- No se encontró el repositorio de BI.
139
-
140
- Ejecuta primero:
141
- super setup
142
- `);
143
- process.exit(1);
144
- }
145
-
146
- console.log(`
147
- ════════════════════════════════════════════════════════════════
148
- Push - Aplicar cambios del repo al archivo original
149
- ════════════════════════════════════════════════════════════════
150
- `);
151
-
152
- let project;
153
-
154
- if (options.projectName) {
155
- project = getProject(repoPath, options.projectName);
156
- if (!project) {
157
- console.log(`✗ Proyecto no encontrado: ${options.projectName}`);
158
- console.log('\nProyectos disponibles:');
159
- getAllProjects(repoPath).forEach((p) => {
160
- console.log(` - ${p.name}`);
161
- });
162
- process.exit(1);
163
- }
164
- } else {
165
- project = detectCurrentProject(repoPath);
166
- if (!project) {
167
- console.log('Especifica un proyecto:\n');
168
- console.log(' super push <nombre-proyecto>\n');
169
- console.log('Proyectos disponibles:');
170
- getAllProjects(repoPath).forEach((p) => {
171
- console.log(` - ${p.name}`);
172
- });
173
- return;
174
- }
175
- }
176
-
177
- console.log(`Proyecto: ${project.name}`);
178
- console.log(`Tipo: ${project.type}`);
179
- console.log(`Archivo original: ${project.source?.path || 'No configurado'}\n`);
180
-
181
- // Handle based on project type
182
- if (project.type === 'power-bi-project') {
183
- // PBIP - we can push directly
184
- const result = pushPbipProject(project);
185
-
186
- if (result.success) {
187
- console.log(`✓ ${result.message}`);
188
- console.log(`
189
- Los cambios se han aplicado al proyecto PBIP.
190
- Abre Power BI Desktop y recarga el proyecto para ver los cambios.
191
- `);
192
- } else {
193
- console.log(`✗ ${result.message}`);
194
- }
195
- } else if (project.type === 'power-bi') {
196
- // PBIX - binary file, cannot push directly
197
- console.log(`
198
- ⚠ Los archivos .pbix son binarios y no se pueden modificar directamente.
199
-
200
- Para aplicar cambios del repo al modelo, tienes estas opciones:
201
-
202
- OPCIÓN 1: Convertir a PBIP (Recomendado)
203
- 1. Abre el .pbix en Power BI Desktop
204
- 2. File → Save as → Power BI Project (.pbip)
205
- 3. Ejecuta: super add "ruta/al/proyecto.pbip"
206
- 4. Los cambios del repo se pueden aplicar con: super push
207
-
208
- OPCIÓN 2: Copiar manualmente
209
- 1. Abre el .pbix en Power BI Desktop
210
- 2. Copia las medidas/queries del repo manualmente
211
- 3. Guarda el archivo
212
-
213
- Los archivos del repo están en:
214
- ${project.projectPath}
215
- `);
216
- } else if (project.type === 'excel' || project.type === 'excel-macro') {
217
- console.log(`
218
- ⚠ Los archivos Excel no se pueden modificar automáticamente.
219
-
220
- La documentación del workbook está en:
221
- ${path.join(project.projectPath, 'workbook')}
222
-
223
- Usa esta documentación como referencia para aplicar
224
- cambios manualmente en Excel.
225
- `);
226
- } else {
227
- console.log(`✗ Tipo de proyecto no soportado para push: ${project.type}`);
228
- }
229
- }
230
-
231
- module.exports = pushCommand;
@@ -1,14 +0,0 @@
1
- /**
2
- * Tests for Push Command
3
- * @module commands/push.test
4
- */
5
-
6
- const { test, describe } = require('node:test');
7
- const assert = require('node:assert');
8
-
9
- describe('Push Command', () => {
10
- test('module exports a function', () => {
11
- const pushCommand = require('./push');
12
- assert.strictEqual(typeof pushCommand, 'function');
13
- });
14
- });
@@ -1,344 +0,0 @@
1
- /**
2
- * Search Command (xray)
3
- * =====================
4
- * Fuzzy search across snippets, skills, and library content.
5
- *
6
- * Usage:
7
- * super xray "query"
8
- * super xray --category dax "query"
9
- * super xray --tag performance
10
- */
11
-
12
- const fs = require('fs');
13
- const path = require('path');
14
- const Fuse = require('fuse.js');
15
- const tui = require('../utils/tui');
16
-
17
- // Fuse.js configuration for fuzzy search
18
- const FUSE_OPTIONS = {
19
- keys: [
20
- { name: 'title', weight: 0.4 },
21
- { name: 'content', weight: 0.3 },
22
- { name: 'category', weight: 0.2 },
23
- { name: 'tags', weight: 0.1 },
24
- ],
25
- threshold: 0.4,
26
- ignoreLocation: true,
27
- includeScore: true,
28
- includeMatches: true,
29
- };
30
-
31
- /**
32
- * Build search index from library content
33
- * @param {string} libraryDir - Path to library directory
34
- * @returns {Object[]} Array of searchable items
35
- */
36
- function buildSearchIndex(libraryDir) {
37
- const items = [];
38
-
39
- if (!fs.existsSync(libraryDir)) {
40
- return items;
41
- }
42
-
43
- // Index snippets
44
- const snippetsDir = path.join(libraryDir, 'snippets');
45
- if (fs.existsSync(snippetsDir)) {
46
- indexDirectory(snippetsDir, items, 'snippet');
47
- }
48
-
49
- // Index templates
50
- const templatesDir = path.join(libraryDir, 'templates');
51
- if (fs.existsSync(templatesDir)) {
52
- indexDirectory(templatesDir, items, 'template');
53
- }
54
-
55
- // Index examples
56
- const examplesDir = path.join(libraryDir, 'examples');
57
- if (fs.existsSync(examplesDir)) {
58
- indexDirectory(examplesDir, items, 'example');
59
- }
60
-
61
- return items;
62
- }
63
-
64
- /**
65
- * Recursively index a directory
66
- * @param {string} dir - Directory path
67
- * @param {Object[]} items - Items array to populate
68
- * @param {string} type - Content type
69
- */
70
- function indexDirectory(dir, items, type) {
71
- const entries = fs.readdirSync(dir, { withFileTypes: true });
72
-
73
- for (const entry of entries) {
74
- const fullPath = path.join(dir, entry.name);
75
-
76
- if (entry.isDirectory()) {
77
- indexDirectory(fullPath, items, type);
78
- } else if (entry.name.endsWith('.md') && entry.name !== 'README.md') {
79
- const item = parseMarkdownFile(fullPath, type);
80
- if (item) {
81
- items.push(item);
82
- }
83
- }
84
- }
85
- }
86
-
87
- /**
88
- * Parse a markdown file for search indexing
89
- * @param {string} filePath - File path
90
- * @param {string} type - Content type
91
- * @returns {Object|null} Parsed item or null
92
- */
93
- function parseMarkdownFile(filePath, type) {
94
- try {
95
- const content = fs.readFileSync(filePath, 'utf8');
96
- const lines = content.split('\n');
97
-
98
- // Extract title from first H1 or filename
99
- let title = path.basename(filePath, '.md');
100
- const h1Match = content.match(/^#\s+(.+)/m);
101
- if (h1Match) {
102
- title = h1Match[1];
103
- }
104
-
105
- // Extract category from parent directory
106
- const pathParts = filePath.split(path.sep);
107
- const snippetsIndex = pathParts.indexOf('snippets');
108
- const templatesIndex = pathParts.indexOf('templates');
109
- const examplesIndex = pathParts.indexOf('examples');
110
-
111
- let category = '';
112
- const typeIndex = Math.max(snippetsIndex, templatesIndex, examplesIndex);
113
- if (typeIndex !== -1 && typeIndex + 1 < pathParts.length) {
114
- category = pathParts[typeIndex + 1];
115
- }
116
-
117
- // Extract tags from content (look for patterns like `tag`, **tag**, or explicit tags)
118
- const tags = [];
119
- const tagMatches = content.match(/`([^`]+)`/g);
120
- if (tagMatches) {
121
- tagMatches.slice(0, 10).forEach((match) => {
122
- const tag = match.replace(/`/g, '').toLowerCase();
123
- if (tag.length > 2 && tag.length < 30 && !tags.includes(tag)) {
124
- tags.push(tag);
125
- }
126
- });
127
- }
128
-
129
- // Extract first paragraph as preview
130
- let preview = '';
131
- for (let i = 0; i < lines.length && i < 20; i++) {
132
- const line = lines[i].trim();
133
- if (line && !line.startsWith('#') && !line.startsWith('```') && !line.startsWith('|')) {
134
- preview = line;
135
- break;
136
- }
137
- }
138
-
139
- return {
140
- title,
141
- path: filePath,
142
- relativePath: filePath.split('library')[1] || filePath,
143
- content: content.substring(0, 2000), // Index first 2000 chars
144
- category,
145
- type,
146
- tags,
147
- preview,
148
- };
149
- } catch (e) {
150
- return null;
151
- }
152
- }
153
-
154
- /**
155
- * Filter results by category
156
- * @param {Object[]} results - Search results
157
- * @param {string} category - Category filter
158
- * @returns {Object[]} Filtered results
159
- */
160
- function filterByCategory(results, category) {
161
- const lowerCategory = category.toLowerCase();
162
- return results.filter(
163
- (r) =>
164
- r.item.category.toLowerCase().includes(lowerCategory) ||
165
- r.item.type.toLowerCase().includes(lowerCategory)
166
- );
167
- }
168
-
169
- /**
170
- * Filter results by tag
171
- * @param {Object[]} results - Search results
172
- * @param {string} tag - Tag filter
173
- * @returns {Object[]} Filtered results
174
- */
175
- function filterByTag(results, tag) {
176
- const lowerTag = tag.toLowerCase();
177
- return results.filter(
178
- (r) =>
179
- r.item.tags.some((t) => t.toLowerCase().includes(lowerTag)) ||
180
- r.item.content.toLowerCase().includes(lowerTag)
181
- );
182
- }
183
-
184
- /**
185
- * Display search results
186
- * @param {Object[]} results - Search results from Fuse.js
187
- * @param {string} query - Original search query
188
- */
189
- function displayResults(results, query) {
190
- if (results.length === 0) {
191
- tui.warning(`No results found for "${query}"`);
192
- console.log(
193
- tui.colors.muted(
194
- '\nTry a different search term or check available content with: super powers'
195
- )
196
- );
197
- return;
198
- }
199
-
200
- tui.section(`Found ${results.length} result${results.length > 1 ? 's' : ''} for "${query}"`);
201
-
202
- // Group by category
203
- const grouped = {};
204
- results.forEach((result) => {
205
- const cat = result.item.category || result.item.type || 'other';
206
- if (!grouped[cat]) {
207
- grouped[cat] = [];
208
- }
209
- grouped[cat].push(result);
210
- });
211
-
212
- // Display grouped results
213
- Object.entries(grouped).forEach(([category, categoryResults]) => {
214
- console.log(`\n${tui.colors.primary(category.toUpperCase())}`);
215
-
216
- categoryResults.slice(0, 5).forEach((result) => {
217
- const score = (1 - result.score) * 100;
218
- const scoreColor =
219
- score > 70 ? tui.colors.success : score > 40 ? tui.colors.warning : tui.colors.muted;
220
-
221
- console.log(` ${tui.icons.bullet} ${tui.colors.highlight(result.item.title)}`);
222
- console.log(` ${tui.colors.muted('Path:')} ${tui.formatPath(result.item.relativePath)}`);
223
-
224
- if (result.item.preview) {
225
- console.log(` ${tui.colors.muted(tui.truncate(result.item.preview, 80))}`);
226
- }
227
-
228
- console.log(` ${tui.colors.muted('Match:')} ${scoreColor(score.toFixed(0) + '%')}`);
229
- });
230
-
231
- if (categoryResults.length > 5) {
232
- console.log(tui.colors.muted(` ... and ${categoryResults.length - 5} more`));
233
- }
234
- });
235
-
236
- console.log('');
237
- }
238
-
239
- /**
240
- * Parse command line arguments
241
- * @param {string[]} args - CLI arguments
242
- * @returns {Object} Parsed options
243
- */
244
- function parseArgs(args) {
245
- const options = {
246
- query: '',
247
- category: null,
248
- tag: null,
249
- limit: 20,
250
- };
251
-
252
- let i = 0;
253
- while (i < args.length) {
254
- const arg = args[i];
255
-
256
- if (arg === '--category' || arg === '-c') {
257
- options.category = args[++i];
258
- } else if (arg === '--tag' || arg === '-t') {
259
- options.tag = args[++i];
260
- } else if (arg === '--limit' || arg === '-l') {
261
- options.limit = parseInt(args[++i], 10) || 20;
262
- } else if (!arg.startsWith('-')) {
263
- options.query = arg;
264
- }
265
- i++;
266
- }
267
-
268
- return options;
269
- }
270
-
271
- /**
272
- * Main search command handler
273
- * @param {string[]} args - Command arguments
274
- * @param {Object} config - CLI configuration with paths
275
- */
276
- function searchCommand(args, config) {
277
- const options = parseArgs(args);
278
-
279
- if (!options.query && !options.category && !options.tag) {
280
- tui.error('Please provide a search query');
281
- console.log('\nUsage:');
282
- console.log(' super xray "query" Search for a term');
283
- console.log(' super xray --category dax Filter by category');
284
- console.log(' super xray --tag performance Filter by tag');
285
- console.log('\nExamples:');
286
- console.log(' super xray "YTD"');
287
- console.log(' super xray --category dax "time intelligence"');
288
- console.log(' super xray --tag performance');
289
- process.exit(1);
290
- }
291
-
292
- // Try cache directory first, fall back to local library
293
- let libraryDir = config.libraryDir;
294
- const localLibrary = path.join(config.packageDir, 'library');
295
-
296
- if (!fs.existsSync(libraryDir)) {
297
- if (fs.existsSync(localLibrary)) {
298
- libraryDir = localLibrary;
299
- } else {
300
- tui.error('Library not found. Try reinstalling: npm install -g @luquimbo/bi-superpowers');
301
- process.exit(1);
302
- }
303
- }
304
-
305
- tui.header('BI Agent Superpowers', 'Search Library');
306
-
307
- // Build search index
308
- const items = buildSearchIndex(libraryDir);
309
-
310
- if (items.length === 0) {
311
- tui.warning('No content found in library');
312
- return;
313
- }
314
-
315
- tui.info(`Indexed ${items.length} items`);
316
-
317
- // Perform search
318
- const fuse = new Fuse(items, FUSE_OPTIONS);
319
-
320
- let results;
321
- if (options.query) {
322
- results = fuse.search(options.query);
323
- } else {
324
- // If no query, return all items as "results"
325
- results = items.map((item) => ({ item, score: 0 }));
326
- }
327
-
328
- // Apply filters
329
- if (options.category) {
330
- results = filterByCategory(results, options.category);
331
- }
332
-
333
- if (options.tag) {
334
- results = filterByTag(results, options.tag);
335
- }
336
-
337
- // Limit results
338
- results = results.slice(0, options.limit);
339
-
340
- // Display results
341
- displayResults(results, options.query || `category:${options.category}` || `tag:${options.tag}`);
342
- }
343
-
344
- module.exports = searchCommand;