lumia-plugin 0.1.5 → 0.1.6

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.
@@ -0,0 +1,243 @@
1
+ # Getting Started with Showcase Plugin Template
2
+
3
+ Welcome to your new Lumia Stream plugin! This guide will help you understand the plugin structure and start building your custom functionality.
4
+
5
+ ## Project Structure
6
+
7
+ ```
8
+ your-plugin/
9
+ ├── manifest.json # Plugin metadata and configuration
10
+ ├── main.js # Your plugin's main code
11
+ ├── package.json # npm dependencies (optional)
12
+ └── README.md # Plugin documentation
13
+ ```
14
+
15
+ ## Understanding the Files
16
+
17
+ ### 📋 manifest.json
18
+
19
+ This file defines your plugin's metadata and capabilities:
20
+
21
+ - **id**: Unique identifier (kebab-case, e.g., "my-awesome-plugin")
22
+ - **name**: Display name shown to users
23
+ - **description**: Short description for the plugin marketplace
24
+ - **main**: Entry point file (usually "main.js")
25
+ - **config**: Defines settings, actions, and variables
26
+
27
+ **Key sections:**
28
+ - `settings`: User-configurable options for your plugin
29
+ - `actions`: Functions users can trigger (e.g., send a message, change color)
30
+ - `variables`: Data your plugin exposes to Lumia Stream
31
+
32
+ ### 🔧 main.js
33
+
34
+ Your plugin's logic lives here. The main class extends the `Plugin` base class:
35
+
36
+ ```javascript
37
+ class YourPlugin extends Plugin {
38
+ constructor(props) {
39
+ super(props);
40
+ // Initialize your plugin
41
+ }
42
+
43
+ async onload() {
44
+ // Called when plugin is loaded
45
+ }
46
+
47
+ async onsettingsupdate() {
48
+ // Called when settings change
49
+ }
50
+
51
+ async onunload() {
52
+ // Called when plugin is unloaded
53
+ }
54
+ }
55
+ ```
56
+
57
+ ## Common Tasks
58
+
59
+ ### Adding a New Setting
60
+
61
+ 1. Edit `manifest.json` in the `config.settings` array:
62
+
63
+ ```json
64
+ {
65
+ "key": "myNewSetting",
66
+ "label": "My New Setting",
67
+ "type": "text",
68
+ "defaultValue": "default value",
69
+ "required": false
70
+ }
71
+ ```
72
+
73
+ 2. Access in `main.js`:
74
+
75
+ ```javascript
76
+ const value = this.getSetting('myNewSetting');
77
+ ```
78
+
79
+ ### Adding a New Action
80
+
81
+ 1. Define in `manifest.json` under `config.actions`:
82
+
83
+ ```json
84
+ {
85
+ "type": "my_custom_action",
86
+ "label": "Do Something Cool",
87
+ "fields": [
88
+ {
89
+ "key": "message",
90
+ "label": "Message",
91
+ "type": "text",
92
+ "required": true
93
+ }
94
+ ]
95
+ }
96
+ ```
97
+
98
+ 2. Handle in `main.js`:
99
+
100
+ ```javascript
101
+ async onAction(action) {
102
+ if (action.type === 'my_custom_action') {
103
+ const message = action.fields.message;
104
+ // Your logic here
105
+ }
106
+ }
107
+ ```
108
+
109
+ ### Creating Variables
110
+
111
+ Variables let other Lumia features access your plugin's data:
112
+
113
+ 1. Define in `manifest.json` under `config.variables`:
114
+
115
+ ```json
116
+ {
117
+ "name": "my_variable",
118
+ "key": "myVariable",
119
+ "origin": "your-plugin-id",
120
+ "type": "string",
121
+ "example": "Sample value"
122
+ }
123
+ ```
124
+
125
+ 2. Update in `main.js`:
126
+
127
+ ```javascript
128
+ this.setVariable('myVariable', 'new value');
129
+ ```
130
+
131
+ ## Testing Your Plugin
132
+
133
+ 1. **Install dependencies** (if using package.json):
134
+ ```bash
135
+ npm install
136
+ ```
137
+
138
+ 2. **Load in Lumia Stream**:
139
+ - Open Lumia Stream
140
+ - Go to Plugins section
141
+ - Load your plugin directory
142
+ - Enable your plugin
143
+
144
+ 3. **Check logs**:
145
+ ```javascript
146
+ this.log('Debug message');
147
+ this.error('Error message');
148
+ ```
149
+
150
+ ## Next Steps
151
+
152
+ - [ ] Customize `manifest.json` with your plugin details
153
+ - [ ] Update the plugin name and description
154
+ - [ ] Add your custom settings
155
+ - [ ] Implement your actions
156
+ - [ ] Test your plugin in Lumia Stream
157
+ - [ ] Write tests (optional)
158
+ - [ ] Update README.md with usage instructions
159
+
160
+ ## API Reference
161
+
162
+ ### Plugin Methods
163
+
164
+ - `this.getSetting(key)` - Get a setting value
165
+ - `this.setVariable(key, value)` - Update a variable
166
+ - `this.log(message)` - Log info message
167
+ - `this.error(message)` - Log error message
168
+ - `this.sendAlert(type, data)` - Trigger an alert
169
+
170
+ ### Lifecycle Hooks
171
+
172
+ - `onload()` - Plugin initialization
173
+ - `onunload()` - Cleanup when plugin is disabled
174
+ - `onsettingsupdate()` - Respond to setting changes
175
+ - `onAction(action)` - Handle triggered actions
176
+
177
+ ## Common Patterns
178
+
179
+ ### Making HTTP Requests
180
+
181
+ ```javascript
182
+ const response = await fetch('https://api.example.com/data');
183
+ const data = await response.json();
184
+ ```
185
+
186
+ ### Using Timers
187
+
188
+ ```javascript
189
+ async onload() {
190
+ this.interval = setInterval(() => {
191
+ // Do something periodically
192
+ }, 5000);
193
+ }
194
+
195
+ async onunload() {
196
+ if (this.interval) {
197
+ clearInterval(this.interval);
198
+ }
199
+ }
200
+ ```
201
+
202
+ ### Error Handling
203
+
204
+ ```javascript
205
+ try {
206
+ // Your code
207
+ } catch (error) {
208
+ this.error(`Something went wrong: ${error.message}`);
209
+ }
210
+ ```
211
+
212
+ ## Troubleshooting
213
+
214
+ **Plugin not loading?**
215
+ - Check manifest.json syntax (use a JSON validator)
216
+ - Ensure the `main` field points to the correct file
217
+ - Check Lumia Stream logs for errors
218
+
219
+ **Settings not showing?**
220
+ - Verify the `settings` array in manifest.json
221
+ - Ensure required fields are filled
222
+
223
+ **Actions not working?**
224
+ - Check the action type matches your handler
225
+ - Verify field keys match what you're accessing
226
+
227
+ ## Resources
228
+
229
+ - [Lumia Stream Documentation](https://docs.lumiastream.com)
230
+ - [Plugin API Reference](https://docs.lumiastream.com/plugins/api)
231
+ - [Example Plugins](https://github.com/lumiastream/plugins)
232
+ - [Community Discord](https://discord.gg/lumiastream)
233
+
234
+ ## Need Help?
235
+
236
+ - Join the Lumia Stream Discord community
237
+ - Check the documentation
238
+ - Review example plugins
239
+ - Ask questions in the developer channel
240
+
241
+ ---
242
+
243
+ Happy coding! 🚀
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumia-plugin",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Command-line tools for creating, building, and validating Lumia Stream plugins.",
5
5
  "bin": {
6
6
  "lumia-plugin": "scripts/cli.js"
@@ -5,207 +5,264 @@ const fs = require("fs");
5
5
  const TEMPLATE_DIR = path.resolve(__dirname, "..", "examples", "base-plugin");
6
6
 
7
7
  function toKebabCase(value) {
8
- return (
9
- value
10
- .trim()
11
- .toLowerCase()
12
- .replace(/[^a-z0-9]+/g, "-")
13
- .replace(/^-+|-+$/g, "") || "my-plugin"
14
- );
8
+ return (
9
+ value
10
+ .trim()
11
+ .toLowerCase()
12
+ .replace(/[^a-z0-9]+/g, "-")
13
+ .replace(/^-+|-+$/g, "") || "my-plugin"
14
+ );
15
15
  }
16
16
 
17
17
  function toDisplayName(id) {
18
- return id
19
- .split("-")
20
- .filter(Boolean)
21
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
22
- .join(" ");
18
+ return id
19
+ .split("-")
20
+ .filter(Boolean)
21
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
22
+ .join(" ");
23
23
  }
24
24
 
25
25
  function printHelp() {
26
- console.log(`Scaffold a new Lumia Stream plugin directory using the showcase template.\n\nUsage: npx lumia-plugin create [target-directory]\n\nExamples:\n npx lumia-plugin create ./plugins/my-plugin\n npx lumia-plugin create my-awesome-plugin\n`);
26
+ console.log(
27
+ `Scaffold a new Lumia Stream plugin directory using the showcase template.\n\nUsage: npx lumia-plugin create [target-directory]\n\nExamples:\n npx lumia-plugin create ./plugins/my-plugin\n npx lumia-plugin create my-awesome-plugin\n`
28
+ );
27
29
  }
28
30
 
29
31
  async function ensureEmptyDir(targetDir) {
30
- try {
31
- const stats = await fs.promises.stat(targetDir);
32
- if (!stats.isDirectory()) {
33
- throw new Error("Target path exists and is not a directory");
34
- }
35
- const contents = await fs.promises.readdir(targetDir);
36
- if (contents.length > 0) {
37
- throw new Error("Target directory must be empty");
38
- }
39
- } catch (error) {
40
- if (error.code === "ENOENT") {
41
- await fs.promises.mkdir(targetDir, { recursive: true });
42
- return;
43
- }
44
- throw error;
45
- }
32
+ try {
33
+ const stats = await fs.promises.stat(targetDir);
34
+ if (!stats.isDirectory()) {
35
+ throw new Error(
36
+ `Path '${path.basename(
37
+ targetDir
38
+ )}' exists and is not a directory.\n Tip: Choose a different name or remove the existing file.`
39
+ );
40
+ }
41
+ const contents = await fs.promises.readdir(targetDir);
42
+ if (contents.length > 0) {
43
+ throw new Error(
44
+ `Directory '${path.basename(
45
+ targetDir
46
+ )}' already exists and is not empty.\n Tip: Use a different name or remove existing files first.`
47
+ );
48
+ }
49
+ } catch (error) {
50
+ if (error.code === "ENOENT") {
51
+ await fs.promises.mkdir(targetDir, { recursive: true });
52
+ return;
53
+ }
54
+ throw error;
55
+ }
46
56
  }
47
57
 
48
58
  async function copyTemplate(src, dest) {
49
- const stats = await fs.promises.stat(src);
50
- if (stats.isDirectory()) {
51
- await fs.promises.mkdir(dest, { recursive: true });
52
- const entries = await fs.promises.readdir(src);
53
- for (const entry of entries) {
54
- await copyTemplate(path.join(src, entry), path.join(dest, entry));
55
- }
56
- } else if (stats.isFile()) {
57
- await fs.promises.copyFile(src, dest);
58
- }
59
+ const stats = await fs.promises.stat(src);
60
+ if (stats.isDirectory()) {
61
+ await fs.promises.mkdir(dest, { recursive: true });
62
+ const entries = await fs.promises.readdir(src);
63
+ for (const entry of entries) {
64
+ await copyTemplate(path.join(src, entry), path.join(dest, entry));
65
+ }
66
+ } else if (stats.isFile()) {
67
+ await fs.promises.copyFile(src, dest);
68
+ }
59
69
  }
60
70
 
61
71
  function ensureArray(value) {
62
- return Array.isArray(value) ? value : [];
72
+ return Array.isArray(value) ? value : [];
63
73
  }
64
74
 
65
75
  async function updateManifest(manifestPath, pluginId, displayName) {
66
- const raw = await fs.promises.readFile(manifestPath, "utf8");
67
- const manifest = JSON.parse(raw);
68
-
69
- manifest.id = pluginId;
70
- manifest.name = displayName;
71
- manifest.description = manifest.description?.trim()
72
- ? manifest.description
73
- : `Describe what ${displayName} does.`;
74
- manifest.longDescription = manifest.longDescription || "";
75
- manifest.repository = manifest.repository || "";
76
- manifest.website = manifest.website || "";
77
- manifest.email = manifest.email || "";
78
- manifest.icon = manifest.icon || "";
79
- manifest.screenshots = ensureArray(manifest.screenshots);
80
- manifest.changelog = manifest.changelog || "";
81
- manifest.keywords = ensureArray(manifest.keywords);
82
-
83
- if (!manifest.keywords.includes(pluginId)) {
84
- manifest.keywords.push(pluginId);
85
- }
86
-
87
- if (!manifest.main) {
88
- manifest.main = "main.js";
89
- }
90
-
91
- if (manifest.config && typeof manifest.config === "object") {
92
- const { settings, actions, variables } = manifest.config;
93
-
94
- const welcomeSetting = ensureArray(settings).find(
95
- (setting) => setting?.key === "welcomeMessage"
96
- );
97
- if (welcomeSetting) {
98
- welcomeSetting.defaultValue = `Hello from ${displayName}!`;
99
- }
100
-
101
- const logAction = ensureArray(actions).find(
102
- (action) => action?.type === "log_message"
103
- );
104
- if (logAction && Array.isArray(logAction.fields)) {
105
- const messageField = logAction.fields.find((field) => field?.key === "message");
106
- if (messageField) {
107
- messageField.defaultValue = `Hello from ${displayName}!`;
108
- }
109
- }
110
-
111
- for (const variable of ensureArray(variables)) {
112
- if (!variable || typeof variable !== "object") continue;
113
- if (variable.origin) {
114
- variable.origin = pluginId;
115
- }
116
- if (variable.name === "last_message" && "example" in variable) {
117
- variable.example = `Hello from ${displayName}!`;
118
- }
119
- }
120
- }
121
-
122
- await fs.promises.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
76
+ const raw = await fs.promises.readFile(manifestPath, "utf8");
77
+ const manifest = JSON.parse(raw);
78
+
79
+ manifest.id = pluginId;
80
+ manifest.name = displayName;
81
+ manifest.description = manifest.description?.trim()
82
+ ? manifest.description
83
+ : `Describe what ${displayName} does.`;
84
+ manifest.longDescription = manifest.longDescription || "";
85
+ manifest.repository = manifest.repository || "";
86
+ manifest.website = manifest.website || "";
87
+ manifest.email = manifest.email || "";
88
+ manifest.icon = manifest.icon || "";
89
+ manifest.screenshots = ensureArray(manifest.screenshots);
90
+ manifest.changelog = manifest.changelog || "";
91
+ manifest.keywords = ensureArray(manifest.keywords);
92
+
93
+ if (!manifest.keywords.includes(pluginId)) {
94
+ manifest.keywords.push(pluginId);
95
+ }
96
+
97
+ if (!manifest.main) {
98
+ manifest.main = "main.js";
99
+ }
100
+
101
+ if (manifest.config && typeof manifest.config === "object") {
102
+ const { settings, actions, variables } = manifest.config;
103
+
104
+ const welcomeSetting = ensureArray(settings).find(
105
+ (setting) => setting?.key === "welcomeMessage"
106
+ );
107
+ if (welcomeSetting) {
108
+ welcomeSetting.defaultValue = `Hello from ${displayName}!`;
109
+ }
110
+
111
+ const logAction = ensureArray(actions).find(
112
+ (action) => action?.type === "log_message"
113
+ );
114
+ if (logAction && Array.isArray(logAction.fields)) {
115
+ const messageField = logAction.fields.find(
116
+ (field) => field?.key === "message"
117
+ );
118
+ if (messageField) {
119
+ messageField.defaultValue = `Hello from ${displayName}!`;
120
+ }
121
+ }
122
+
123
+ for (const variable of ensureArray(variables)) {
124
+ if (!variable || typeof variable !== "object") continue;
125
+ if (variable.origin) {
126
+ variable.origin = pluginId;
127
+ }
128
+ if (variable.name === "last_message" && "example" in variable) {
129
+ variable.example = `Hello from ${displayName}!`;
130
+ }
131
+ }
132
+ }
133
+
134
+ await fs.promises.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
123
135
  }
124
136
 
125
137
  async function updateMain(mainPath, pluginId, className, displayName) {
126
- let source = await fs.promises.readFile(mainPath, "utf8");
127
- source = source.replace(
128
- /class\s+ShowcasePluginTemplate\s+extends\s+Plugin/,
129
- `class ${className} extends Plugin`
130
- );
131
- source = source.replace(
132
- /module\.exports\s*=\s*ShowcasePluginTemplate;/,
133
- `module.exports = ${className};`
134
- );
135
- source = source.replace(/Hello from Showcase Plugin!/g, `Hello from ${displayName}!`);
136
- source = source.replace(/Showcase Plugin/g, displayName);
137
- source = source.replace(/showcase-plugin/g, pluginId);
138
- await fs.promises.writeFile(mainPath, source);
138
+ let source = await fs.promises.readFile(mainPath, "utf8");
139
+ source = source.replace(
140
+ /class\s+ShowcasePluginTemplate\s+extends\s+Plugin/,
141
+ `class ${className} extends Plugin`
142
+ );
143
+ source = source.replace(
144
+ /module\.exports\s*=\s*ShowcasePluginTemplate;/,
145
+ `module.exports = ${className};`
146
+ );
147
+ source = source.replace(
148
+ /Hello from Showcase Plugin!/g,
149
+ `Hello from ${displayName}!`
150
+ );
151
+ source = source.replace(/Showcase Plugin/g, displayName);
152
+ source = source.replace(/showcase-plugin/g, pluginId);
153
+ await fs.promises.writeFile(mainPath, source);
139
154
  }
140
155
 
141
156
  async function updatePackageJson(packagePath, pluginId, displayName) {
142
- if (!fs.existsSync(packagePath)) return;
143
- const pkg = JSON.parse(await fs.promises.readFile(packagePath, "utf8"));
144
- pkg.name = `lumia-plugin-${pluginId}`;
145
- pkg.description = pkg.description || `${displayName} for Lumia Stream`;
146
- await fs.promises.writeFile(packagePath, JSON.stringify(pkg, null, 2));
157
+ if (!fs.existsSync(packagePath)) return;
158
+ const pkg = JSON.parse(await fs.promises.readFile(packagePath, "utf8"));
159
+ pkg.name = `lumia-plugin-${pluginId}`;
160
+ pkg.description = pkg.description || `${displayName} for Lumia Stream`;
161
+ await fs.promises.writeFile(packagePath, JSON.stringify(pkg, null, 2));
147
162
  }
148
163
 
149
164
  async function updateReadme(readmePath, displayName) {
150
- if (!fs.existsSync(readmePath)) return;
151
- let content = await fs.promises.readFile(readmePath, "utf8");
152
- content = content.replace(/^# .*$/m, `# ${displayName}`);
153
- await fs.promises.writeFile(readmePath, content);
165
+ if (!fs.existsSync(readmePath)) return;
166
+ let content = await fs.promises.readFile(readmePath, "utf8");
167
+ content = content.replace(/^# .*$/m, `# ${displayName}`);
168
+ await fs.promises.writeFile(readmePath, content);
169
+ }
170
+
171
+ async function updateGettingStarted(gettingStartedPath, displayName) {
172
+ if (!fs.existsSync(gettingStartedPath)) return;
173
+ let content = await fs.promises.readFile(gettingStartedPath, "utf8");
174
+ content = content.replace(
175
+ /^# Getting Started with .*$/m,
176
+ `# Getting Started with ${displayName}`
177
+ );
178
+ content = content.replace(/Showcase Plugin Template/g, displayName);
179
+ await fs.promises.writeFile(gettingStartedPath, content);
154
180
  }
155
181
 
156
182
  async function main() {
157
- const args = process.argv.slice(2);
158
- if (args.includes("--help") || args.includes("-h")) {
159
- printHelp();
160
- return;
161
- }
162
-
163
- if (!fs.existsSync(TEMPLATE_DIR)) {
164
- console.error("✖ Template directory not found:", TEMPLATE_DIR);
165
- process.exit(1);
166
- }
167
-
168
- const targetDir = path.resolve(args[0] || "my-plugin");
169
- const pluginId = toKebabCase(path.basename(targetDir));
170
- const displayName = toDisplayName(pluginId) || "My Plugin";
171
- const className = displayName.replace(/[^a-zA-Z0-9]/g, "") || "MyPlugin";
172
-
173
- try {
174
- await ensureEmptyDir(targetDir);
175
- await copyTemplate(TEMPLATE_DIR, targetDir);
176
-
177
- await updateManifest(
178
- path.join(targetDir, "manifest.json"),
179
- pluginId,
180
- displayName
181
- );
182
- await updateMain(
183
- path.join(targetDir, "main.js"),
184
- pluginId,
185
- className,
186
- displayName
187
- );
188
- await updatePackageJson(
189
- path.join(targetDir, "package.json"),
190
- pluginId,
191
- displayName
192
- );
193
- await updateReadme(path.join(targetDir, "README.md"), displayName);
194
-
195
- console.log("✔ Plugin scaffold created");
196
- console.log(` - directory: ${targetDir}`);
197
- console.log(" - manifest.json");
198
- console.log(" - main.js");
199
- if (fs.existsSync(path.join(targetDir, "README.md"))) {
200
- console.log(" - README.md");
201
- }
202
- if (fs.existsSync(path.join(targetDir, "package.json"))) {
203
- console.log(" - package.json");
204
- }
205
- } catch (error) {
206
- console.error(`✖ Failed to create plugin: ${error.message}`);
207
- process.exit(1);
208
- }
183
+ const args = process.argv.slice(2);
184
+ if (args.includes("--help") || args.includes("-h")) {
185
+ printHelp();
186
+ return;
187
+ }
188
+
189
+ if (!fs.existsSync(TEMPLATE_DIR)) {
190
+ console.error("✖ Template directory not found:", TEMPLATE_DIR);
191
+ console.error(" Tip: Make sure lumia-plugin is properly installed.");
192
+ process.exit(1);
193
+ }
194
+
195
+ const targetDir = path.resolve(args[0] || "my-plugin");
196
+ const pluginId = toKebabCase(path.basename(targetDir));
197
+ const displayName = toDisplayName(pluginId) || "My Plugin";
198
+ const className = displayName.replace(/[^a-zA-Z0-9]/g, "") || "MyPlugin";
199
+
200
+ try {
201
+ console.log("⏳ Creating plugin scaffold...\n");
202
+
203
+ console.log(" 📁 Creating directory...");
204
+ await ensureEmptyDir(targetDir);
205
+
206
+ console.log(" 📋 Copying template files...");
207
+ await copyTemplate(TEMPLATE_DIR, targetDir);
208
+
209
+ console.log(" ✏️ Updating manifest.json...");
210
+ await updateManifest(
211
+ path.join(targetDir, "manifest.json"),
212
+ pluginId,
213
+ displayName
214
+ );
215
+
216
+ console.log(" ✏️ Updating main.js...");
217
+ await updateMain(
218
+ path.join(targetDir, "main.js"),
219
+ pluginId,
220
+ className,
221
+ displayName
222
+ );
223
+
224
+ console.log(" ✏️ Updating package.json...");
225
+ await updatePackageJson(
226
+ path.join(targetDir, "package.json"),
227
+ pluginId,
228
+ displayName
229
+ );
230
+
231
+ console.log(" ✏️ Updating README.md...");
232
+ await updateReadme(path.join(targetDir, "README.md"), displayName);
233
+
234
+ console.log(" ✏️ Updating GETTING_STARTED.md...");
235
+ await updateGettingStarted(
236
+ path.join(targetDir, "GETTING_STARTED.md"),
237
+ displayName
238
+ );
239
+
240
+ console.log("\n✅ Plugin scaffold created successfully!\n");
241
+ console.log("📦 Created files:");
242
+ console.log(` - ${targetDir}/`);
243
+ console.log(" - manifest.json");
244
+ console.log(" - main.js");
245
+ if (fs.existsSync(path.join(targetDir, "README.md"))) {
246
+ console.log(" - README.md");
247
+ }
248
+ if (fs.existsSync(path.join(targetDir, "GETTING_STARTED.md"))) {
249
+ console.log(" - GETTING_STARTED.md");
250
+ }
251
+ if (fs.existsSync(path.join(targetDir, "package.json"))) {
252
+ console.log(" - package.json");
253
+ }
254
+
255
+ console.log("\n🚀 Next steps:");
256
+ console.log(` 1. cd ${path.basename(targetDir)}`);
257
+ console.log(" 2. npm install");
258
+ console.log(" 3. Read GETTING_STARTED.md for a complete guide");
259
+ console.log(" 4. Edit manifest.json to configure your plugin");
260
+ console.log(" 5. Start coding in main.js");
261
+ console.log("\n📖 Learn more: https://docs.lumiastream.com");
262
+ } catch (error) {
263
+ console.error(`\n✖ Failed to create plugin: ${error.message}`);
264
+ process.exit(1);
265
+ }
209
266
  }
210
267
 
211
268
  main();