@luquimbo/bi-superpowers 1.0.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 (193) hide show
  1. package/.claude-plugin/plugin.json +8 -0
  2. package/.mcp.json +25 -0
  3. package/AGENTS.md +244 -0
  4. package/CHANGELOG.md +265 -0
  5. package/LICENSE +21 -0
  6. package/README.md +211 -0
  7. package/bin/build-plugin.js +30 -0
  8. package/bin/cli.js +1064 -0
  9. package/bin/commands/add.js +533 -0
  10. package/bin/commands/add.test.js +77 -0
  11. package/bin/commands/build-desktop.js +166 -0
  12. package/bin/commands/changelog.js +443 -0
  13. package/bin/commands/diff.js +325 -0
  14. package/bin/commands/lint.js +419 -0
  15. package/bin/commands/lint.test.js +103 -0
  16. package/bin/commands/mcp-setup.js +246 -0
  17. package/bin/commands/pull.js +287 -0
  18. package/bin/commands/pull.test.js +36 -0
  19. package/bin/commands/push.js +231 -0
  20. package/bin/commands/push.test.js +14 -0
  21. package/bin/commands/search.js +344 -0
  22. package/bin/commands/search.test.js +115 -0
  23. package/bin/commands/setup.js +545 -0
  24. package/bin/commands/setup.test.js +46 -0
  25. package/bin/commands/sync-profile.js +405 -0
  26. package/bin/commands/sync-profile.test.js +14 -0
  27. package/bin/commands/sync-source.js +418 -0
  28. package/bin/commands/sync-source.test.js +14 -0
  29. package/bin/commands/watch.js +206 -0
  30. package/bin/lib/generators/claude-plugin.js +266 -0
  31. package/bin/lib/generators/claude-plugin.test.js +110 -0
  32. package/bin/lib/generators/index.js +116 -0
  33. package/bin/lib/generators/shared.js +282 -0
  34. package/bin/lib/licensing/index.js +35 -0
  35. package/bin/lib/licensing/storage.js +364 -0
  36. package/bin/lib/licensing/storage.test.js +55 -0
  37. package/bin/lib/licensing/validator.js +213 -0
  38. package/bin/lib/licensing/validator.test.js +137 -0
  39. package/bin/lib/microsoft-mcp.js +176 -0
  40. package/bin/lib/microsoft-mcp.test.js +106 -0
  41. package/bin/lib/skills.js +84 -0
  42. package/bin/mcp/powerbi-modeling-launcher.js +38 -0
  43. package/bin/postinstall.js +44 -0
  44. package/bin/utils/errors.js +159 -0
  45. package/bin/utils/git.js +298 -0
  46. package/bin/utils/logger.js +142 -0
  47. package/bin/utils/mcp-detect.js +274 -0
  48. package/bin/utils/mcp-detect.test.js +105 -0
  49. package/bin/utils/pbix.js +305 -0
  50. package/bin/utils/pbix.test.js +37 -0
  51. package/bin/utils/profiles.js +312 -0
  52. package/bin/utils/projects.js +168 -0
  53. package/bin/utils/readline.js +206 -0
  54. package/bin/utils/readline.test.js +47 -0
  55. package/bin/utils/tui.js +314 -0
  56. package/bin/utils/tui.test.js +127 -0
  57. package/commands/contributions.md +265 -0
  58. package/commands/data-model-design.md +468 -0
  59. package/commands/dax-doctor.md +248 -0
  60. package/commands/fabric-scripts.md +452 -0
  61. package/commands/migration-assistant.md +290 -0
  62. package/commands/model-documenter.md +242 -0
  63. package/commands/pbi-connect.md +239 -0
  64. package/commands/project-kickoff.md +905 -0
  65. package/commands/report-layout.md +296 -0
  66. package/commands/rls-design.md +533 -0
  67. package/commands/theme-tweaker.md +624 -0
  68. package/config.example.json +23 -0
  69. package/config.json +23 -0
  70. package/desktop-extension/manifest.json +37 -0
  71. package/desktop-extension/package.json +10 -0
  72. package/desktop-extension/server.js +95 -0
  73. package/docs/openrouter-free-models.md +92 -0
  74. package/library/examples/README.md +151 -0
  75. package/library/examples/finance-reporting/README.md +351 -0
  76. package/library/examples/finance-reporting/data-model.md +267 -0
  77. package/library/examples/finance-reporting/measures.dax +557 -0
  78. package/library/examples/hr-analytics/README.md +371 -0
  79. package/library/examples/hr-analytics/data-model.md +315 -0
  80. package/library/examples/hr-analytics/measures.dax +460 -0
  81. package/library/examples/marketing-analytics/README.md +37 -0
  82. package/library/examples/marketing-analytics/data-model.md +62 -0
  83. package/library/examples/marketing-analytics/measures.dax +110 -0
  84. package/library/examples/retail-analytics/README.md +439 -0
  85. package/library/examples/retail-analytics/data-model.md +288 -0
  86. package/library/examples/retail-analytics/measures.dax +481 -0
  87. package/library/examples/supply-chain/README.md +37 -0
  88. package/library/examples/supply-chain/data-model.md +69 -0
  89. package/library/examples/supply-chain/measures.dax +77 -0
  90. package/library/examples/udf-library/README.md +228 -0
  91. package/library/examples/udf-library/functions.dax +571 -0
  92. package/library/snippets/dax/README.md +292 -0
  93. package/library/snippets/dax/business-domains.md +576 -0
  94. package/library/snippets/dax/calculate-patterns.md +276 -0
  95. package/library/snippets/dax/calculation-groups.md +489 -0
  96. package/library/snippets/dax/error-handling.md +495 -0
  97. package/library/snippets/dax/iterators-and-aggregations.md +474 -0
  98. package/library/snippets/dax/kpis-and-metrics.md +293 -0
  99. package/library/snippets/dax/rankings-and-topn.md +235 -0
  100. package/library/snippets/dax/security-patterns.md +413 -0
  101. package/library/snippets/dax/text-and-formatting.md +316 -0
  102. package/library/snippets/dax/time-intelligence.md +196 -0
  103. package/library/snippets/dax/user-defined-functions.md +477 -0
  104. package/library/snippets/dax/virtual-tables.md +546 -0
  105. package/library/snippets/excel-formulas/README.md +84 -0
  106. package/library/snippets/excel-formulas/aggregations.md +330 -0
  107. package/library/snippets/excel-formulas/dates-and-times.md +361 -0
  108. package/library/snippets/excel-formulas/dynamic-arrays.md +314 -0
  109. package/library/snippets/excel-formulas/lookups.md +169 -0
  110. package/library/snippets/excel-formulas/text-functions.md +363 -0
  111. package/library/snippets/governance/naming-conventions.md +97 -0
  112. package/library/snippets/governance/review-checklists.md +107 -0
  113. package/library/snippets/power-query/README.md +389 -0
  114. package/library/snippets/power-query/api-integration.md +707 -0
  115. package/library/snippets/power-query/connections.md +434 -0
  116. package/library/snippets/power-query/data-cleaning.md +298 -0
  117. package/library/snippets/power-query/error-handling.md +526 -0
  118. package/library/snippets/power-query/parameters.md +350 -0
  119. package/library/snippets/power-query/performance.md +506 -0
  120. package/library/snippets/power-query/transformations.md +330 -0
  121. package/library/snippets/report-design/accessibility.md +78 -0
  122. package/library/snippets/report-design/chart-selection.md +54 -0
  123. package/library/snippets/report-design/layout-patterns.md +87 -0
  124. package/library/templates/data-models/README.md +93 -0
  125. package/library/templates/data-models/finance-model.md +627 -0
  126. package/library/templates/data-models/retail-star-schema.md +473 -0
  127. package/library/templates/excel/README.md +83 -0
  128. package/library/templates/excel/budget-tracker.md +432 -0
  129. package/library/templates/excel/data-entry-form.md +533 -0
  130. package/library/templates/power-bi/README.md +72 -0
  131. package/library/templates/power-bi/finance-report.md +449 -0
  132. package/library/templates/power-bi/kpi-scorecard.md +461 -0
  133. package/library/templates/power-bi/sales-dashboard.md +281 -0
  134. package/library/themes/excel/README.md +436 -0
  135. package/library/themes/power-bi/README.md +271 -0
  136. package/library/themes/power-bi/accessible.json +307 -0
  137. package/library/themes/power-bi/bi-superpowers-default.json +858 -0
  138. package/library/themes/power-bi/corporate-blue.json +291 -0
  139. package/library/themes/power-bi/dark-mode.json +291 -0
  140. package/library/themes/power-bi/minimal.json +292 -0
  141. package/library/themes/power-bi/print-friendly.json +309 -0
  142. package/package.json +93 -0
  143. package/skills/contributions/SKILL.md +267 -0
  144. package/skills/data-model-design/SKILL.md +470 -0
  145. package/skills/data-modeling/SKILL.md +254 -0
  146. package/skills/data-quality/SKILL.md +664 -0
  147. package/skills/dax/SKILL.md +708 -0
  148. package/skills/dax-doctor/SKILL.md +250 -0
  149. package/skills/dax-udf/SKILL.md +489 -0
  150. package/skills/deployment/SKILL.md +320 -0
  151. package/skills/excel-formulas/SKILL.md +463 -0
  152. package/skills/fabric-scripts/SKILL.md +454 -0
  153. package/skills/fast-standard/SKILL.md +509 -0
  154. package/skills/governance/SKILL.md +205 -0
  155. package/skills/migration-assistant/SKILL.md +292 -0
  156. package/skills/model-documenter/SKILL.md +244 -0
  157. package/skills/pbi-connect/SKILL.md +241 -0
  158. package/skills/power-query/SKILL.md +406 -0
  159. package/skills/project-kickoff/SKILL.md +907 -0
  160. package/skills/query-performance/SKILL.md +480 -0
  161. package/skills/report-design/SKILL.md +207 -0
  162. package/skills/report-layout/SKILL.md +298 -0
  163. package/skills/rls-design/SKILL.md +535 -0
  164. package/skills/semantic-model/SKILL.md +237 -0
  165. package/skills/testing-validation/SKILL.md +643 -0
  166. package/skills/theme-tweaker/SKILL.md +626 -0
  167. package/src/content/base.md +237 -0
  168. package/src/content/mcp-requirements.json +69 -0
  169. package/src/content/routing.md +203 -0
  170. package/src/content/skills/contributions.md +259 -0
  171. package/src/content/skills/data-model-design.md +462 -0
  172. package/src/content/skills/data-modeling.md +246 -0
  173. package/src/content/skills/data-quality.md +656 -0
  174. package/src/content/skills/dax-doctor.md +242 -0
  175. package/src/content/skills/dax-udf.md +481 -0
  176. package/src/content/skills/dax.md +700 -0
  177. package/src/content/skills/deployment.md +312 -0
  178. package/src/content/skills/excel-formulas.md +455 -0
  179. package/src/content/skills/fabric-scripts.md +446 -0
  180. package/src/content/skills/fast-standard.md +501 -0
  181. package/src/content/skills/governance.md +197 -0
  182. package/src/content/skills/migration-assistant.md +284 -0
  183. package/src/content/skills/model-documenter.md +236 -0
  184. package/src/content/skills/pbi-connect.md +233 -0
  185. package/src/content/skills/power-query.md +398 -0
  186. package/src/content/skills/project-kickoff.md +899 -0
  187. package/src/content/skills/query-performance.md +472 -0
  188. package/src/content/skills/report-design.md +199 -0
  189. package/src/content/skills/report-layout.md +290 -0
  190. package/src/content/skills/rls-design.md +527 -0
  191. package/src/content/skills/semantic-model.md +229 -0
  192. package/src/content/skills/testing-validation.md +635 -0
  193. package/src/content/skills/theme-tweaker.md +618 -0
