@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,289 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for the install command
|
|
3
|
-
*
|
|
4
|
-
* Run with: npm test
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const { test, describe, beforeEach, afterEach } = require('node:test');
|
|
8
|
-
const assert = require('node:assert');
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
const os = require('os');
|
|
12
|
-
|
|
13
|
-
const installCommand = require('./install');
|
|
14
|
-
const { parseArgs, detectAgents, copySkillDir, formatFsError, AGENTS, UNIVERSAL_DIR } =
|
|
15
|
-
installCommand;
|
|
16
|
-
|
|
17
|
-
describe('install command - parseArgs', () => {
|
|
18
|
-
test('parses --yes/-y flag', () => {
|
|
19
|
-
assert.strictEqual(parseArgs(['--yes']).isYes, true);
|
|
20
|
-
assert.strictEqual(parseArgs(['-y']).isYes, true);
|
|
21
|
-
assert.strictEqual(parseArgs([]).isYes, false);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('parses --all flag', () => {
|
|
25
|
-
assert.strictEqual(parseArgs(['--all']).isAll, true);
|
|
26
|
-
assert.strictEqual(parseArgs([]).isAll, false);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('parses multiple --agent flags', () => {
|
|
30
|
-
const opts = parseArgs(['--agent', 'claude-code', '--agent', 'codex']);
|
|
31
|
-
assert.deepStrictEqual(opts.agentFlags, ['claude-code', 'codex']);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('parses short -a flag', () => {
|
|
35
|
-
const opts = parseArgs(['-a', 'claude-code', '-a', 'kilo']);
|
|
36
|
-
assert.deepStrictEqual(opts.agentFlags, ['claude-code', 'kilo']);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('ignores --agent without value (end of args)', () => {
|
|
40
|
-
const opts = parseArgs(['--agent']);
|
|
41
|
-
assert.deepStrictEqual(opts.agentFlags, []);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test('ignores --agent when next arg is another flag', () => {
|
|
45
|
-
const opts = parseArgs(['--agent', '--yes']);
|
|
46
|
-
assert.deepStrictEqual(opts.agentFlags, []);
|
|
47
|
-
assert.strictEqual(opts.isYes, true);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test('combines all flags correctly', () => {
|
|
51
|
-
const opts = parseArgs(['--all', '--yes', '-a', 'claude-code']);
|
|
52
|
-
assert.strictEqual(opts.isAll, true);
|
|
53
|
-
assert.strictEqual(opts.isYes, true);
|
|
54
|
-
assert.deepStrictEqual(opts.agentFlags, ['claude-code']);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
describe('install command - AGENTS registry', () => {
|
|
59
|
-
test('has exactly 5 supported agents', () => {
|
|
60
|
-
assert.strictEqual(Object.keys(AGENTS).length, 5);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('includes the 5 officially supported agents', () => {
|
|
64
|
-
const ids = Object.keys(AGENTS);
|
|
65
|
-
assert.ok(ids.includes('github-copilot'));
|
|
66
|
-
assert.ok(ids.includes('claude-code'));
|
|
67
|
-
assert.ok(ids.includes('codex'));
|
|
68
|
-
assert.ok(ids.includes('gemini-cli'));
|
|
69
|
-
assert.ok(ids.includes('kilo'));
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('every agent has name and dir fields', () => {
|
|
73
|
-
for (const [id, agent] of Object.entries(AGENTS)) {
|
|
74
|
-
assert.ok(agent.name, `${id} missing name`);
|
|
75
|
-
assert.ok(agent.dir, `${id} missing dir`);
|
|
76
|
-
assert.ok(agent.dir.startsWith('.'), `${id} dir should start with .`);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('UNIVERSAL_DIR is .agents/skills', () => {
|
|
81
|
-
assert.strictEqual(UNIVERSAL_DIR, '.agents/skills');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test('codex uses UNIVERSAL_DIR', () => {
|
|
85
|
-
assert.strictEqual(AGENTS.codex.dir, UNIVERSAL_DIR);
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe('install command - detectAgents', () => {
|
|
90
|
-
let tempDir;
|
|
91
|
-
|
|
92
|
-
beforeEach(() => {
|
|
93
|
-
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bi-install-test-'));
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
afterEach(() => {
|
|
97
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('returns empty array when no agents are installed', () => {
|
|
101
|
-
const detected = detectAgents(tempDir);
|
|
102
|
-
assert.deepStrictEqual(detected, []);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test('detects claude-code when .claude exists', () => {
|
|
106
|
-
fs.mkdirSync(path.join(tempDir, '.claude'), { recursive: true });
|
|
107
|
-
const detected = detectAgents(tempDir);
|
|
108
|
-
assert.ok(detected.includes('claude-code'));
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test('detects multiple agents', () => {
|
|
112
|
-
fs.mkdirSync(path.join(tempDir, '.claude'), { recursive: true });
|
|
113
|
-
fs.mkdirSync(path.join(tempDir, '.copilot'), { recursive: true });
|
|
114
|
-
fs.mkdirSync(path.join(tempDir, '.gemini'), { recursive: true });
|
|
115
|
-
const detected = detectAgents(tempDir);
|
|
116
|
-
assert.ok(detected.includes('claude-code'));
|
|
117
|
-
assert.ok(detected.includes('github-copilot'));
|
|
118
|
-
assert.ok(detected.includes('gemini-cli'));
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
describe('install command - copySkillDir', () => {
|
|
123
|
-
let srcDir;
|
|
124
|
-
let destDir;
|
|
125
|
-
|
|
126
|
-
beforeEach(() => {
|
|
127
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bi-install-copy-'));
|
|
128
|
-
srcDir = path.join(tempDir, 'src');
|
|
129
|
-
destDir = path.join(tempDir, 'dest');
|
|
130
|
-
fs.mkdirSync(srcDir, { recursive: true });
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
afterEach(() => {
|
|
134
|
-
fs.rmSync(path.dirname(srcDir), { recursive: true, force: true });
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test('copies a single file', () => {
|
|
138
|
-
fs.writeFileSync(path.join(srcDir, 'SKILL.md'), '# Test');
|
|
139
|
-
copySkillDir(srcDir, destDir);
|
|
140
|
-
assert.strictEqual(fs.readFileSync(path.join(destDir, 'SKILL.md'), 'utf8'), '# Test');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test('creates destination directory if missing', () => {
|
|
144
|
-
fs.writeFileSync(path.join(srcDir, 'SKILL.md'), 'content');
|
|
145
|
-
copySkillDir(srcDir, destDir);
|
|
146
|
-
assert.ok(fs.existsSync(destDir));
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test('copies nested directories recursively', () => {
|
|
150
|
-
fs.writeFileSync(path.join(srcDir, 'SKILL.md'), 'top');
|
|
151
|
-
fs.mkdirSync(path.join(srcDir, 'references'));
|
|
152
|
-
fs.writeFileSync(path.join(srcDir, 'references', 'notes.md'), 'nested');
|
|
153
|
-
fs.mkdirSync(path.join(srcDir, 'scripts'));
|
|
154
|
-
fs.writeFileSync(path.join(srcDir, 'scripts', 'run.sh'), '#!/bin/sh');
|
|
155
|
-
|
|
156
|
-
copySkillDir(srcDir, destDir);
|
|
157
|
-
|
|
158
|
-
assert.strictEqual(fs.readFileSync(path.join(destDir, 'SKILL.md'), 'utf8'), 'top');
|
|
159
|
-
assert.strictEqual(
|
|
160
|
-
fs.readFileSync(path.join(destDir, 'references', 'notes.md'), 'utf8'),
|
|
161
|
-
'nested'
|
|
162
|
-
);
|
|
163
|
-
assert.strictEqual(
|
|
164
|
-
fs.readFileSync(path.join(destDir, 'scripts', 'run.sh'), 'utf8'),
|
|
165
|
-
'#!/bin/sh'
|
|
166
|
-
);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
describe('install command - formatFsError', () => {
|
|
171
|
-
test('includes the context and error message', () => {
|
|
172
|
-
const err = new Error('oops');
|
|
173
|
-
err.code = 'UNKNOWN';
|
|
174
|
-
const msg = formatFsError(err, 'Failed');
|
|
175
|
-
assert.ok(msg.includes('Failed'));
|
|
176
|
-
assert.ok(msg.includes('oops'));
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test('adds hint for EACCES', () => {
|
|
180
|
-
const err = new Error('permission denied');
|
|
181
|
-
err.code = 'EACCES';
|
|
182
|
-
const msg = formatFsError(err, 'Failed');
|
|
183
|
-
assert.ok(msg.toLowerCase().includes('permiso'));
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test('adds hint for EPERM (Windows admin)', () => {
|
|
187
|
-
const err = new Error('not permitted');
|
|
188
|
-
err.code = 'EPERM';
|
|
189
|
-
const msg = formatFsError(err, 'Failed');
|
|
190
|
-
assert.ok(msg.toLowerCase().includes('admin'));
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
test('adds hint for ENOSPC', () => {
|
|
194
|
-
const err = new Error('no space');
|
|
195
|
-
err.code = 'ENOSPC';
|
|
196
|
-
const msg = formatFsError(err, 'Failed');
|
|
197
|
-
assert.ok(msg.toLowerCase().includes('espacio'));
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
describe('install command - module exports', () => {
|
|
202
|
-
test('module default export is a function', () => {
|
|
203
|
-
assert.strictEqual(typeof installCommand, 'function');
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test('exposes internal helpers for testing', () => {
|
|
207
|
-
assert.strictEqual(typeof installCommand.parseArgs, 'function');
|
|
208
|
-
assert.strictEqual(typeof installCommand.detectAgents, 'function');
|
|
209
|
-
assert.strictEqual(typeof installCommand.copySkillDir, 'function');
|
|
210
|
-
assert.strictEqual(typeof installCommand.formatFsError, 'function');
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
describe('install command - integration: --all --yes', () => {
|
|
215
|
-
let tempHome;
|
|
216
|
-
let tempPkg;
|
|
217
|
-
let origHome;
|
|
218
|
-
|
|
219
|
-
beforeEach(() => {
|
|
220
|
-
tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'bi-install-int-home-'));
|
|
221
|
-
tempPkg = fs.mkdtempSync(path.join(os.tmpdir(), 'bi-install-int-pkg-'));
|
|
222
|
-
|
|
223
|
-
// Minimal fake package layout: skills/<name>/SKILL.md + launcher file
|
|
224
|
-
const skillsDir = path.join(tempPkg, 'skills');
|
|
225
|
-
for (const skillName of ['project-kickoff', 'pbi-connect']) {
|
|
226
|
-
const dir = path.join(skillsDir, skillName);
|
|
227
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
228
|
-
fs.writeFileSync(
|
|
229
|
-
path.join(dir, 'SKILL.md'),
|
|
230
|
-
`---\nname: ${skillName}\ndescription: fake skill for test\n---\n# ${skillName}\n`
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
fs.mkdirSync(path.join(tempPkg, 'bin', 'mcp'), { recursive: true });
|
|
234
|
-
fs.writeFileSync(
|
|
235
|
-
path.join(tempPkg, 'bin', 'mcp', 'powerbi-modeling-launcher.js'),
|
|
236
|
-
'// fake launcher\n'
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
origHome = os.homedir;
|
|
240
|
-
os.homedir = () => tempHome;
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
afterEach(() => {
|
|
244
|
-
os.homedir = origHome;
|
|
245
|
-
fs.rmSync(tempHome, { recursive: true, force: true });
|
|
246
|
-
fs.rmSync(tempPkg, { recursive: true, force: true });
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
test('installs skills and writes MCP config for all 5 agents', async () => {
|
|
250
|
-
const origExitCode = process.exitCode;
|
|
251
|
-
try {
|
|
252
|
-
await installCommand(['--all', '--yes'], {
|
|
253
|
-
packageDir: tempPkg,
|
|
254
|
-
version: '9.9.9-test',
|
|
255
|
-
});
|
|
256
|
-
} finally {
|
|
257
|
-
// Reset exit code so subsequent tests aren't affected
|
|
258
|
-
process.exitCode = origExitCode;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Skills installed at universal path
|
|
262
|
-
assert.ok(
|
|
263
|
-
fs.existsSync(path.join(tempHome, '.agents', 'skills', 'project-kickoff', 'SKILL.md'))
|
|
264
|
-
);
|
|
265
|
-
assert.ok(fs.existsSync(path.join(tempHome, '.agents', 'skills', 'pbi-connect', 'SKILL.md')));
|
|
266
|
-
|
|
267
|
-
// MCP configs written for all 5 agents
|
|
268
|
-
const expectedMcpFiles = [
|
|
269
|
-
path.join(tempHome, '.claude.json'),
|
|
270
|
-
path.join(tempHome, '.copilot', 'mcp-config.json'),
|
|
271
|
-
path.join(tempHome, '.codex', 'config.toml'),
|
|
272
|
-
path.join(tempHome, '.gemini', 'settings.json'),
|
|
273
|
-
path.join(tempHome, '.kilo', 'mcp_settings.json'),
|
|
274
|
-
];
|
|
275
|
-
for (const filePath of expectedMcpFiles) {
|
|
276
|
-
assert.ok(fs.existsSync(filePath), `expected MCP config at ${filePath}`);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Claude Code config has both servers under mcpServers
|
|
280
|
-
const claudeConfig = JSON.parse(fs.readFileSync(path.join(tempHome, '.claude.json'), 'utf8'));
|
|
281
|
-
assert.ok(claudeConfig.mcpServers['powerbi-modeling']);
|
|
282
|
-
assert.ok(claudeConfig.mcpServers['microsoft-learn']);
|
|
283
|
-
|
|
284
|
-
// Codex TOML has both sections
|
|
285
|
-
const codexToml = fs.readFileSync(path.join(tempHome, '.codex', 'config.toml'), 'utf8');
|
|
286
|
-
assert.ok(codexToml.includes('[mcp_servers.powerbi-modeling]'));
|
|
287
|
-
assert.ok(codexToml.includes('[mcp_servers.microsoft-learn]'));
|
|
288
|
-
});
|
|
289
|
-
});
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for the lint command (checkup)
|
|
3
|
-
*
|
|
4
|
-
* Run with: npm test
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const { test, describe } = require('node:test');
|
|
8
|
-
const assert = require('node:assert');
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
|
|
12
|
-
// Mock skill content for testing
|
|
13
|
-
const validSkillContent = `# Test Skill
|
|
14
|
-
|
|
15
|
-
## Trigger
|
|
16
|
-
Activate this skill when user mentions:
|
|
17
|
-
- "test keyword"
|
|
18
|
-
- "another trigger"
|
|
19
|
-
|
|
20
|
-
## Identity
|
|
21
|
-
You are a **Test Expert** who helps users with testing.
|
|
22
|
-
|
|
23
|
-
## MANDATORY RULES
|
|
24
|
-
1. **ALWAYS TEST FIRST.** Never skip validation.
|
|
25
|
-
2. **USE ASSERTIONS.** Verify expected outcomes.
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## PHASE 0: Initial Assessment
|
|
30
|
-
|
|
31
|
-
Start with basic questions.
|
|
32
|
-
|
|
33
|
-
\`\`\`dax
|
|
34
|
-
// Example DAX
|
|
35
|
-
TestMeasure = SUM(Table[Column])
|
|
36
|
-
\`\`\`
|
|
37
|
-
`;
|
|
38
|
-
|
|
39
|
-
const invalidSkillContent = `# Missing Sections
|
|
40
|
-
|
|
41
|
-
This skill is missing required sections like Trigger and MANDATORY RULES.
|
|
42
|
-
|
|
43
|
-
Some content here but no proper structure.
|
|
44
|
-
`;
|
|
45
|
-
|
|
46
|
-
describe('Lint Command', () => {
|
|
47
|
-
test('valid skill content should have required sections', () => {
|
|
48
|
-
const hasTrigger = /##\s+Trigger/i.test(validSkillContent);
|
|
49
|
-
const hasIdentity = /##\s+Identity/i.test(validSkillContent);
|
|
50
|
-
const hasMandatoryRules = /##\s+MANDATORY RULES/i.test(validSkillContent);
|
|
51
|
-
|
|
52
|
-
assert.strictEqual(hasTrigger, true, 'Should have Trigger section');
|
|
53
|
-
assert.strictEqual(hasIdentity, true, 'Should have Identity section');
|
|
54
|
-
assert.strictEqual(hasMandatoryRules, true, 'Should have MANDATORY RULES section');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('invalid skill content should be missing required sections', () => {
|
|
58
|
-
const hasTrigger = /##\s+Trigger/i.test(invalidSkillContent);
|
|
59
|
-
const hasMandatoryRules = /##\s+MANDATORY RULES/i.test(invalidSkillContent);
|
|
60
|
-
|
|
61
|
-
assert.strictEqual(hasTrigger, false, 'Should not have Trigger section');
|
|
62
|
-
assert.strictEqual(hasMandatoryRules, false, 'Should not have MANDATORY RULES section');
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('skill should have H1 title', () => {
|
|
66
|
-
const hasH1 = /^#\s+.+/m.test(validSkillContent);
|
|
67
|
-
assert.strictEqual(hasH1, true, 'Should have H1 title');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('code blocks should have language specifier', () => {
|
|
71
|
-
// Check for code blocks with language
|
|
72
|
-
const codeBlockWithLang = /```\w+/;
|
|
73
|
-
const hasLanguage = codeBlockWithLang.test(validSkillContent);
|
|
74
|
-
assert.strictEqual(hasLanguage, true, 'Code blocks should have language specifier');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('trigger section should have quoted phrases', () => {
|
|
78
|
-
const triggerSection = validSkillContent.match(/##\s+Trigger[\s\S]*?(?=##|$)/i);
|
|
79
|
-
assert.ok(triggerSection, 'Should have Trigger section');
|
|
80
|
-
|
|
81
|
-
const hasQuotedPhrases = /[-*]\s+["'].+["']/.test(triggerSection[0]);
|
|
82
|
-
assert.strictEqual(hasQuotedPhrases, true, 'Trigger should have quoted phrases');
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
describe('File naming convention', () => {
|
|
87
|
-
test('kebab-case pattern should match valid names', () => {
|
|
88
|
-
const pattern = /^[a-z0-9-]+\.md$/;
|
|
89
|
-
|
|
90
|
-
assert.strictEqual(pattern.test('dax.md'), true);
|
|
91
|
-
assert.strictEqual(pattern.test('power-query.md'), true);
|
|
92
|
-
assert.strictEqual(pattern.test('data-model-design.md'), true);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test('kebab-case pattern should reject invalid names', () => {
|
|
96
|
-
const pattern = /^[a-z0-9-]+\.md$/;
|
|
97
|
-
|
|
98
|
-
assert.strictEqual(pattern.test('DAX.md'), false);
|
|
99
|
-
assert.strictEqual(pattern.test('PowerQuery.md'), false);
|
|
100
|
-
assert.strictEqual(pattern.test('my_skill.md'), false);
|
|
101
|
-
assert.strictEqual(pattern.test('skill.txt'), false);
|
|
102
|
-
});
|
|
103
|
-
});
|
package/bin/commands/pull.js
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
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;
|