@luquimbo/bi-superpowers 3.1.0 → 3.2.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 (110) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.claude-plugin/skill-manifest.json +1 -1
  4. package/.plugin/plugin.json +1 -1
  5. package/README.md +2 -2
  6. package/bin/build-plugin.js +6 -6
  7. package/bin/cli.js +169 -310
  8. package/bin/commands/install.js +87 -70
  9. package/bin/commands/install.test.js +2 -2
  10. package/bin/lib/agents.js +21 -2
  11. package/bin/lib/mcp-config.js +27 -5
  12. package/bin/lib/mcp-config.test.js +1 -1
  13. package/desktop-extension/manifest.json +4 -11
  14. package/desktop-extension/server.js +34 -25
  15. package/package.json +3 -9
  16. package/skills/pbi-connect/SKILL.md +1 -1
  17. package/skills/project-kickoff/SKILL.md +1 -1
  18. package/bin/commands/add.js +0 -533
  19. package/bin/commands/add.test.js +0 -77
  20. package/bin/commands/changelog.js +0 -443
  21. package/bin/commands/pull.js +0 -287
  22. package/bin/commands/pull.test.js +0 -36
  23. package/bin/commands/push.js +0 -231
  24. package/bin/commands/push.test.js +0 -14
  25. package/bin/commands/search.js +0 -344
  26. package/bin/commands/search.test.js +0 -115
  27. package/bin/commands/setup.js +0 -545
  28. package/bin/commands/setup.test.js +0 -46
  29. package/bin/commands/sync-profile.js +0 -405
  30. package/bin/commands/sync-profile.test.js +0 -14
  31. package/bin/commands/sync-source.js +0 -418
  32. package/bin/commands/sync-source.test.js +0 -14
  33. package/bin/utils/errors.js +0 -159
  34. package/bin/utils/git.js +0 -298
  35. package/bin/utils/logger.js +0 -142
  36. package/bin/utils/pbix.js +0 -305
  37. package/bin/utils/pbix.test.js +0 -37
  38. package/bin/utils/profiles.js +0 -312
  39. package/bin/utils/projects.js +0 -169
  40. package/bin/utils/readline.js +0 -206
  41. package/bin/utils/readline.test.js +0 -47
  42. package/docs/openrouter-free-models.md +0 -92
  43. package/library/examples/README.md +0 -151
  44. package/library/examples/finance-reporting/README.md +0 -351
  45. package/library/examples/finance-reporting/data-model.md +0 -267
  46. package/library/examples/finance-reporting/measures.dax +0 -557
  47. package/library/examples/hr-analytics/README.md +0 -371
  48. package/library/examples/hr-analytics/data-model.md +0 -315
  49. package/library/examples/hr-analytics/measures.dax +0 -460
  50. package/library/examples/marketing-analytics/README.md +0 -37
  51. package/library/examples/marketing-analytics/data-model.md +0 -62
  52. package/library/examples/marketing-analytics/measures.dax +0 -110
  53. package/library/examples/retail-analytics/README.md +0 -439
  54. package/library/examples/retail-analytics/data-model.md +0 -288
  55. package/library/examples/retail-analytics/measures.dax +0 -481
  56. package/library/examples/supply-chain/README.md +0 -37
  57. package/library/examples/supply-chain/data-model.md +0 -69
  58. package/library/examples/supply-chain/measures.dax +0 -77
  59. package/library/examples/udf-library/README.md +0 -228
  60. package/library/examples/udf-library/functions.dax +0 -571
  61. package/library/snippets/dax/README.md +0 -292
  62. package/library/snippets/dax/business-domains.md +0 -576
  63. package/library/snippets/dax/calculate-patterns.md +0 -276
  64. package/library/snippets/dax/calculation-groups.md +0 -489
  65. package/library/snippets/dax/error-handling.md +0 -495
  66. package/library/snippets/dax/iterators-and-aggregations.md +0 -474
  67. package/library/snippets/dax/kpis-and-metrics.md +0 -293
  68. package/library/snippets/dax/rankings-and-topn.md +0 -235
  69. package/library/snippets/dax/security-patterns.md +0 -413
  70. package/library/snippets/dax/text-and-formatting.md +0 -316
  71. package/library/snippets/dax/time-intelligence.md +0 -196
  72. package/library/snippets/dax/user-defined-functions.md +0 -477
  73. package/library/snippets/dax/virtual-tables.md +0 -546
  74. package/library/snippets/excel-formulas/README.md +0 -84
  75. package/library/snippets/excel-formulas/aggregations.md +0 -330
  76. package/library/snippets/excel-formulas/dates-and-times.md +0 -361
  77. package/library/snippets/excel-formulas/dynamic-arrays.md +0 -314
  78. package/library/snippets/excel-formulas/lookups.md +0 -169
  79. package/library/snippets/excel-formulas/text-functions.md +0 -363
  80. package/library/snippets/governance/naming-conventions.md +0 -97
  81. package/library/snippets/governance/review-checklists.md +0 -107
  82. package/library/snippets/power-query/README.md +0 -389
  83. package/library/snippets/power-query/api-integration.md +0 -707
  84. package/library/snippets/power-query/connections.md +0 -434
  85. package/library/snippets/power-query/data-cleaning.md +0 -298
  86. package/library/snippets/power-query/error-handling.md +0 -526
  87. package/library/snippets/power-query/parameters.md +0 -350
  88. package/library/snippets/power-query/performance.md +0 -506
  89. package/library/snippets/power-query/transformations.md +0 -330
  90. package/library/snippets/report-design/accessibility.md +0 -78
  91. package/library/snippets/report-design/chart-selection.md +0 -54
  92. package/library/snippets/report-design/layout-patterns.md +0 -87
  93. package/library/templates/data-models/README.md +0 -93
  94. package/library/templates/data-models/finance-model.md +0 -627
  95. package/library/templates/data-models/retail-star-schema.md +0 -473
  96. package/library/templates/excel/README.md +0 -83
  97. package/library/templates/excel/budget-tracker.md +0 -432
  98. package/library/templates/excel/data-entry-form.md +0 -533
  99. package/library/templates/power-bi/README.md +0 -72
  100. package/library/templates/power-bi/finance-report.md +0 -449
  101. package/library/templates/power-bi/kpi-scorecard.md +0 -461
  102. package/library/templates/power-bi/sales-dashboard.md +0 -281
  103. package/library/themes/excel/README.md +0 -436
  104. package/library/themes/power-bi/README.md +0 -271
  105. package/library/themes/power-bi/accessible.json +0 -307
  106. package/library/themes/power-bi/bi-superpowers-default.json +0 -858
  107. package/library/themes/power-bi/corporate-blue.json +0 -291
  108. package/library/themes/power-bi/dark-mode.json +0 -291
  109. package/library/themes/power-bi/minimal.json +0 -292
  110. package/library/themes/power-bi/print-friendly.json +0 -309
