@qelos/plugins-cli 0.0.13 → 0.0.14

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/cli.mjs CHANGED
@@ -9,6 +9,7 @@ import process from 'node:process';
9
9
  import createCommand from './commands/create.mjs';
10
10
  import pushCommand from './commands/push.mjs';
11
11
  import pullCommand from './commands/pull.mjs';
12
+ import generateCommand from './commands/generate.mjs';
12
13
  const __dirname = dirname(fileURLToPath(import.meta.url));
13
14
 
14
15
  const program = yargs(hideBin(process.argv));
@@ -24,5 +25,6 @@ program.option('verbose', {
24
25
  createCommand(program)
25
26
  pushCommand(program)
26
27
  pullCommand(program)
28
+ generateCommand(program)
27
29
 
28
30
  program.help().argv;
@@ -0,0 +1,15 @@
1
+ import generateController from "../controllers/generate.mjs";
2
+
3
+ export default function generateCommand(program) {
4
+ program
5
+ .command('generate rules <type>', 'Generate IDE-specific rules files for working with pulled Qelos resources.',
6
+ (yargs) => {
7
+ return yargs
8
+ .positional('type', {
9
+ describe: 'Type of IDE rules to generate. Can be windsurf, cursor, claude, or all.',
10
+ type: 'string',
11
+ choices: ['windsurf', 'cursor', 'claude', 'all']
12
+ })
13
+ },
14
+ generateController)
15
+ }
@@ -0,0 +1,34 @@
1
+ import { generateRules } from '../services/generate-rules.mjs';
2
+ import { logger } from '../services/logger.mjs';
3
+ import path from 'node:path';
4
+ import fs from 'node:fs';
5
+
6
+ export default async function generateController({ type }) {
7
+ try {
8
+ const cwd = process.cwd();
9
+
10
+ logger.section(`Generating ${type} rules for ${cwd}`);
11
+
12
+ // Determine which IDE types to generate
13
+ const ideTypes = type === 'all'
14
+ ? ['windsurf', 'cursor', 'claude']
15
+ : [type];
16
+
17
+ for (const ideType of ideTypes) {
18
+ logger.step(`Generating ${ideType} rules...`);
19
+ const result = await generateRules(ideType, cwd);
20
+
21
+ if (result.success) {
22
+ logger.success(`Generated ${ideType} rules at: ${result.filePath}`);
23
+ } else {
24
+ logger.warning(`Skipped ${ideType}: ${result.message}`);
25
+ }
26
+ }
27
+
28
+ logger.success(`Rules generation completed`);
29
+
30
+ } catch (error) {
31
+ logger.error(`Failed to generate rules`, error);
32
+ process.exit(1);
33
+ }
34
+ }
@@ -8,7 +8,7 @@ import { logger } from '../services/logger.mjs';
8
8
  import fs from 'node:fs';
9
9
  import path from 'node:path';
10
10
 
11
- export default async function pullController({ type, path: targetPath }) {
11
+ export default async function pullController({ type, path: targetPath = './' }) {
12
12
  try {
13
13
  // Validate parent directory exists
14
14
  const parentDir = path.dirname(targetPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qelos/plugins-cli",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
4
4
  "description": "CLI to manage QELOS plugins",
5
5
  "main": "cli.mjs",
6
6
  "bin": {
@@ -0,0 +1,667 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { logger } from './logger.mjs';
4
+
5
+ /**
6
+ * Fetch Qelos global components from documentation
7
+ * @returns {Promise<Object>} Object with components and directives
8
+ */
9
+ async function fetchQelosGlobalComponents() {
10
+ try {
11
+ const response = await fetch('https://docs.qelos.io/pre-designed-frontends/components/');
12
+ if (!response.ok) {
13
+ logger.debug('Failed to fetch Qelos components documentation');
14
+ return null;
15
+ }
16
+
17
+ const html = await response.text();
18
+
19
+ // Extract component names and descriptions from the HTML
20
+ const components = [];
21
+ const directives = [];
22
+
23
+ // Documented components with links and descriptions
24
+ const documentedComponents = [
25
+ { name: 'ai-chat', description: 'Complete AI chat interface with streaming, file attachments, and customizable UI' },
26
+ { name: 'form-input', description: 'Input component for forms' },
27
+ { name: 'form-row-group', description: 'Group form inputs in rows' },
28
+ { name: 'save-button', description: 'Button for saving forms' },
29
+ { name: 'monaco', description: 'Code editor component' },
30
+ { name: 'quick-table', description: 'Simplified table component' },
31
+ { name: 'v-chart', description: 'Chart visualization component' },
32
+ { name: 'content-box', description: 'Component that loads HTML content blocks from the database' },
33
+ { name: 'copy-to-clipboard', description: 'Button to copy content to clipboard' },
34
+ { name: 'empty-state', description: 'Component for empty state display' },
35
+ { name: 'life-cycle', description: 'Component for displaying lifecycle stages' },
36
+ { name: 'q-pre', description: 'Pre-formatted text component with HTML escaping and line break handling' }
37
+ ];
38
+
39
+ // Other available components
40
+ const otherComponents = [
41
+ { name: 'edit-header', description: 'Header for edit pages' },
42
+ { name: 'info-icon', description: 'Icon with tooltip information' },
43
+ { name: 'block-item', description: 'Block container for content' },
44
+ { name: 'list-page-title', description: 'Title component for list pages' },
45
+ { name: 'general-form', description: 'Generic form component' },
46
+ { name: 'blueprint-entity-form', description: 'Form for blueprint entities' },
47
+ { name: 'confirm-message', description: 'Confirmation dialog component' },
48
+ { name: 'remove-button', description: 'Button for deletion actions' },
49
+ { name: 'editable-content', description: 'Content that can be edited inline' },
50
+ { name: 'remove-confirmation', description: 'Confirmation dialog for delete actions' },
51
+ { name: 'stats-card', description: 'Card for displaying statistics' },
52
+ { name: 'q-rating', description: 'Rating component' }
53
+ ];
54
+
55
+ components.push(...documentedComponents, ...otherComponents);
56
+
57
+ // Directives
58
+ directives.push({ name: 'v-loading', description: 'Adds loading state to an element' });
59
+
60
+ return { components, directives };
61
+ } catch (error) {
62
+ logger.debug(`Failed to fetch Qelos components: ${error.message}`);
63
+ return null;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Scan directory for pulled resources
69
+ * @param {string} basePath - Base path to scan
70
+ * @returns {Object} Object containing found resources
71
+ */
72
+ function scanPulledResources(basePath) {
73
+ const resources = {
74
+ components: null,
75
+ blocks: null,
76
+ blueprints: [],
77
+ plugins: [],
78
+ microFrontends: []
79
+ };
80
+
81
+ // Check for components directory and components.json
82
+ const componentsPath = path.join(basePath, 'components');
83
+ const componentsJsonPath = path.join(componentsPath, 'components.json');
84
+ if (fs.existsSync(componentsJsonPath)) {
85
+ try {
86
+ resources.components = {
87
+ path: componentsPath,
88
+ metadata: JSON.parse(fs.readFileSync(componentsJsonPath, 'utf-8'))
89
+ };
90
+ } catch (error) {
91
+ logger.debug(`Failed to parse components.json: ${error.message}`);
92
+ }
93
+ }
94
+
95
+ // Check for blocks directory and blocks.json
96
+ const blocksPath = path.join(basePath, 'blocks');
97
+ const blocksJsonPath = path.join(blocksPath, 'blocks.json');
98
+ if (fs.existsSync(blocksJsonPath)) {
99
+ try {
100
+ resources.blocks = {
101
+ path: blocksPath,
102
+ metadata: JSON.parse(fs.readFileSync(blocksJsonPath, 'utf-8'))
103
+ };
104
+ } catch (error) {
105
+ logger.debug(`Failed to parse blocks.json: ${error.message}`);
106
+ }
107
+ }
108
+
109
+ // Check for blueprints directory
110
+ const blueprintsPath = path.join(basePath, 'blueprints');
111
+ if (fs.existsSync(blueprintsPath)) {
112
+ const blueprintFiles = fs.readdirSync(blueprintsPath)
113
+ .filter(f => f.endsWith('.blueprint.json'));
114
+
115
+ for (const file of blueprintFiles) {
116
+ try {
117
+ const blueprint = JSON.parse(
118
+ fs.readFileSync(path.join(blueprintsPath, file), 'utf-8')
119
+ );
120
+ resources.blueprints.push({
121
+ file,
122
+ identifier: blueprint.identifier,
123
+ name: blueprint.name,
124
+ properties: blueprint.properties,
125
+ relations: blueprint.relations,
126
+ dispatchers: blueprint.dispatchers
127
+ });
128
+ } catch (error) {
129
+ logger.debug(`Failed to parse ${file}: ${error.message}`);
130
+ }
131
+ }
132
+ }
133
+
134
+ // Check for plugins directory
135
+ const pluginsPath = path.join(basePath, 'plugins');
136
+ if (fs.existsSync(pluginsPath)) {
137
+ const pluginFiles = fs.readdirSync(pluginsPath)
138
+ .filter(f => f.endsWith('.plugin.json'));
139
+
140
+ for (const file of pluginFiles) {
141
+ try {
142
+ const plugin = JSON.parse(
143
+ fs.readFileSync(path.join(pluginsPath, file), 'utf-8')
144
+ );
145
+ resources.plugins.push({
146
+ file,
147
+ apiPath: plugin.apiPath,
148
+ name: plugin.name,
149
+ microFrontends: plugin.microFrontends || [],
150
+ injectables: plugin.injectables || [],
151
+ navBarGroups: plugin.navBarGroups || []
152
+ });
153
+
154
+ // Check for micro-frontends directory
155
+ const microFrontendsDir = path.join(pluginsPath, 'micro-frontends');
156
+ if (fs.existsSync(microFrontendsDir)) {
157
+ const htmlFiles = fs.readdirSync(microFrontendsDir)
158
+ .filter(f => f.endsWith('.html'));
159
+
160
+ for (const htmlFile of htmlFiles) {
161
+ resources.microFrontends.push({
162
+ file: htmlFile,
163
+ pluginApiPath: plugin.apiPath,
164
+ path: path.join(microFrontendsDir, htmlFile)
165
+ });
166
+ }
167
+ }
168
+ } catch (error) {
169
+ logger.debug(`Failed to parse ${file}: ${error.message}`);
170
+ }
171
+ }
172
+ }
173
+
174
+ return resources;
175
+ }
176
+
177
+ /**
178
+ * Generate rules content based on scanned resources
179
+ * @param {Object} resources - Scanned resources
180
+ * @param {string} ideType - Type of IDE (windsurf, cursor, claude)
181
+ * @param {Object} qelosComponents - Qelos global components from docs
182
+ * @returns {string} Generated rules content
183
+ */
184
+ function generateRulesContent(resources, ideType, qelosComponents) {
185
+ const sections = [];
186
+
187
+ // Header
188
+ sections.push(`# Qelos Project Rules for ${ideType.charAt(0).toUpperCase() + ideType.slice(1)}`);
189
+ sections.push('');
190
+ sections.push('This file contains rules to help you work with pulled Qelos resources.');
191
+ sections.push('Generated automatically by the Qelos CLI.');
192
+ sections.push('');
193
+
194
+ // Components section
195
+ if (resources.components) {
196
+ sections.push('## Components');
197
+ sections.push('');
198
+ sections.push('### Component Structure');
199
+ sections.push('- Components are Vue 3.5 Single File Components (.vue files)');
200
+ sections.push('- Each component has metadata stored in `components.json`');
201
+ sections.push('- Use Composition API with `<script setup>` syntax');
202
+ sections.push('- Components can use Vue Router, Element Plus, and Vue I18n');
203
+ sections.push('');
204
+ sections.push('### Available Libraries & Documentation');
205
+ sections.push('- **Vue 3**: https://vuejs.org/api/');
206
+ sections.push('- **Vue Router**: https://router.vuejs.org/api/');
207
+ sections.push('- **Element Plus**: https://element-plus.org/en-US/component/overview.html');
208
+ sections.push('- **Vue I18n**: https://vue-i18n.intlify.dev/api/general.html');
209
+ sections.push('- **Pinia**: https://pinia.vuejs.org/api/');
210
+ sections.push('');
211
+ sections.push('### Qelos SDK Access');
212
+ sections.push('Components have access to the Qelos SDK instance via the `@sdk` alias:');
213
+ sections.push('```javascript');
214
+ sections.push('import sdk from "@sdk";');
215
+ sections.push('');
216
+ sections.push('// sdk is an instance of QelosAdministratorSDK');
217
+ sections.push('// Available methods include:');
218
+ sections.push('// - sdk.manageComponents');
219
+ sections.push('// - sdk.manageBlueprints');
220
+ sections.push('// - sdk.managePlugins');
221
+ sections.push('// - sdk.manageConfigurations');
222
+ sections.push('// - And more...');
223
+ sections.push('```');
224
+ sections.push('');
225
+ sections.push('**SDK Documentation**: https://docs.qelos.io/sdk/sdk');
226
+ sections.push('');
227
+ sections.push('### Qelos Global Components');
228
+ sections.push('All components and HTML templates can use Qelos pre-designed components **without importing them**.');
229
+ sections.push('These components are globally registered and available everywhere in kebab-case format.');
230
+ sections.push('');
231
+ sections.push('**Documentation**: https://docs.qelos.io/pre-designed-frontends/components/');
232
+ sections.push('');
233
+
234
+ if (qelosComponents && qelosComponents.components && qelosComponents.components.length > 0) {
235
+ sections.push('**Available Components:**');
236
+ qelosComponents.components.forEach(comp => {
237
+ sections.push(`- \`<${comp.name}>\` - ${comp.description}`);
238
+ });
239
+ sections.push('');
240
+
241
+ if (qelosComponents.directives && qelosComponents.directives.length > 0) {
242
+ sections.push('**Available Directives:**');
243
+ qelosComponents.directives.forEach(dir => {
244
+ sections.push(`- \`${dir.name}\` - ${dir.description}`);
245
+ });
246
+ sections.push('');
247
+ }
248
+ } else {
249
+ // Fallback if fetch failed
250
+ sections.push('**Note**: Visit the documentation link above for the complete list of available components.');
251
+ sections.push('');
252
+ }
253
+
254
+ sections.push('**Usage Example:**');
255
+ sections.push('```html');
256
+ sections.push('<form-input label="Name" v-model="name"></form-input>');
257
+ sections.push('<save-button @click="saveData"></save-button>');
258
+ sections.push('<content-box title="User Information">Content goes here</content-box>');
259
+ sections.push('<div v-loading="isLoading">Loading content...</div>');
260
+ sections.push('```');
261
+ sections.push('');
262
+ sections.push('**Important**: All components must use kebab-case and have closing tags.');
263
+ sections.push('');
264
+ sections.push('### Component Metadata Mapping');
265
+ sections.push('The `components.json` file maps component names to their metadata:');
266
+ sections.push('```json');
267
+ sections.push('{');
268
+ sections.push(' "VideoPlayer": {');
269
+ sections.push(' "_id": "507f1f77bcf86cd799439011",');
270
+ sections.push(' "componentName": "VideoPlayer",');
271
+ sections.push(' "identifier": "video-player-component",');
272
+ sections.push(' "description": "A reusable video player component"');
273
+ sections.push(' },');
274
+ sections.push(' "UserProfile": {');
275
+ sections.push(' "_id": "507f191e810c19729de860ea",');
276
+ sections.push(' "componentName": "UserProfile",');
277
+ sections.push(' "identifier": "user-profile-component",');
278
+ sections.push(' "description": "Displays user profile information"');
279
+ sections.push(' }');
280
+ sections.push('}');
281
+ sections.push('```');
282
+ sections.push('');
283
+ sections.push('### Component Naming Convention');
284
+ sections.push('Components follow Vue naming conventions:');
285
+ sections.push('- **File names**: PascalCase (e.g., `ProductCard.vue`, `VideoPlayer.vue`)');
286
+ sections.push('- **Template usage**: kebab-case (e.g., `<product-card>`, `<video-player>`)');
287
+ sections.push('- **Conversion**: PascalCase file names are automatically converted to kebab-case in templates');
288
+ sections.push('');
289
+ sections.push('**Example mapping:**');
290
+ sections.push('```');
291
+ sections.push('ProductCard.vue → <product-card>');
292
+ sections.push('VideoPlayer.vue → <video-player>');
293
+ sections.push('UserProfile.vue → <user-profile>');
294
+ sections.push('DataTable.vue → <data-table>');
295
+ sections.push('```');
296
+ sections.push('');
297
+ sections.push('When you see a component used in HTML/templates like `<product-card>`,');
298
+ sections.push('the actual component file is `ProductCard.vue` in the components directory.');
299
+ sections.push('');
300
+ sections.push('### Working with Components');
301
+ sections.push('- When modifying a component, update both the `.vue` file and its entry in `components.json`');
302
+ sections.push('- The `_id` field in `components.json` is used to sync with the remote Qelos instance');
303
+ sections.push('- Component files are named exactly as their `componentName` property (e.g., `VideoPlayer.vue`)');
304
+ sections.push('- Use `qelos-cli push components <path>` to push changes back to Qelos');
305
+ sections.push('');
306
+ }
307
+
308
+ // Blocks section
309
+ if (resources.blocks) {
310
+ sections.push('## Blocks');
311
+ sections.push('');
312
+ sections.push('### Block Structure');
313
+ sections.push('- Blocks are HTML template files (.html files)');
314
+ sections.push('- Each block has metadata stored in `blocks.json`');
315
+ sections.push('- Blocks are used as reusable HTML templates in the Qelos platform');
316
+ sections.push('');
317
+ sections.push('### IMPORTANT: Block Limitations');
318
+ sections.push('**Blocks CANNOT contain `<script>` tags or JavaScript code.**');
319
+ sections.push('');
320
+ sections.push('To add JavaScript functionality to a block:');
321
+ sections.push('1. Create a new Vue component in the `components/` folder');
322
+ sections.push('2. Implement your JavaScript logic in the component');
323
+ sections.push('3. Use the component in your block HTML using kebab-case with closing tags');
324
+ sections.push('4. No import statement is needed - components are globally available');
325
+ sections.push('');
326
+ sections.push('**Example:**');
327
+ sections.push('```html');
328
+ sections.push('<!-- ❌ WRONG: Do not add <script> tags in blocks -->');
329
+ sections.push('<div id="my-element"></div>');
330
+ sections.push('<script>');
331
+ sections.push(' document.getElementById("my-element").addEventListener("click", ...);');
332
+ sections.push('</script>');
333
+ sections.push('');
334
+ sections.push('<!-- ✅ CORRECT: Create a component and use it -->');
335
+ sections.push('<!-- First create components/InteractiveButton.vue with your logic -->');
336
+ sections.push('<interactive-button></interactive-button>');
337
+ sections.push('```');
338
+ sections.push('');
339
+ sections.push('Blocks can contain:');
340
+ sections.push('- HTML markup');
341
+ sections.push('- CSS in `<style>` tags');
342
+ sections.push('- Vue components (kebab-case, with closing tags)');
343
+ sections.push('- Qelos global components (ai-chat, form-input, etc.)');
344
+ sections.push('');
345
+ sections.push('### Block Metadata Mapping');
346
+ sections.push('The `blocks.json` file maps block filenames (kebab-case) to their metadata:');
347
+ sections.push('```json');
348
+ sections.push('{');
349
+ sections.push(' "login-header": {');
350
+ sections.push(' "_id": "507f1f77bcf86cd799439011",');
351
+ sections.push(' "name": "Login Header",');
352
+ sections.push(' "description": "Header component for login page",');
353
+ sections.push(' "contentType": "html"');
354
+ sections.push(' },');
355
+ sections.push(' "footer-template": {');
356
+ sections.push(' "_id": "507f191e810c19729de860ea",');
357
+ sections.push(' "name": "Footer Template",');
358
+ sections.push(' "description": "Reusable footer template",');
359
+ sections.push(' "contentType": "html"');
360
+ sections.push(' }');
361
+ sections.push('}');
362
+ sections.push('```');
363
+ sections.push('');
364
+ sections.push('### Working with Blocks');
365
+ sections.push('- When modifying a block, update both the `.html` file and its entry in `blocks.json`');
366
+ sections.push('- The filename in kebab-case must match the key in `blocks.json`');
367
+ sections.push('- Block names are converted to kebab-case for filenames (e.g., "Login Header" → `login-header.html`)');
368
+ sections.push('- The `_id` field is used to sync with the remote Qelos instance');
369
+ sections.push('- Use `qelos-cli push blocks <path>` to push changes back to Qelos');
370
+ sections.push('');
371
+ }
372
+
373
+ // Blueprints section
374
+ if (resources.blueprints.length > 0) {
375
+ sections.push('## Blueprints');
376
+ sections.push('');
377
+ sections.push('### Blueprint Structure');
378
+ sections.push('Blueprints define data models and entity structures in Qelos. Each blueprint file contains:');
379
+ sections.push('');
380
+ sections.push('```typescript');
381
+ sections.push('interface IBlueprint {');
382
+ sections.push(' identifier: string; // Unique identifier');
383
+ sections.push(' name: string; // Display name');
384
+ sections.push(' description?: string; // Description');
385
+ sections.push(' entityIdentifierMechanism: string; // "objectid" or "guid"');
386
+ sections.push(' properties: Record<string, PropertyDescriptor>; // Entity properties/fields');
387
+ sections.push(' relations: { key: string, target: string }[]; // Relations to other blueprints');
388
+ sections.push(' dispatchers: { // Event dispatchers');
389
+ sections.push(' create: boolean,');
390
+ sections.push(' update: boolean,');
391
+ sections.push(' delete: boolean');
392
+ sections.push(' };');
393
+ sections.push(' permissions: Array<PermissionsDescriptor>; // Access permissions');
394
+ sections.push(' permissionScope: string; // "user", "workspace", or "tenant"');
395
+ sections.push(' limitations?: Array<Limitation>; // Usage limitations');
396
+ sections.push('}');
397
+ sections.push('```');
398
+ sections.push('');
399
+ sections.push('### Blueprint Example');
400
+ sections.push('```json');
401
+ sections.push('{');
402
+ sections.push(' "identifier": "product",');
403
+ sections.push(' "name": "Product",');
404
+ sections.push(' "description": "Product catalog item",');
405
+ sections.push(' "entityIdentifierMechanism": "objectid",');
406
+ sections.push(' "permissionScope": "workspace",');
407
+ sections.push(' "properties": {');
408
+ sections.push(' "name": {');
409
+ sections.push(' "title": "Product Name",');
410
+ sections.push(' "type": "string",');
411
+ sections.push(' "description": "Name of the product",');
412
+ sections.push(' "required": true');
413
+ sections.push(' },');
414
+ sections.push(' "price": {');
415
+ sections.push(' "title": "Price",');
416
+ sections.push(' "type": "number",');
417
+ sections.push(' "description": "Product price",');
418
+ sections.push(' "required": true,');
419
+ sections.push(' "min": 0');
420
+ sections.push(' },');
421
+ sections.push(' "inStock": {');
422
+ sections.push(' "title": "In Stock",');
423
+ sections.push(' "type": "boolean",');
424
+ sections.push(' "description": "Whether product is in stock",');
425
+ sections.push(' "required": false');
426
+ sections.push(' }');
427
+ sections.push(' },');
428
+ sections.push(' "relations": [');
429
+ sections.push(' {');
430
+ sections.push(' "key": "category",');
431
+ sections.push(' "target": "product_category"');
432
+ sections.push(' }');
433
+ sections.push(' ],');
434
+ sections.push(' "dispatchers": {');
435
+ sections.push(' "create": true,');
436
+ sections.push(' "update": true,');
437
+ sections.push(' "delete": false');
438
+ sections.push(' },');
439
+ sections.push(' "permissions": [');
440
+ sections.push(' {');
441
+ sections.push(' "scope": "workspace",');
442
+ sections.push(' "operation": "create",');
443
+ sections.push(' "roleBased": ["admin", "editor"]');
444
+ sections.push(' }');
445
+ sections.push(' ]');
446
+ sections.push('}');
447
+ sections.push('```');
448
+ sections.push('');
449
+ sections.push('### Blueprint to Entity Mapping');
450
+ sections.push('When working with blueprint entities:');
451
+ sections.push('- **Properties** define the fields available on each entity instance');
452
+ sections.push(' - Properties is a Record (object) where keys are field names');
453
+ sections.push(' - Example: A `product` entity will have `name`, `price`, and `inStock` fields');
454
+ sections.push('- **Relations** define how entities connect to other blueprint entities');
455
+ sections.push(' - Each relation has a `key` (field name) and `target` (blueprint identifier)');
456
+ sections.push(' - Example: `category` relation to `product_category` blueprint');
457
+ sections.push('- **Dispatchers** define which CRUD operations trigger events');
458
+ sections.push(' - Boolean flags for `create`, `update`, and `delete` operations');
459
+ sections.push(' - Example: Product creation and updates trigger events, but deletion does not');
460
+ sections.push('- **Permissions** control who can perform operations on entities');
461
+ sections.push(' - Scoped at user, workspace, or tenant level');
462
+ sections.push(' - Can be role-based or label-based');
463
+ sections.push('- Use the blueprint structure to understand what data is available when building components');
464
+ sections.push('');
465
+ sections.push('### Working with Blueprints');
466
+ sections.push('- Blueprint files are named as `{identifier}.blueprint.json`');
467
+ sections.push('- When building components that display blueprint entities, reference the properties structure');
468
+ sections.push('- Use relations to understand how to fetch related entity data');
469
+ sections.push('- Consider dispatchers when implementing entity lifecycle hooks');
470
+ sections.push('- Use `qelos-cli push blueprints <path>` to push changes back to Qelos');
471
+ sections.push('');
472
+ }
473
+
474
+ // Plugins section
475
+ if (resources.plugins.length > 0) {
476
+ sections.push('## Plugins');
477
+ sections.push('');
478
+ sections.push('### Plugin Structure');
479
+ sections.push('Plugins extend Qelos functionality and can include:');
480
+ sections.push('- **Micro-frontends**: UI components loaded dynamically');
481
+ sections.push('- **Injectables**: Services or utilities injected into the platform');
482
+ sections.push('- **Navigation groups**: Menu items and navigation structure');
483
+ sections.push('');
484
+ sections.push('### Plugin Files and References');
485
+ sections.push('Each plugin has:');
486
+ sections.push('- A main `.plugin.json` file with plugin configuration');
487
+ sections.push('- A `micro-frontends/` directory containing HTML structure files');
488
+ sections.push('- Micro-frontend structures are referenced using `{ "$ref": "./micro-frontends/filename.html" }`');
489
+ sections.push('');
490
+ sections.push('### Plugin Example');
491
+ sections.push('```json');
492
+ sections.push('{');
493
+ sections.push(' "name": "Agent Editor Plugin",');
494
+ sections.push(' "apiPath": "agent-editor",');
495
+ sections.push(' "description": "Plugin for agent editing functionality",');
496
+ sections.push(' "microFrontends": [');
497
+ sections.push(' {');
498
+ sections.push(' "name": "Agent Editor",');
499
+ sections.push(' "route": {');
500
+ sections.push(' "name": "agent-editor",');
501
+ sections.push(' "path": "/editor/:agentId",');
502
+ sections.push(' "requirements": {');
503
+ sections.push(' "permissions": ["agent.edit"],');
504
+ sections.push(' "blueprints": ["agent"]');
505
+ sections.push(' }');
506
+ sections.push(' },');
507
+ sections.push(' "structure": {');
508
+ sections.push(' "$ref": "./micro-frontends/agent-editor.html"');
509
+ sections.push(' }');
510
+ sections.push(' }');
511
+ sections.push(' ],');
512
+ sections.push(' "navBarGroups": [');
513
+ sections.push(' {');
514
+ sections.push(' "label": "Agent Tools",');
515
+ sections.push(' "items": [...]');
516
+ sections.push(' }');
517
+ sections.push(' ],');
518
+ sections.push(' "injectables": [...]');
519
+ sections.push('}');
520
+ sections.push('```');
521
+ sections.push('');
522
+ sections.push('### Micro-frontend Structure Reference');
523
+ sections.push('The `$ref` field points to an HTML file in the `micro-frontends/` directory:');
524
+ sections.push('- **Route name**: Used to identify the micro-frontend (converted to kebab-case for filename)');
525
+ sections.push('- **Route path**: The URL path where the micro-frontend is accessible');
526
+ sections.push('- **Requirements**: Conditions that must be met for the micro-frontend to load');
527
+ sections.push(' - `permissions`: Required user permissions');
528
+ sections.push(' - `blueprints`: Required blueprint entities');
529
+ sections.push('- **Structure file**: HTML template referenced via `$ref`');
530
+ sections.push('');
531
+ sections.push('### IMPORTANT: Micro-frontend HTML Limitations');
532
+ sections.push('**Micro-frontend HTML files CANNOT contain `<script>` tags or JavaScript code.**');
533
+ sections.push('');
534
+ sections.push('Just like blocks, to add JavaScript functionality:');
535
+ sections.push('1. Create a Vue component in `components/` folder with your logic');
536
+ sections.push('2. Use the component in your micro-frontend HTML (kebab-case, with closing tags)');
537
+ sections.push('3. No import needed - all components are globally available');
538
+ sections.push('');
539
+ sections.push('### Using Components in Micro-frontends');
540
+ sections.push('Micro-frontend HTML files can use components from the `components/` directory:');
541
+ sections.push('```html');
542
+ sections.push('<!-- In micro-frontends/agent-editor.html -->');
543
+ sections.push('<agent-editor');
544
+ sections.push(' :agent-id="currentAgent.id"');
545
+ sections.push(' :autoplay="true"');
546
+ sections.push(' @ended="handleAgentEnd"');
547
+ sections.push('/>');
548
+ sections.push('```');
549
+ sections.push('');
550
+ sections.push('This `<agent-editor>` component maps to:');
551
+ sections.push('- **Component file**: `components/AgentEditor.vue`');
552
+ sections.push('- **Metadata entry**: `components.json["AgentEditor"]`');
553
+ sections.push('');
554
+ sections.push('Remember: kebab-case in templates = PascalCase component file name.');
555
+ sections.push('');
556
+ sections.push('### Working with Plugins');
557
+ sections.push('- Plugin files are named as `{apiPath}.plugin.json`');
558
+ sections.push('- When modifying micro-frontend structures, edit the HTML files in `micro-frontends/`');
559
+ sections.push('- The plugin JSON file references these HTML files using `$ref`');
560
+ sections.push('- Route names and paths are defined in the plugin configuration');
561
+ sections.push('- Requirements specify what conditions must be met for the micro-frontend to load');
562
+ sections.push('- Use `qelos-cli push plugins <path>` to push changes back to Qelos');
563
+ sections.push('');
564
+ }
565
+
566
+ // General guidelines
567
+ sections.push('## General Guidelines');
568
+ sections.push('');
569
+ sections.push('### File Naming Conventions');
570
+ sections.push('- Components: `ComponentName.vue`');
571
+ sections.push('- Blocks: `block-name.html` (kebab-case)');
572
+ sections.push('- Blueprints: `identifier.blueprint.json`');
573
+ sections.push('- Plugins: `api-path.plugin.json`');
574
+ sections.push('- Micro-frontends: `route-name.html` (kebab-case)');
575
+ sections.push('');
576
+ sections.push('### Metadata Files');
577
+ sections.push('- `components.json`: Maps component filenames to metadata');
578
+ sections.push('- `blocks.json`: Maps block filenames to metadata');
579
+ sections.push('- Always keep metadata files in sync with their corresponding files');
580
+ sections.push('- The `_id` field is crucial for syncing with the remote Qelos instance');
581
+ sections.push('');
582
+ sections.push('### Best Practices');
583
+ sections.push('- Use the Qelos CLI to pull and push resources');
584
+ sections.push('- Maintain the directory structure created by the pull command');
585
+ sections.push('- Reference blueprint structures when building components that display entities');
586
+ sections.push('- Use micro-frontend route names and requirements to understand loading conditions');
587
+ sections.push('- Test changes locally before pushing to the remote Qelos instance');
588
+ sections.push('');
589
+
590
+ return sections.join('\n');
591
+ }
592
+
593
+ /**
594
+ * Get the appropriate file path for the IDE type
595
+ * @param {string} ideType - Type of IDE
596
+ * @param {string} basePath - Base path
597
+ * @returns {string} File path for the rules file
598
+ */
599
+ function getRulesFilePath(ideType, basePath) {
600
+ switch (ideType) {
601
+ case 'windsurf':
602
+ return path.join(basePath, '.windsurf', 'rules', 'qelos-resources.md');
603
+ case 'cursor':
604
+ return path.join(basePath, '.cursorrules');
605
+ case 'claude':
606
+ return path.join(basePath, '.clinerules');
607
+ default:
608
+ throw new Error(`Unknown IDE type: ${ideType}`);
609
+ }
610
+ }
611
+
612
+ /**
613
+ * Generate rules file for a specific IDE
614
+ * @param {string} ideType - Type of IDE (windsurf, cursor, claude)
615
+ * @param {string} basePath - Base path to scan for resources
616
+ * @returns {Object} Result object with success status and file path
617
+ */
618
+ export async function generateRules(ideType, basePath) {
619
+ try {
620
+ // Scan for pulled resources
621
+ const resources = scanPulledResources(basePath);
622
+
623
+ // Check if any resources were found
624
+ const hasResources =
625
+ resources.components !== null ||
626
+ resources.blocks !== null ||
627
+ resources.blueprints.length > 0 ||
628
+ resources.plugins.length > 0;
629
+
630
+ if (!hasResources) {
631
+ return {
632
+ success: false,
633
+ message: 'No pulled resources found. Run pull command first.'
634
+ };
635
+ }
636
+
637
+ // Fetch Qelos global components from documentation
638
+ logger.debug('Fetching Qelos global components from documentation...');
639
+ const qelosComponents = await fetchQelosGlobalComponents();
640
+ if (qelosComponents) {
641
+ logger.debug(`Fetched ${qelosComponents.components.length} components and ${qelosComponents.directives.length} directives`);
642
+ }
643
+
644
+ // Generate rules content
645
+ const content = generateRulesContent(resources, ideType, qelosComponents);
646
+
647
+ // Get file path and ensure directory exists
648
+ const filePath = getRulesFilePath(ideType, basePath);
649
+ const dir = path.dirname(filePath);
650
+
651
+ if (!fs.existsSync(dir)) {
652
+ fs.mkdirSync(dir, { recursive: true });
653
+ }
654
+
655
+ // Write rules file
656
+ fs.writeFileSync(filePath, content, 'utf-8');
657
+
658
+ return {
659
+ success: true,
660
+ filePath
661
+ };
662
+
663
+ } catch (error) {
664
+ logger.error(`Failed to generate ${ideType} rules: ${error.message}`);
665
+ throw error;
666
+ }
667
+ }