mycelia-kernel-plugin 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +248 -0
  3. package/bin/cli.js +433 -0
  4. package/package.json +63 -0
  5. package/src/builder/context-resolver.js +62 -0
  6. package/src/builder/dependency-graph-cache.js +105 -0
  7. package/src/builder/dependency-graph.js +141 -0
  8. package/src/builder/facet-validator.js +43 -0
  9. package/src/builder/hook-processor.js +271 -0
  10. package/src/builder/index.js +13 -0
  11. package/src/builder/subsystem-builder.js +104 -0
  12. package/src/builder/utils.js +165 -0
  13. package/src/contract/contracts/hierarchy.contract.js +60 -0
  14. package/src/contract/contracts/index.js +17 -0
  15. package/src/contract/contracts/listeners.contract.js +66 -0
  16. package/src/contract/contracts/processor.contract.js +47 -0
  17. package/src/contract/contracts/queue.contract.js +58 -0
  18. package/src/contract/contracts/router.contract.js +53 -0
  19. package/src/contract/contracts/scheduler.contract.js +65 -0
  20. package/src/contract/contracts/server.contract.js +88 -0
  21. package/src/contract/contracts/speak.contract.js +50 -0
  22. package/src/contract/contracts/storage.contract.js +107 -0
  23. package/src/contract/contracts/websocket.contract.js +90 -0
  24. package/src/contract/facet-contract-registry.js +155 -0
  25. package/src/contract/facet-contract.js +136 -0
  26. package/src/contract/index.js +63 -0
  27. package/src/core/create-hook.js +63 -0
  28. package/src/core/facet.js +189 -0
  29. package/src/core/index.js +3 -0
  30. package/src/hooks/listeners/handler-group-manager.js +88 -0
  31. package/src/hooks/listeners/listener-manager-policies.js +229 -0
  32. package/src/hooks/listeners/listener-manager.js +668 -0
  33. package/src/hooks/listeners/listener-registry.js +176 -0
  34. package/src/hooks/listeners/listener-statistics.js +106 -0
  35. package/src/hooks/listeners/pattern-matcher.js +283 -0
  36. package/src/hooks/listeners/use-listeners.js +164 -0
  37. package/src/hooks/queue/bounded-queue.js +341 -0
  38. package/src/hooks/queue/circular-buffer.js +231 -0
  39. package/src/hooks/queue/subsystem-queue-manager.js +198 -0
  40. package/src/hooks/queue/use-queue.js +96 -0
  41. package/src/hooks/speak/use-speak.js +79 -0
  42. package/src/index.js +49 -0
  43. package/src/manager/facet-manager-transaction.js +45 -0
  44. package/src/manager/facet-manager.js +570 -0
  45. package/src/manager/index.js +3 -0
  46. package/src/system/base-subsystem.js +416 -0
  47. package/src/system/base-subsystem.utils.js +106 -0
  48. package/src/system/index.js +4 -0
  49. package/src/system/standalone-plugin-system.js +70 -0
  50. package/src/utils/debug-flag.js +34 -0
  51. package/src/utils/find-facet.js +30 -0
  52. package/src/utils/logger.js +84 -0
  53. package/src/utils/semver.js +221 -0
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mycelia Kernel Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # Mycelia Plugin System
2
+
3
+ A sophisticated, dependency-aware plugin system with transaction safety and lifecycle management.
4
+
5
+ ## Overview
6
+
7
+ Mycelia Plugin System is a standalone plugin architecture extracted from [Mycelia Kernel](https://github.com/lesfleursdelanuitdev/mycelia-kernel). It provides:
8
+
9
+ - **Hook-based composition** - Extend systems without modification
10
+ - **Dependency resolution** - Automatic topological sorting
11
+ - **Transaction safety** - Atomic installation with rollback
12
+ - **Lifecycle management** - Built-in initialization and disposal
13
+ - **Hot reloading** - Reload and extend plugins without full teardown
14
+ - **Facet contracts** - Runtime validation of plugin interfaces
15
+ - **Standalone mode** - Works without message system or other dependencies
16
+
17
+ ## Quick Start
18
+
19
+ ```javascript
20
+ import { StandalonePluginSystem, createHook, Facet } from 'mycelia-kernel-plugin';
21
+
22
+ // Create a hook
23
+ const useDatabase = createHook({
24
+ kind: 'database',
25
+ version: '1.0.0',
26
+ attach: true,
27
+ source: import.meta.url,
28
+ fn: (ctx, api, subsystem) => {
29
+ const config = ctx.config?.database || {};
30
+
31
+ return new Facet('database', {
32
+ attach: true,
33
+ source: import.meta.url
34
+ })
35
+ .add({
36
+ async query(sql) {
37
+ // Database query implementation
38
+ return { rows: [] };
39
+ },
40
+
41
+ async close() {
42
+ // Cleanup
43
+ }
44
+ })
45
+ .onInit(async ({ ctx }) => {
46
+ // Initialize database connection
47
+ })
48
+ .onDispose(async () => {
49
+ // Close database connection
50
+ });
51
+ }
52
+ });
53
+
54
+ // Create and use the system
55
+ const system = new StandalonePluginSystem('my-app', {
56
+ config: {
57
+ database: { host: 'localhost' }
58
+ }
59
+ });
60
+
61
+ system
62
+ .use(useDatabase)
63
+ .build();
64
+
65
+ // Use the plugin
66
+ const db = system.find('database');
67
+ await db.query('SELECT * FROM users');
68
+ ```
69
+
70
+ ## Installation
71
+
72
+ ```bash
73
+ npm install mycelia-kernel-plugin
74
+ ```
75
+
76
+ ## Features
77
+
78
+ ### Hook System
79
+ Create composable plugins using the `createHook` factory:
80
+
81
+ ```javascript
82
+ const useCache = createHook({
83
+ kind: 'cache',
84
+ required: ['database'], // Dependencies
85
+ attach: true,
86
+ source: import.meta.url,
87
+ fn: (ctx, api, subsystem) => {
88
+ const db = subsystem.find('database');
89
+
90
+ return new Facet('cache', { attach: true })
91
+ .add({
92
+ async get(key) {
93
+ // Cache implementation
94
+ }
95
+ });
96
+ }
97
+ });
98
+ ```
99
+
100
+ ### Dependency Resolution
101
+ Dependencies are automatically resolved and initialized in the correct order:
102
+
103
+ ```javascript
104
+ system
105
+ .use(useDatabase) // Will be initialized first
106
+ .use(useCache) // Will be initialized after database
107
+ .build();
108
+ ```
109
+
110
+ ### Transaction Safety
111
+ If any plugin fails during initialization, all changes are rolled back:
112
+
113
+ ```javascript
114
+ try {
115
+ await system
116
+ .use(useDatabase)
117
+ .use(useCache)
118
+ .build();
119
+ } catch (error) {
120
+ // System is in clean state - all plugins rolled back
121
+ }
122
+ ```
123
+
124
+ ### Hot Reloading
125
+ Reload the system and add more plugins without full teardown:
126
+
127
+ ```javascript
128
+ // Initial build
129
+ await system.use(useDatabase).build();
130
+
131
+ // Hot reload - add more plugins
132
+ await system.reload();
133
+ await system.use(useCache).use(useAuth).build();
134
+
135
+ // All plugins (old + new) are now active
136
+ ```
137
+
138
+ The `reload()` method:
139
+ - Disposes all facets and resets built state
140
+ - Preserves hooks and configuration
141
+ - Allows adding more hooks and rebuilding
142
+ - Perfect for development and hot-reload scenarios
143
+
144
+ ### Facet Contracts
145
+ Validate plugin interfaces at build time:
146
+
147
+ ```javascript
148
+ import { createFacetContract } from 'mycelia-kernel-plugin';
149
+
150
+ const databaseContract = createFacetContract({
151
+ name: 'database',
152
+ requiredMethods: ['query', 'close'],
153
+ requiredProperties: ['connection']
154
+ });
155
+
156
+ // Contract is automatically enforced during build
157
+ ```
158
+
159
+ ## Architecture
160
+
161
+ ```
162
+ StandalonePluginSystem
163
+ ├── BaseSubsystem (base class)
164
+ ├── SubsystemBuilder (build orchestrator)
165
+ ├── FacetManager (plugin registry)
166
+ ├── FacetContractRegistry (contract validation)
167
+ └── DependencyGraphCache (performance optimization)
168
+ ```
169
+
170
+ ## API Reference
171
+
172
+ ### Core Classes
173
+
174
+ - **`StandalonePluginSystem`** - Main plugin system class
175
+ - **`BaseSubsystem`** - Base class for plugin containers
176
+ - **`SubsystemBuilder`** - Build orchestrator
177
+ - **`FacetManager`** - Plugin registry
178
+ - **`FacetContractRegistry`** - Contract validation
179
+
180
+ ### Factory Functions
181
+
182
+ - **`createHook()`** - Create a plugin hook
183
+ - **`createFacetContract()`** - Create a facet contract
184
+
185
+ ### Utilities
186
+
187
+ - **`createLogger()`** - Create a logger
188
+ - **`getDebugFlag()`** - Extract debug flag from config
189
+
190
+ ## Documentation
191
+
192
+ Comprehensive documentation is available in the [`docs/`](./docs/) directory:
193
+
194
+ - **[Getting Started Guide](./docs/getting-started/README.md)** - Quick start with examples
195
+ - **[Hooks and Facets Overview](./docs/core-concepts/HOOKS-AND-FACETS-OVERVIEW.md)** - Core concepts
196
+ - **[Standalone Plugin System](./docs/standalone/STANDALONE-PLUGIN-SYSTEM.md)** - Complete usage guide
197
+ - **[Documentation Index](./docs/README.md)** - Full documentation index
198
+
199
+ ## Examples
200
+
201
+ See the `examples/` directory for:
202
+ - Basic plugin usage
203
+ - Plugins with dependencies
204
+ - Lifecycle management
205
+ - Contract validation
206
+ - Hot reloading
207
+
208
+ ## CLI Tool
209
+
210
+ The package includes a CLI tool for scaffolding hooks, contracts, and projects:
211
+
212
+ ```bash
213
+ # Create a new hook
214
+ npx mycelia-kernel-plugin create hook database
215
+
216
+ # Create a new contract
217
+ npx mycelia-kernel-plugin create contract database
218
+
219
+ # Initialize a new project
220
+ npx mycelia-kernel-plugin init my-app
221
+ ```
222
+
223
+ Or install globally:
224
+ ```bash
225
+ npm install -g mycelia-kernel-plugin
226
+ mycelia-kernel-plugin create hook database
227
+ ```
228
+
229
+ ## Testing
230
+
231
+ ```bash
232
+ npm test
233
+ npm run test:watch
234
+ ```
235
+
236
+ ## License
237
+
238
+ MIT License - see [LICENSE](./LICENSE) for details.
239
+
240
+ ## Links
241
+
242
+ - **GitHub:** https://github.com/lesfleursdelanuitdev/mycelia-kernel-plugin-system
243
+ - **Main Project:** https://github.com/lesfleursdelanuitdev/mycelia-kernel
244
+
245
+ ---
246
+
247
+ Made with ❤️ by [@lesfleursdelanuitdev](https://github.com/lesfleursdelanuitdev)
248
+
package/bin/cli.js ADDED
@@ -0,0 +1,433 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Mycelia Plugin System CLI
5
+ *
6
+ * Command-line tool for scaffolding hooks, contracts, and project structure.
7
+ */
8
+
9
+ import { fileURLToPath } from 'url';
10
+ import { dirname, join } from 'path';
11
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
12
+ import { execSync } from 'child_process';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+
17
+ // Get command and arguments
18
+ const [,, command, ...args] = process.argv;
19
+
20
+ // Commands
21
+ const commands = {
22
+ 'create': handleCreate,
23
+ 'init': handleInit,
24
+ 'help': showHelp,
25
+ '--help': showHelp,
26
+ '-h': showHelp
27
+ };
28
+
29
+ // Main entry point
30
+ if (!command || !commands[command]) {
31
+ showHelp();
32
+ process.exit(1);
33
+ }
34
+
35
+ commands[command](args);
36
+
37
+ /**
38
+ * Handle 'create' command
39
+ */
40
+ function handleCreate(args) {
41
+ if (args.length < 2) {
42
+ console.error('Error: Missing arguments');
43
+ console.error('Usage: mycelia-kernel-plugin create <type> <name>');
44
+ console.error('Types: hook, contract');
45
+ process.exit(1);
46
+ }
47
+
48
+ const [type, name] = args;
49
+
50
+ if (type === 'hook') {
51
+ createHook(name);
52
+ } else if (type === 'contract') {
53
+ createContract(name);
54
+ } else {
55
+ console.error(`Error: Unknown type "${type}"`);
56
+ console.error('Types: hook, contract');
57
+ process.exit(1);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Handle 'init' command
63
+ */
64
+ function handleInit(args) {
65
+ const projectName = args[0] || 'my-plugin-system';
66
+ initProject(projectName);
67
+ }
68
+
69
+ /**
70
+ * Create a hook file
71
+ */
72
+ function createHook(name) {
73
+ if (!name) {
74
+ console.error('Error: Hook name is required');
75
+ console.error('Usage: mycelia-kernel-plugin create hook <name>');
76
+ process.exit(1);
77
+ }
78
+
79
+ // Validate name (kebab-case or camelCase)
80
+ const sanitizedName = name.replace(/[^a-zA-Z0-9-]/g, '');
81
+ const hookName = toCamelCase(sanitizedName, true); // useXxx
82
+ const kindName = toKebabCase(sanitizedName); // xxx
83
+ const className = toPascalCase(sanitizedName); // Xxx
84
+
85
+ // Determine output directory
86
+ const cwd = process.cwd();
87
+ let outputDir;
88
+
89
+ if (existsSync(join(cwd, 'src/hooks'))) {
90
+ outputDir = join(cwd, 'src/hooks');
91
+ } else if (existsSync(join(cwd, 'hooks'))) {
92
+ outputDir = join(cwd, 'hooks');
93
+ } else if (existsSync(join(cwd, 'src'))) {
94
+ outputDir = join(cwd, 'src');
95
+ mkdirSync(join(outputDir, 'hooks'), { recursive: true });
96
+ outputDir = join(outputDir, 'hooks');
97
+ } else {
98
+ mkdirSync(join(cwd, 'src', 'hooks'), { recursive: true });
99
+ outputDir = join(cwd, 'src', 'hooks');
100
+ }
101
+
102
+ if (!existsSync(outputDir)) {
103
+ mkdirSync(outputDir, { recursive: true });
104
+ }
105
+
106
+ const fileName = `use-${kindName}.js`;
107
+ const filePath = join(outputDir, fileName);
108
+
109
+ if (existsSync(filePath)) {
110
+ console.error(`Error: File already exists: ${filePath}`);
111
+ process.exit(1);
112
+ }
113
+
114
+ const template = `import { createHook, Facet } from 'mycelia-kernel-plugin';
115
+
116
+ /**
117
+ * ${className} Hook
118
+ *
119
+ * ${sanitizedName} plugin hook.
120
+ */
121
+ export const ${hookName} = createHook({
122
+ kind: '${kindName}',
123
+ version: '1.0.0',
124
+ attach: true,
125
+ source: import.meta.url,
126
+ fn: (ctx, api, subsystem) => {
127
+ const config = ctx.config?.${kindName} || {};
128
+
129
+ return new Facet('${kindName}', {
130
+ attach: true,
131
+ source: import.meta.url,
132
+ version: '1.0.0'
133
+ })
134
+ .add({
135
+ // Add your methods here
136
+ })
137
+ .onInit(async ({ ctx }) => {
138
+ // Initialization logic
139
+ if (ctx.debug) {
140
+ console.log('${className} plugin initialized');
141
+ }
142
+ })
143
+ .onDispose(async () => {
144
+ // Cleanup logic
145
+ });
146
+ }
147
+ });
148
+ `;
149
+
150
+ writeFileSync(filePath, template, 'utf8');
151
+ console.log(`✅ Created hook: ${filePath}`);
152
+ console.log(` Hook name: ${hookName}`);
153
+ console.log(` Facet kind: ${kindName}`);
154
+ }
155
+
156
+ /**
157
+ * Create a contract file
158
+ */
159
+ function createContract(name) {
160
+ if (!name) {
161
+ console.error('Error: Contract name is required');
162
+ console.error('Usage: mycelia-kernel-plugin create contract <name>');
163
+ process.exit(1);
164
+ }
165
+
166
+ // Validate name
167
+ const sanitizedName = name.replace(/[^a-zA-Z0-9-]/g, '');
168
+ const contractName = toCamelCase(sanitizedName); // xxxContract
169
+ const contractType = toKebabCase(sanitizedName); // xxx
170
+
171
+ // Determine output directory
172
+ const cwd = process.cwd();
173
+ let outputDir;
174
+
175
+ if (existsSync(join(cwd, 'src/contracts'))) {
176
+ outputDir = join(cwd, 'src/contracts');
177
+ } else if (existsSync(join(cwd, 'contracts'))) {
178
+ outputDir = join(cwd, 'contracts');
179
+ } else if (existsSync(join(cwd, 'src'))) {
180
+ outputDir = join(cwd, 'src');
181
+ mkdirSync(join(outputDir, 'contracts'), { recursive: true });
182
+ outputDir = join(outputDir, 'contracts');
183
+ } else {
184
+ mkdirSync(join(cwd, 'src', 'contracts'), { recursive: true });
185
+ outputDir = join(cwd, 'src', 'contracts');
186
+ }
187
+
188
+ if (!existsSync(outputDir)) {
189
+ mkdirSync(outputDir, { recursive: true });
190
+ }
191
+
192
+ const fileName = `${contractType}.contract.js`;
193
+ const filePath = join(outputDir, fileName);
194
+
195
+ if (existsSync(filePath)) {
196
+ console.error(`Error: File already exists: ${filePath}`);
197
+ process.exit(1);
198
+ }
199
+
200
+ const template = `import { createFacetContract } from 'mycelia-kernel-plugin';
201
+
202
+ /**
203
+ * ${sanitizedName} Contract
204
+ *
205
+ * Defines the contract for ${sanitizedName} facets.
206
+ */
207
+ export const ${contractName}Contract = createFacetContract({
208
+ name: '${contractType}',
209
+ requiredMethods: [
210
+ // Add required method names here
211
+ ],
212
+ requiredProperties: [
213
+ // Add required property names here
214
+ ],
215
+ validate: (ctx, api, subsystem, facet) => {
216
+ // Custom validation logic
217
+ // Throw an error if validation fails
218
+ }
219
+ });
220
+
221
+ // Register with default registry (optional)
222
+ // import { defaultContractRegistry } from 'mycelia-kernel-plugin';
223
+ // defaultContractRegistry.register(${contractName}Contract);
224
+ `;
225
+
226
+ writeFileSync(filePath, template, 'utf8');
227
+ console.log(`✅ Created contract: ${filePath}`);
228
+ console.log(` Contract name: ${contractName}Contract`);
229
+ }
230
+
231
+ /**
232
+ * Initialize a new project
233
+ */
234
+ function initProject(projectName) {
235
+ const projectDir = projectName;
236
+
237
+ if (existsSync(projectDir)) {
238
+ console.error(`Error: Directory already exists: ${projectDir}`);
239
+ process.exit(1);
240
+ }
241
+
242
+ console.log(`Creating project: ${projectName}...`);
243
+
244
+ // Create directory structure
245
+ mkdirSync(projectDir, { recursive: true });
246
+ mkdirSync(join(projectDir, 'src'), { recursive: true });
247
+ mkdirSync(join(projectDir, 'src/hooks'), { recursive: true });
248
+ mkdirSync(join(projectDir, 'src/contracts'), { recursive: true });
249
+
250
+ // Create package.json
251
+ const packageJson = {
252
+ name: projectName,
253
+ version: '1.0.0',
254
+ type: 'module',
255
+ main: 'src/index.js',
256
+ scripts: {
257
+ start: 'node src/index.js',
258
+ test: 'vitest run'
259
+ },
260
+ dependencies: {
261
+ 'mycelia-kernel-plugin': '^1.0.0'
262
+ },
263
+ devDependencies: {
264
+ vitest: '^2.1.5'
265
+ }
266
+ };
267
+
268
+ writeFileSync(
269
+ join(projectDir, 'package.json'),
270
+ JSON.stringify(packageJson, null, 2),
271
+ 'utf8'
272
+ );
273
+
274
+ // Create README.md
275
+ const readme = `# ${projectName}
276
+
277
+ A plugin system built with Mycelia Plugin System.
278
+
279
+ ## Getting Started
280
+
281
+ \`\`\`bash
282
+ npm install
283
+ npm start
284
+ \`\`\`
285
+
286
+ ## Creating Plugins
287
+
288
+ Use the CLI to scaffold new plugins:
289
+
290
+ \`\`\`bash
291
+ npx mycelia-kernel-plugin create hook my-plugin
292
+ \`\`\`
293
+
294
+ ## Structure
295
+
296
+ - \`src/hooks/\` - Plugin hooks
297
+ - \`src/contracts/\` - Facet contracts
298
+ - \`src/index.js\` - Main entry point
299
+ `;
300
+
301
+ writeFileSync(join(projectDir, 'README.md'), readme, 'utf8');
302
+
303
+ // Create example hook
304
+ const exampleHook = `import { createHook, Facet } from 'mycelia-kernel-plugin';
305
+
306
+ /**
307
+ * Example Hook
308
+ */
309
+ export const useExample = createHook({
310
+ kind: 'example',
311
+ version: '1.0.0',
312
+ attach: true,
313
+ source: import.meta.url,
314
+ fn: (ctx, api, subsystem) => {
315
+ return new Facet('example', {
316
+ attach: true,
317
+ source: import.meta.url,
318
+ version: '1.0.0'
319
+ })
320
+ .add({
321
+ greet(name) {
322
+ return \`Hello, \${name}!\`;
323
+ }
324
+ });
325
+ }
326
+ });
327
+ `;
328
+
329
+ writeFileSync(join(projectDir, 'src/hooks/use-example.js'), exampleHook, 'utf8');
330
+
331
+ // Create main index.js
332
+ const indexJs = `import { StandalonePluginSystem } from 'mycelia-kernel-plugin';
333
+ import { useExample } from './hooks/use-example.js';
334
+
335
+ async function main() {
336
+ const system = new StandalonePluginSystem('${projectName}', {
337
+ config: {},
338
+ debug: true
339
+ });
340
+
341
+ await system
342
+ .use(useExample)
343
+ .build();
344
+
345
+ const example = system.find('example');
346
+ console.log(example.greet('World'));
347
+
348
+ await system.dispose();
349
+ }
350
+
351
+ main().catch(console.error);
352
+ `;
353
+
354
+ writeFileSync(join(projectDir, 'src/index.js'), indexJs, 'utf8');
355
+
356
+ // Create .gitignore
357
+ const gitignore = `node_modules/
358
+ *.log
359
+ .DS_Store
360
+ `;
361
+
362
+ writeFileSync(join(projectDir, '.gitignore'), gitignore, 'utf8');
363
+
364
+ console.log(`✅ Project created: ${projectDir}`);
365
+ console.log(`\nNext steps:`);
366
+ console.log(` cd ${projectDir}`);
367
+ console.log(` npm install`);
368
+ console.log(` npm start`);
369
+ }
370
+
371
+ /**
372
+ * Show help message
373
+ */
374
+ function showHelp() {
375
+ const help = `
376
+ Mycelia Plugin System CLI
377
+
378
+ Usage:
379
+ mycelia-kernel-plugin <command> [options]
380
+
381
+ Commands:
382
+ create hook <name> Create a new hook file
383
+ create contract <name> Create a new contract file
384
+ init [name] Initialize a new project
385
+ help Show this help message
386
+
387
+ Examples:
388
+ mycelia-kernel-plugin create hook database
389
+ mycelia-kernel-plugin create contract database
390
+ mycelia-kernel-plugin init my-app
391
+
392
+ For more information, visit:
393
+ https://github.com/lesfleursdelanuitdev/mycelia-kernel-plugin-system
394
+ `;
395
+ console.log(help);
396
+ }
397
+
398
+ /**
399
+ * Convert string to camelCase (with use prefix for hooks)
400
+ */
401
+ function toCamelCase(str, usePrefix = false) {
402
+ const parts = str.split('-').filter(Boolean);
403
+ const camel = parts.map((part, i) => {
404
+ if (i === 0 && !usePrefix) {
405
+ return part.toLowerCase();
406
+ }
407
+ return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
408
+ }).join('');
409
+ return usePrefix ? `use${camel}` : camel;
410
+ }
411
+
412
+ /**
413
+ * Convert string to kebab-case
414
+ */
415
+ function toKebabCase(str) {
416
+ return str
417
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
418
+ .toLowerCase()
419
+ .replace(/[^a-z0-9-]/g, '-')
420
+ .replace(/-+/g, '-')
421
+ .replace(/^-|-$/g, '');
422
+ }
423
+
424
+ /**
425
+ * Convert string to PascalCase
426
+ */
427
+ function toPascalCase(str) {
428
+ const parts = str.split('-').filter(Boolean);
429
+ return parts.map(part =>
430
+ part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()
431
+ ).join('');
432
+ }
433
+