package/bin/utils/pbix.js DELETED
@@ -1,305 +0,0 @@
1
- /**
2
- * Power BI File Utilities for BI Agent Superpowers
3
- * =================================================
4
- *
5
- * Handles extraction and manipulation of Power BI files (.pbix, .pbip).
6
- *
7
- * Note: .pbix files are ZIP archives containing:
8
- * - DataModel (binary - Analysis Services database)
9
- * - Report/Layout (JSON)
10
- * - Connections (JSON)
11
- * - Metadata (XML)
12
- *
13
- * For TMDL extraction, we rely on Power BI Desktop's "Save as PBIP" feature
14
- * or the pbi-tools utility. Direct extraction from .pbix is complex.
15
- *
16
- * @module utils/pbix
17
- */
18
-
19
- const fs = require('fs');
20
- const path = require('path');
21
-
22
- /**
23
- * Supported file types and their handlers
24
- */
25
- const FILE_TYPES = {
26
- '.pbix': 'power-bi',
27
- '.pbip': 'power-bi-project',
28
- '.xlsx': 'excel',
29
- '.xlsm': 'excel-macro',
30
- };
31
-
32
- /**
33
- * Detect the type of BI file
34
- * @param {string} filePath - Path to the file
35
- * @returns {Object} File info object
36
- */
37
- function detectFileType(filePath) {
38
- const ext = path.extname(filePath).toLowerCase();
39
- const type = FILE_TYPES[ext] || 'unknown';
40
-
41
- return {
42
- path: filePath,
43
- extension: ext,
44
- type,
45
- name: path.basename(filePath, ext),
46
- exists: fs.existsSync(filePath),
47
- };
48
- }
49
-
50
- /**
51
- * Check if a file is a Power BI project (.pbip)
52
- * @param {string} filePath - Path to check
53
- * @returns {boolean} True if it's a PBIP project
54
- */
55
- function isPbipProject(filePath) {
56
- const ext = path.extname(filePath).toLowerCase();
57
- return ext === '.pbip';
58
- }
59
-
60
- /**
61
- * Get the TMDL definition folder for a PBIP project
62
- * @param {string} pbipPath - Path to .pbip file
63
- * @returns {string|null} Path to definition folder or null
64
- */
65
- function getPbipDefinitionPath(pbipPath) {
66
- const dir = path.dirname(pbipPath);
67
- const name = path.basename(pbipPath, '.pbip');
68
-
69
- // PBIP structure: Project.pbip + Project.SemanticModel/definition/
70
- const semanticModelDir = path.join(dir, `${name}.SemanticModel`);
71
- const definitionDir = path.join(semanticModelDir, 'definition');
72
-
73
- if (fs.existsSync(definitionDir)) {
74
- return definitionDir;
75
- }
76
-
77
- // Alternative structure: definition folder next to .pbip
78
- const altDefinitionDir = path.join(dir, 'definition');
79
- if (fs.existsSync(altDefinitionDir)) {
80
- return altDefinitionDir;
81
- }
82
-
83
- return null;
84
- }
85
-
86
- /**
87
- * Copy PBIP definition to the repo project folder
88
- * @param {string} pbipPath - Path to .pbip file
89
- * @param {string} projectDir - Target project directory in repo
90
- * @returns {Object} Result with success status and copied files
91
- */
92
- function extractPbipToRepo(pbipPath, projectDir) {
93
- const result = {
94
- success: false,
95
- files: [],
96
- error: null,
97
- };
98
-
99
- const definitionPath = getPbipDefinitionPath(pbipPath);
100
-
101
- if (!definitionPath) {
102
- result.error = 'Could not find TMDL definition folder for PBIP project';
103
- return result;
104
- }
105
-
106
- const targetDir = path.join(projectDir, 'definition');
107
-
108
- try {
109
- // Create target directory
110
- if (!fs.existsSync(targetDir)) {
111
- fs.mkdirSync(targetDir, { recursive: true });
112
- }
113
-
114
- // Copy all files recursively
115
- result.files = copyDirectoryRecursive(definitionPath, targetDir);
116
- result.success = true;
117
- } catch (e) {
118
- result.error = e.message;
119
- }
120
-
121
- return result;
122
- }
123
-
124
- /**
125
- * Copy directory recursively
126
- * @param {string} src - Source directory
127
- * @param {string} dest - Destination directory
128
- * @returns {string[]} List of copied files (relative paths)
129
- */
130
- function copyDirectoryRecursive(src, dest, baseSrc = src) {
131
- const copied = [];
132
-
133
- if (!fs.existsSync(dest)) {
134
- fs.mkdirSync(dest, { recursive: true });
135
- }
136
-
137
- const entries = fs.readdirSync(src, { withFileTypes: true });
138
-
139
- for (const entry of entries) {
140
- const srcPath = path.join(src, entry.name);
141
- const destPath = path.join(dest, entry.name);
142
-
143
- if (entry.isDirectory()) {
144
- copied.push(...copyDirectoryRecursive(srcPath, destPath, baseSrc));
145
- } else {
146
- fs.copyFileSync(srcPath, destPath);
147
- copied.push(path.relative(baseSrc, srcPath));
148
- }
149
- }
150
-
151
- return copied;
152
- }
153
-
154
- /**
155
- * Get file hash for change detection
156
- * @param {string} filePath - Path to file
157
- * @returns {string} Hash string
158
- */
159
- function getFileHash(filePath) {
160
- if (!fs.existsSync(filePath)) {
161
- return '';
162
- }
163
-
164
- const crypto = require('crypto');
165
- const content = fs.readFileSync(filePath);
166
- return crypto.createHash('md5').update(content).digest('hex');
167
- }
168
-
169
- /**
170
- * Get last modified time of a file
171
- * @param {string} filePath - Path to file
172
- * @returns {Date|null} Last modified date or null
173
- */
174
- function getLastModified(filePath) {
175
- if (!fs.existsSync(filePath)) {
176
- return null;
177
- }
178
-
179
- const stats = fs.statSync(filePath);
180
- return stats.mtime;
181
- }
182
-
183
- /**
184
- * Compare two directories and find differences
185
- * @param {string} dir1 - First directory
186
- * @param {string} dir2 - Second directory
187
- * @returns {Object} Differences object
188
- */
189
- function compareDirectories(dir1, dir2) {
190
- const diff = {
191
- added: [],
192
- modified: [],
193
- deleted: [],
194
- };
195
-
196
- const files1 = getAllFiles(dir1);
197
- const files2 = getAllFiles(dir2);
198
-
199
- const set1 = new Set(files1.map((f) => path.relative(dir1, f)));
200
- const set2 = new Set(files2.map((f) => path.relative(dir2, f)));
201
-
202
- // Find added (in dir2 but not dir1)
203
- for (const file of set2) {
204
- if (!set1.has(file)) {
205
- diff.added.push(file);
206
- }
207
- }
208
-
209
- // Find deleted (in dir1 but not dir2)
210
- for (const file of set1) {
211
- if (!set2.has(file)) {
212
- diff.deleted.push(file);
213
- }
214
- }
215
-
216
- // Find modified (in both but different)
217
- for (const file of set1) {
218
- if (set2.has(file)) {
219
- const path1 = path.join(dir1, file);
220
- const path2 = path.join(dir2, file);
221
-
222
- if (getFileHash(path1) !== getFileHash(path2)) {
223
- diff.modified.push(file);
224
- }
225
- }
226
- }
227
-
228
- return diff;
229
- }
230
-
231
- /**
232
- * Get all files in a directory recursively
233
- * @param {string} dir - Directory to scan
234
- * @returns {string[]} Array of file paths
235
- */
236
- function getAllFiles(dir) {
237
- const files = [];
238
-
239
- if (!fs.existsSync(dir)) {
240
- return files;
241
- }
242
-
243
- const entries = fs.readdirSync(dir, { withFileTypes: true });
244
-
245
- for (const entry of entries) {
246
- const fullPath = path.join(dir, entry.name);
247
-
248
- if (entry.isDirectory()) {
249
- files.push(...getAllFiles(fullPath));
250
- } else {
251
- files.push(fullPath);
252
- }
253
- }
254
-
255
- return files;
256
- }
257
-
258
- /**
259
- * Create project.json for a new project
260
- * @param {Object} options - Project options
261
- * @returns {Object} Project configuration object
262
- */
263
- function createProjectConfig(options) {
264
- return {
265
- name: options.name,
266
- displayName: options.displayName || options.name,
267
- type: options.type || 'power-bi',
268
- profile: options.profile || 'default',
269
- source: {
270
- path: options.sourcePath,
271
- lastSync: new Date().toISOString(),
272
- hash: options.sourcePath ? getFileHash(options.sourcePath) : '',
273
- },
274
- created: new Date().toISOString(),
275
- description: options.description || '',
276
- };
277
- }
278
-
279
- /**
280
- * Generate a slug from a display name
281
- * @param {string} name - Display name
282
- * @returns {string} URL-friendly slug
283
- */
284
- function generateSlug(name) {
285
- return name
286
- .toLowerCase()
287
- .replace(/[^a-z0-9]+/g, '-')
288
- .replace(/^-+|-+$/g, '')
289
- .substring(0, 50);
290
- }
291
-
292
- module.exports = {
293
- FILE_TYPES,
294
- detectFileType,
295
- isPbipProject,
296
- getPbipDefinitionPath,
297
- extractPbipToRepo,
298
- copyDirectoryRecursive,
299
- getFileHash,
300
- getLastModified,
301
- compareDirectories,
302
- getAllFiles,
303
- createProjectConfig,
304
- generateSlug,
305
- };
@@ -1,37 +0,0 @@
1
- /**
2
- * Tests for Power BI file utilities
3
- * @module utils/pbix.test
4
- */
5
-
6
- const test = require('node:test');
7
- const assert = require('node:assert/strict');
8
- const fs = require('fs');
9
- const os = require('os');
10
- const path = require('path');
11
-
12
- const pbix = require('./pbix');
13
-
14
- function makeTempDir() {
15
- return fs.mkdtempSync(path.join(os.tmpdir(), 'bi-superpowers-pbix-'));
16
- }
17
-
18
- test('copyDirectoryRecursive returns relative paths for nested files', () => {
19
- const dir = makeTempDir();
20
-
21
- const src = path.join(dir, 'src');
22
- const dest = path.join(dir, 'dest');
23
-
24
- fs.mkdirSync(path.join(src, 'subdir'), { recursive: true });
25
- fs.writeFileSync(path.join(src, 'root.txt'), 'root');
26
- fs.writeFileSync(path.join(src, 'subdir', 'nested.txt'), 'nested');
27
-
28
- const copied = pbix.copyDirectoryRecursive(src, dest);
29
-
30
- // Returned paths should be relative to the source root
31
- assert.ok(copied.includes('root.txt'));
32
- assert.ok(copied.includes(path.join('subdir', 'nested.txt')));
33
-
34
- // Files should exist in destination with the same structure
35
- assert.equal(fs.readFileSync(path.join(dest, 'root.txt'), 'utf8'), 'root');
36
- assert.equal(fs.readFileSync(path.join(dest, 'subdir', 'nested.txt'), 'utf8'), 'nested');
37
- });
@@ -1,312 +0,0 @@
1
- /**
2
- * Profile Management Utilities for BI Agent Superpowers
3
- * =====================================================
4
- *
5
- * Handles profile configuration, inheritance, and management.
6
- * Profiles allow users to have different sets of snippets for
7
- * different industries (finance, retail, healthcare, etc.)
8
- *
9
- * @module utils/profiles
10
- */
11
-
12
- const fs = require('fs');
13
- const path = require('path');
14
- const os = require('os');
15
-
16
- /** Base directory for BI Superpowers global config */
17
- const BASE_CONFIG_DIR = path.join(os.homedir(), '.bi-superpowers');
18
-
19
- /** Path to global config file */
20
- const CONFIG_FILE = path.join(BASE_CONFIG_DIR, 'config.json');
21
-
22
- /** Path to profiles directory */
23
- const PROFILES_DIR = path.join(BASE_CONFIG_DIR, 'profiles');
24
-
25
- /**
26
- * Default configuration structure
27
- */
28
- const DEFAULT_CONFIG = {
29
- version: '3.0',
30
- defaultProfile: 'default',
31
- profiles: {},
32
- repoPath: null,
33
- };
34
-
35
- /**
36
- * Ensure the base config directory exists
37
- */
38
- function ensureConfigDir() {
39
- if (!fs.existsSync(BASE_CONFIG_DIR)) {
40
- fs.mkdirSync(BASE_CONFIG_DIR, { recursive: true });
41
- }
42
- if (!fs.existsSync(PROFILES_DIR)) {
43
- fs.mkdirSync(PROFILES_DIR, { recursive: true });
44
- }
45
- }
46
-
47
- /**
48
- * Load the global configuration
49
- * @returns {Object} Configuration object
50
- */
51
- function loadConfig() {
52
- ensureConfigDir();
53
-
54
- if (fs.existsSync(CONFIG_FILE)) {
55
- try {
56
- const data = fs.readFileSync(CONFIG_FILE, 'utf8');
57
- return { ...DEFAULT_CONFIG, ...JSON.parse(data) };
58
- } catch (e) {
59
- return { ...DEFAULT_CONFIG };
60
- }
61
- }
62
-
63
- return { ...DEFAULT_CONFIG };
64
- }
65
-
66
- /**
67
- * Save the global configuration
68
- * @param {Object} config - Configuration to save
69
- */
70
- function saveConfig(config) {
71
- ensureConfigDir();
72
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
73
- }
74
-
75
- /**
76
- * Get the path to the user's bi-repo
77
- * @returns {string|null} Path to bi-repo or null if not configured
78
- */
79
- function getRepoPath() {
80
- const config = loadConfig();
81
- return config.repoPath;
82
- }
83
-
84
- /**
85
- * Set the path to the user's bi-repo
86
- * @param {string} repoPath - Path to the bi-repo
87
- */
88
- function setRepoPath(repoPath) {
89
- const config = loadConfig();
90
- config.repoPath = repoPath;
91
- saveConfig(config);
92
- }
93
-
94
- /**
95
- * Get list of available profiles
96
- * @returns {string[]} Array of profile names
97
- */
98
- function listProfiles() {
99
- ensureConfigDir();
100
-
101
- if (!fs.existsSync(PROFILES_DIR)) {
102
- return ['default'];
103
- }
104
-
105
- const profiles = fs
106
- .readdirSync(PROFILES_DIR)
107
- .filter((f) => fs.statSync(path.join(PROFILES_DIR, f)).isDirectory());
108
-
109
- // Always include default
110
- if (!profiles.includes('default')) {
111
- profiles.unshift('default');
112
- }
113
-
114
- return profiles;
115
- }
116
-
117
- /**
118
- * Check if a profile exists
119
- * @param {string} profileName - Name of the profile
120
- * @returns {boolean} True if profile exists
121
- */
122
- function profileExists(profileName) {
123
- const profilePath = path.join(PROFILES_DIR, profileName);
124
- return fs.existsSync(profilePath);
125
- }
126
-
127
- /**
128
- * Create a new profile
129
- * @param {string} profileName - Name of the new profile
130
- * @param {Object} options - Options for the profile
131
- * @param {string} options.inheritsFrom - Parent profile name
132
- * @returns {boolean} True if successful
133
- */
134
- function createProfile(profileName, options = {}) {
135
- ensureConfigDir();
136
-
137
- const profilePath = path.join(PROFILES_DIR, profileName);
138
-
139
- if (fs.existsSync(profilePath)) {
140
- return false;
141
- }
142
-
143
- // Create profile directory structure
144
- fs.mkdirSync(profilePath, { recursive: true });
145
- fs.mkdirSync(path.join(profilePath, 'snippets'), { recursive: true });
146
-
147
- // Update config with inheritance
148
- const config = loadConfig();
149
- if (options.inheritsFrom) {
150
- config.profiles[profileName] = { inheritsFrom: options.inheritsFrom };
151
- } else {
152
- config.profiles[profileName] = {};
153
- }
154
- saveConfig(config);
155
-
156
- return true;
157
- }
158
-
159
- /**
160
- * Get the inheritance chain for a profile
161
- * @param {string} profileName - Name of the profile
162
- * @returns {string[]} Array of profile names from most specific to base
163
- */
164
- function getInheritanceChain(profileName) {
165
- const config = loadConfig();
166
- const chain = [profileName];
167
-
168
- let current = profileName;
169
- const visited = new Set([profileName]);
170
-
171
- while (config.profiles[current]?.inheritsFrom) {
172
- const parent = config.profiles[current].inheritsFrom;
173
-
174
- // Prevent circular dependencies
175
- if (visited.has(parent)) {
176
- break;
177
- }
178
-
179
- chain.push(parent);
180
- visited.add(parent);
181
- current = parent;
182
- }
183
-
184
- return chain;
185
- }
186
-
187
- /**
188
- * Get all snippets for a profile, including inherited ones
189
- * @param {string} profileName - Name of the profile
190
- * @returns {Object[]} Array of snippet objects with path and profile
191
- */
192
- function getProfileSnippets(profileName) {
193
- const chain = getInheritanceChain(profileName);
194
- const snippets = [];
195
- const seen = new Set();
196
-
197
- // Process from most specific to least specific
198
- for (const profile of chain) {
199
- const profilePath = path.join(PROFILES_DIR, profile, 'snippets');
200
-
201
- if (!fs.existsSync(profilePath)) {
202
- continue;
203
- }
204
-
205
- // Recursively find all .md files
206
- const files = findMdFiles(profilePath);
207
-
208
- for (const file of files) {
209
- const relativePath = path.relative(profilePath, file);
210
-
211
- // Skip if we already have this snippet from a more specific profile
212
- if (!seen.has(relativePath)) {
213
- seen.add(relativePath);
214
- snippets.push({
215
- path: file,
216
- relativePath,
217
- profile,
218
- });
219
- }
220
- }
221
- }
222
-
223
- return snippets;
224
- }
225
-
226
- /**
227
- * Recursively find all .md files in a directory
228
- * @param {string} dir - Directory to search
229
- * @returns {string[]} Array of file paths
230
- */
231
- function findMdFiles(dir) {
232
- const files = [];
233
-
234
- if (!fs.existsSync(dir)) {
235
- return files;
236
- }
237
-
238
- const entries = fs.readdirSync(dir, { withFileTypes: true });
239
-
240
- for (const entry of entries) {
241
- const fullPath = path.join(dir, entry.name);
242
-
243
- if (entry.isDirectory()) {
244
- files.push(...findMdFiles(fullPath));
245
- } else if (entry.isFile() && entry.name.endsWith('.md')) {
246
- files.push(fullPath);
247
- }
248
- }
249
-
250
- return files;
251
- }
252
-
253
- /**
254
- * Copy a snippet to a profile
255
- * @param {string} sourcePath - Path to the source snippet
256
- * @param {string} profileName - Target profile name
257
- * @param {string} relativePath - Relative path within snippets folder
258
- * @returns {boolean} True if successful
259
- */
260
- function copySnippetToProfile(sourcePath, profileName, relativePath) {
261
- const targetPath = path.join(PROFILES_DIR, profileName, 'snippets', relativePath);
262
- const targetDir = path.dirname(targetPath);
263
-
264
- if (!fs.existsSync(targetDir)) {
265
- fs.mkdirSync(targetDir, { recursive: true });
266
- }
267
-
268
- try {
269
- fs.copyFileSync(sourcePath, targetPath);
270
- return true;
271
- } catch (e) {
272
- return false;
273
- }
274
- }
275
-
276
- /**
277
- * Get the default profile name
278
- * @returns {string} Default profile name
279
- */
280
- function getDefaultProfile() {
281
- const config = loadConfig();
282
- return config.defaultProfile || 'default';
283
- }
284
-
285
- /**
286
- * Set the default profile
287
- * @param {string} profileName - Profile to set as default
288
- */
289
- function setDefaultProfile(profileName) {
290
- const config = loadConfig();
291
- config.defaultProfile = profileName;
292
- saveConfig(config);
293
- }
294
-
295
- module.exports = {
296
- BASE_CONFIG_DIR,
297
- PROFILES_DIR,
298
- ensureConfigDir,
299
- loadConfig,
300
- saveConfig,
301
- getRepoPath,
302
- setRepoPath,
303
- listProfiles,
304
- profileExists,
305
- createProfile,
306
- getInheritanceChain,
307
- getProfileSnippets,
308
- findMdFiles,
309
- copySnippetToProfile,
310
- getDefaultProfile,
311
- setDefaultProfile,
312
- };