@@ -0,0 +1,246 @@
1
+ /**
2
+ * MCP Setup Command
3
+ * =================
4
+ * Configures the official Microsoft MCP servers for the Claude Code plugin
5
+ * and legacy adapter formats.
6
+ *
7
+ * Usage:
8
+ * super mcp-setup
9
+ * super mcp-setup --tool claude-plugin
10
+ * super mcp-setup --dry-run
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const tui = require('../utils/tui');
16
+ const mcpDetect = require('../utils/mcp-detect');
17
+ const {
18
+ ABSOLUTE_LAUNCHER_MODE,
19
+ PLUGIN_ROOT_LAUNCHER_MODE,
20
+ createMcpConfigForFormat,
21
+ mergeMcpConfig,
22
+ } = require('../lib/microsoft-mcp');
23
+
24
+ const MCP_CONFIGS = {
25
+ 'claude-plugin': {
26
+ name: 'Claude Code Plugin',
27
+ configPath: '.mcp.json',
28
+ format: 'plugin',
29
+ },
30
+ 'claude-code': {
31
+ name: 'Claude Code (legacy standalone)',
32
+ configPath: '.claude/settings.json',
33
+ format: 'claude',
34
+ },
35
+ cursor: {
36
+ name: 'Cursor (legacy)',
37
+ configPath: '.cursor/mcp.json',
38
+ format: 'cursor',
39
+ },
40
+ kilocode: {
41
+ name: 'Kilo Code (legacy)',
42
+ configPath: '.kilocode/mcp.json',
43
+ format: 'kilo',
44
+ },
45
+ 'open-code': {
46
+ name: 'OpenCode (legacy)',
47
+ configPath: 'opencode.config.json',
48
+ format: 'opencode',
49
+ },
50
+ vscode: {
51
+ name: 'VS Code (legacy)',
52
+ configPath: '.vscode/settings.json',
53
+ format: 'vscode',
54
+ },
55
+ };
56
+
57
+ function parseArgs(args) {
58
+ const options = {
59
+ tool: null,
60
+ dryRun: false,
61
+ targetDir: process.cwd(),
62
+ deprecatedFlags: [],
63
+ };
64
+
65
+ for (let index = 0; index < args.length; index++) {
66
+ const arg = args[index];
67
+
68
+ if (arg === '--tool' || arg === '-t') {
69
+ options.tool = args[++index];
70
+ } else if (arg === '--dry-run') {
71
+ options.dryRun = true;
72
+ } else if (
73
+ arg === '--port' ||
74
+ arg === '-p' ||
75
+ arg === '--github' ||
76
+ arg === '-g' ||
77
+ arg === '--fabric'
78
+ ) {
79
+ options.deprecatedFlags.push(arg);
80
+ if (args[index + 1] && !args[index + 1].startsWith('-')) {
81
+ index++;
82
+ }
83
+ } else if (!arg.startsWith('-')) {
84
+ options.targetDir = arg;
85
+ }
86
+ }
87
+
88
+ return options;
89
+ }
90
+
91
+ function loadConfiguredTools(targetDir) {
92
+ const configPath = path.join(targetDir, '.bi-superpowers.json');
93
+ if (fs.existsSync(configPath)) {
94
+ try {
95
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
96
+ if (Array.isArray(config.tools) && config.tools.length > 0) {
97
+ return Array.from(new Set(config.tools));
98
+ }
99
+ } catch (error) {
100
+ // Ignore invalid config and fall back below.
101
+ }
102
+ }
103
+
104
+ const pluginManifest = path.join(targetDir, '.claude-plugin', 'plugin.json');
105
+ if (fs.existsSync(pluginManifest)) {
106
+ return ['claude-plugin'];
107
+ }
108
+
109
+ return ['claude-plugin'];
110
+ }
111
+
112
+ function readJson(filePath) {
113
+ if (!fs.existsSync(filePath)) {
114
+ return {};
115
+ }
116
+
117
+ try {
118
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
119
+ } catch (error) {
120
+ return {};
121
+ }
122
+ }
123
+
124
+ function writeConfig(filePath, config, dryRun) {
125
+ const directory = path.dirname(filePath);
126
+
127
+ if (dryRun) {
128
+ tui.info(`Would write: ${tui.formatPath(filePath)}`);
129
+ tui.muted(JSON.stringify(config, null, 2).slice(0, 220) + '...');
130
+ return true;
131
+ }
132
+
133
+ try {
134
+ if (!fs.existsSync(directory)) {
135
+ fs.mkdirSync(directory, { recursive: true });
136
+ }
137
+
138
+ fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n');
139
+ return true;
140
+ } catch (error) {
141
+ tui.error(`Failed to write ${filePath}: ${error.message}`);
142
+ return false;
143
+ }
144
+ }
145
+
146
+ function getLauncherMode(targetDir, packageDir, tool) {
147
+ if (tool === 'claude-plugin' && path.resolve(targetDir) === path.resolve(packageDir)) {
148
+ return PLUGIN_ROOT_LAUNCHER_MODE;
149
+ }
150
+
151
+ return ABSOLUTE_LAUNCHER_MODE;
152
+ }
153
+
154
+ function mcpSetupCommand(args, config) {
155
+ const options = parseArgs(args);
156
+ const targetDir = path.resolve(options.targetDir);
157
+
158
+ tui.header('BI Agent Superpowers', 'Microsoft MCP Setup');
159
+
160
+ if (options.dryRun) {
161
+ tui.dryRunNotice();
162
+ }
163
+
164
+ if (options.deprecatedFlags.length > 0) {
165
+ tui.warning(
166
+ `Deprecated flags ignored: ${options.deprecatedFlags.join(', ')}. MCP setup is now plugin-first and uses the official Microsoft servers.`
167
+ );
168
+ }
169
+
170
+ let configuredTools = loadConfiguredTools(targetDir);
171
+
172
+ if (options.tool) {
173
+ if (!MCP_CONFIGS[options.tool]) {
174
+ tui.error(`Unknown tool: ${options.tool}`);
175
+ tui.info(`Available tools: ${Object.keys(MCP_CONFIGS).join(', ')}`);
176
+ process.exit(1);
177
+ }
178
+
179
+ configuredTools = [options.tool];
180
+ }
181
+
182
+ tui.section('Configured Outputs');
183
+ configuredTools.forEach((tool) => {
184
+ tui.success(MCP_CONFIGS[tool].name);
185
+ });
186
+
187
+ const status = mcpDetect.getMcpStatus();
188
+
189
+ tui.section('Official Microsoft MCP Status');
190
+ if (status.local.installed) {
191
+ tui.success(`Power BI Modeling MCP detected: ${status.local.path}`);
192
+ if (status.local.version) {
193
+ tui.info(`Version: ${status.local.version}`);
194
+ }
195
+ } else {
196
+ tui.warning('Power BI Modeling MCP not detected locally.');
197
+ tui.info(mcpDetect.getModelingMcpError());
198
+ }
199
+
200
+ tui.success(`Power BI Remote MCP: ${status.remote.url}`);
201
+ tui.success(`Fabric MCP package: ${status.fabric.package}`);
202
+
203
+ const results = {
204
+ success: [],
205
+ failed: [],
206
+ };
207
+
208
+ tui.section('Writing Configurations');
209
+ configuredTools.forEach((tool) => {
210
+ const toolConfig = MCP_CONFIGS[tool];
211
+ const filePath = path.join(targetDir, toolConfig.configPath);
212
+ const existingConfig = readJson(filePath);
213
+ const generatedConfig = createMcpConfigForFormat(toolConfig.format, {
214
+ packageDir: config.packageDir,
215
+ launcherMode: getLauncherMode(targetDir, config.packageDir, tool),
216
+ });
217
+ const mergedConfig = mergeMcpConfig(existingConfig, generatedConfig, toolConfig.format);
218
+ const ok = writeConfig(filePath, mergedConfig, options.dryRun);
219
+
220
+ if (ok) {
221
+ results.success.push(toolConfig.name);
222
+ tui.success(`${toolConfig.name}: ${tui.formatPath(toolConfig.configPath)}`);
223
+ } else {
224
+ results.failed.push(toolConfig.name);
225
+ }
226
+ });
227
+
228
+ console.log('');
229
+ tui.section('Next Steps');
230
+ tui.listItem(
231
+ 'Install the official Microsoft Power BI Modeling MCP extension in VS Code/Cursor on Windows if you need write access to Desktop/Fabric/PBIP models.'
232
+ );
233
+ tui.listItem('Refresh or restart your MCP client after config changes.');
234
+
235
+ if (configuredTools.includes('claude-plugin')) {
236
+ tui.listItem(`Run Claude Code with the plugin: claude --plugin-dir ${targetDir}`);
237
+ }
238
+
239
+ if (results.failed.length > 0) {
240
+ console.log('');
241
+ tui.warning(`Completed with failures: ${results.failed.join(', ')}`);
242
+ process.exit(1);
243
+ }
244
+ }
245
+
246
+ module.exports = mcpSetupCommand;
@@ -0,0 +1,287 @@
1
+ /**
2
+ * Pull Command - Sync from Original to Repo
3
+ * ==========================================
4
+ *
5
+ * Pulls changes from the original BI file to the repo.
6
+ * Extracts updated TMDL/content and creates a Git commit.
7
+ *
8
+ * Usage:
9
+ * super pull Pull current project
10
+ * super pull <project-name> Pull specific project
11
+ * super pull --all Pull all projects
12
+ *
13
+ * @module commands/pull
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ const git = require('../utils/git');
20
+ const profiles = require('../utils/profiles');
21
+ const pbix = require('../utils/pbix');
22
+ const { getAllProjects, getProject, detectCurrentProject } = require('../utils/projects');
23
+
24
+ /**
25
+ * Parse command line arguments
26
+ * @param {string[]} args - CLI arguments
27
+ * @returns {Object} Parsed options
28
+ */
29
+ function parseArgs(args) {
30
+ const options = {
31
+ projectName: null,
32
+ all: false,
33
+ force: false,
34
+ help: false,
35
+ };
36
+
37
+ for (let i = 0; i < args.length; i++) {
38
+ const arg = args[i];
39
+
40
+ if (arg === '--all' || arg === '-a') {
41
+ options.all = true;
42
+ } else if (arg === '--force' || arg === '-f') {
43
+ options.force = true;
44
+ } else if (arg === '--help' || arg === '-h') {
45
+ options.help = true;
46
+ } else if (!arg.startsWith('-') && !options.projectName) {
47
+ options.projectName = arg;
48
+ }
49
+ }
50
+
51
+ return options;
52
+ }
53
+
54
+ /**
55
+ * Show help message
56
+ */
57
+ function showHelp() {
58
+ console.log(`
59
+ super pull - Traer cambios del archivo original al repo
60
+
61
+ Uso:
62
+ super pull Pull del proyecto actual (si estás en la carpeta)
63
+ super pull <nombre-proyecto> Pull de un proyecto específico
64
+ super pull --all Pull de todos los proyectos
65
+
66
+ Opciones:
67
+ --all, -a Pull de todos los proyectos
68
+ --force, -f Forzar pull aunque no haya cambios detectados
69
+ --help, -h Mostrar esta ayuda
70
+
71
+ Ejemplos:
72
+ super pull sales-q4
73
+ super pull --all
74
+ super pull hr-dashboard --force
75
+ `);
76
+ }
77
+
78
+ // Note: getAllProjects, getProject, and detectCurrentProject are imported from ../utils/projects
79
+
80
+ /**
81
+ * Pull changes for a single project
82
+ * @param {Object} project - Project config
83
+ * @param {boolean} force - Force pull even if no changes
84
+ * @returns {Object} Result object
85
+ */
86
+ function pullProject(project, force = false) {
87
+ const result = {
88
+ success: false,
89
+ project: project.name,
90
+ message: '',
91
+ filesChanged: 0,
92
+ };
93
+
94
+ // Check if source file exists
95
+ const sourcePath = project.source?.path;
96
+ if (!sourcePath || !fs.existsSync(sourcePath)) {
97
+ result.message = `Archivo original no encontrado: ${sourcePath || 'no configurado'}`;
98
+ return result;
99
+ }
100
+
101
+ // Check if file has changed (by modification time or hash)
102
+ const currentHash = pbix.getFileHash(sourcePath);
103
+ const lastHash = project.source?.hash || '';
104
+
105
+ if (currentHash === lastHash && !force) {
106
+ result.success = true;
107
+ result.message = 'Sin cambios';
108
+ return result;
109
+ }
110
+
111
+ // Detect file type and extract
112
+ const fileInfo = pbix.detectFileType(sourcePath);
113
+
114
+ if (fileInfo.type === 'power-bi-project') {
115
+ // PBIP - extract TMDL
116
+ const definitionPath = pbix.getPbipDefinitionPath(sourcePath);
117
+
118
+ if (definitionPath) {
119
+ const targetDef = path.join(project.projectPath, 'definition');
120
+
121
+ // Compare directories to show changes
122
+ const changes = pbix.compareDirectories(targetDef, definitionPath);
123
+
124
+ // Copy new content
125
+ const extraction = pbix.extractPbipToRepo(sourcePath, project.projectPath);
126
+
127
+ if (extraction.success) {
128
+ result.success = true;
129
+ result.filesChanged =
130
+ changes.added.length + changes.modified.length + changes.deleted.length;
131
+ result.message = `Actualizado: +${changes.added.length} ~${changes.modified.length} -${changes.deleted.length}`;
132
+ } else {
133
+ result.message = extraction.error;
134
+ }
135
+ } else {
136
+ result.message = 'No se encontró carpeta definition en PBIP';
137
+ }
138
+ } else if (fileInfo.type === 'power-bi') {
139
+ // PBIX - just update modification time and hash
140
+ result.success = true;
141
+ result.message = 'Archivo .pbix actualizado (binario, sin diff detallado)';
142
+
143
+ // Update the README with latest info
144
+ const readmePath = path.join(project.projectPath, 'README.md');
145
+ if (fs.existsSync(readmePath)) {
146
+ let content = fs.readFileSync(readmePath, 'utf8');
147
+ const lastMod = pbix.getLastModified(sourcePath);
148
+
149
+ // Add update note
150
+ if (!content.includes('## Historial de Actualizaciones')) {
151
+ content += '\n\n## Historial de Actualizaciones\n';
152
+ }
153
+ content += `\n- ${lastMod?.toISOString()}: Pull desde archivo original`;
154
+ fs.writeFileSync(readmePath, content);
155
+ }
156
+ } else if (fileInfo.type === 'excel' || fileInfo.type === 'excel-macro') {
157
+ // Excel - update modification tracking
158
+ result.success = true;
159
+ result.message = 'Archivo Excel actualizado';
160
+ } else {
161
+ result.message = 'Tipo de archivo no soportado para pull';
162
+ }
163
+
164
+ // Update project.json with new hash and sync time
165
+ if (result.success) {
166
+ project.source.hash = currentHash;
167
+ project.source.lastSync = new Date().toISOString();
168
+
169
+ const configPath = path.join(project.projectPath, 'project.json');
170
+ fs.writeFileSync(configPath, JSON.stringify(project, null, 2));
171
+ }
172
+
173
+ return result;
174
+ }
175
+
176
+ /**
177
+ * Main pull command handler
178
+ */
179
+ async function pullCommand(args, _config) {
180
+ const options = parseArgs(args);
181
+
182
+ if (options.help) {
183
+ showHelp();
184
+ return;
185
+ }
186
+
187
+ // Check if repo exists
188
+ const repoPath = profiles.getRepoPath();
189
+ if (!repoPath || !fs.existsSync(repoPath)) {
190
+ console.log(`
191
+ No se encontró el repositorio de BI.
192
+
193
+ Ejecuta primero:
194
+ super setup
195
+ `);
196
+ process.exit(1);
197
+ }
198
+
199
+ console.log(`
200
+ ════════════════════════════════════════════════════════════════
201
+ Pull - Traer cambios de archivos originales
202
+ ════════════════════════════════════════════════════════════════
203
+ `);
204
+
205
+ let projectsToPull = [];
206
+
207
+ if (options.all) {
208
+ // Pull all projects
209
+ projectsToPull = getAllProjects(repoPath);
210
+ console.log(`Proyectos encontrados: ${projectsToPull.length}\n`);
211
+ } else if (options.projectName) {
212
+ // Pull specific project
213
+ const project = getProject(repoPath, options.projectName);
214
+ if (!project) {
215
+ console.log(`✗ Proyecto no encontrado: ${options.projectName}`);
216
+ console.log('\nProyectos disponibles:');
217
+ getAllProjects(repoPath).forEach((p) => {
218
+ console.log(` - ${p.name}`);
219
+ });
220
+ process.exit(1);
221
+ }
222
+ projectsToPull = [project];
223
+ } else {
224
+ // Try to detect current project
225
+ const current = detectCurrentProject(repoPath);
226
+ if (current) {
227
+ projectsToPull = [current];
228
+ } else {
229
+ // No project specified, show help
230
+ console.log('Especifica un proyecto o usa --all:\n');
231
+ console.log(' super pull <nombre-proyecto>');
232
+ console.log(' super pull --all\n');
233
+ console.log('Proyectos disponibles:');
234
+ getAllProjects(repoPath).forEach((p) => {
235
+ console.log(` - ${p.name}`);
236
+ });
237
+ return;
238
+ }
239
+ }
240
+
241
+ if (projectsToPull.length === 0) {
242
+ console.log('No hay proyectos para actualizar.');
243
+ return;
244
+ }
245
+
246
+ // Pull each project
247
+ const results = [];
248
+
249
+ for (const project of projectsToPull) {
250
+ process.stdout.write(` ${project.name}... `);
251
+
252
+ const result = pullProject(project, options.force);
253
+ results.push(result);
254
+
255
+ if (result.success) {
256
+ console.log(`✓ ${result.message}`);
257
+ } else {
258
+ console.log(`✗ ${result.message}`);
259
+ }
260
+ }
261
+
262
+ // Git commit if there were changes
263
+ const changedProjects = results.filter((r) => r.success && r.filesChanged > 0);
264
+
265
+ if (changedProjects.length > 0 && git.isGitRepo(repoPath)) {
266
+ console.log('\nGuardando cambios en Git...');
267
+
268
+ git.stageFiles(repoPath, '.');
269
+
270
+ const projectNames = changedProjects.map((r) => r.project).join(', ');
271
+ git.commit(repoPath, `Pull updates: ${projectNames}`);
272
+
273
+ console.log(` ✓ Commit: "Pull updates: ${projectNames}"`);
274
+ }
275
+
276
+ // Summary
277
+ const successful = results.filter((r) => r.success).length;
278
+ const failed = results.filter((r) => !r.success).length;
279
+
280
+ console.log(`
281
+ ════════════════════════════════════════════════════════════════
282
+ Resumen: ${successful} exitosos, ${failed} fallidos
283
+ ════════════════════════════════════════════════════════════════
284
+ `);
285
+ }
286
+
287
+ module.exports = pullCommand;
@@ -0,0 +1,36 @@
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
+ });