@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
|
@@ -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
|
-
});
|
package/bin/commands/push.js
DELETED
|
@@ -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
|
-
});
|
package/bin/commands/search.js
DELETED
|
@@ -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;
|