lumia-plugin 0.5.0 → 0.5.2

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/README.md CHANGED
@@ -8,6 +8,19 @@ The `lumia-plugin` package bundles the command-line tools for creating, building
8
8
  npx lumia-plugin create <directory>
9
9
  npx lumia-plugin build [--dir <path>] [--out <file>]
10
10
  npx lumia-plugin validate <plugin-directory>
11
+ npx lumia-plugin skills [tool] [--target <path>] [--codex-home <path>]
12
+ ```
13
+
14
+ Quick examples:
15
+
16
+ ```bash
17
+ npx lumia-plugin skills
18
+ npx lumia-plugin skills claude
19
+ npx lumia-plugin skills copilot
20
+ npx lumia-plugin skills gemini
21
+ npx lumia-plugin skills cursor
22
+ npx lumia-plugin skills codex
23
+ npx lumia-plugin skills codex --codex-home "$CODEX_HOME"
11
24
  ```
12
25
 
13
26
  Run any command with `--help` to see detailed options.
@@ -5,6 +5,6 @@
5
5
  "description": "Internal template illustrating settings, actions, variables, and alerts for Lumia Stream plugins.",
6
6
  "main": "main.js",
7
7
  "dependencies": {
8
- "@lumiastream/plugin": "^0.5.0"
8
+ "@lumiastream/plugin": "^0.5.2"
9
9
  }
10
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumia-plugin",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Command-line tools for creating, building, and validating Lumia Stream plugins.",
5
5
  "bin": {
6
6
  "lumia-plugin": "scripts/cli.js"
@@ -24,7 +24,7 @@
24
24
  "author": "Lumia Stream",
25
25
  "license": "MIT",
26
26
  "dependencies": {
27
- "@lumiastream/plugin": "^0.5.0",
27
+ "@lumiastream/plugin": "^0.5.2",
28
28
  "jszip": "3.10.1"
29
29
  }
30
30
  }
package/scripts/cli.js CHANGED
@@ -9,6 +9,7 @@ const commands = {
9
9
  create: "create-plugin.js",
10
10
  build: "build-plugin.js",
11
11
  validate: "validate-plugin.js",
12
+ skills: "skills.js",
12
13
  };
13
14
 
14
15
  const scriptArgs = args.slice(1);
@@ -22,11 +23,19 @@ Commands:
22
23
  create [directory] Create a new plugin from template
23
24
  build [directory] Build a plugin into .lumiaplugin package
24
25
  validate [file] Validate a .lumiaplugin package
26
+ skills [tool] Install/update skills
25
27
 
26
28
  Examples:
27
29
  lumia-plugin create my_plugin
28
30
  lumia-plugin build ./my_plugin
29
31
  lumia-plugin validate my_plugin.lumiaplugin
32
+ lumia-plugin skills
33
+ lumia-plugin skills claude
34
+ lumia-plugin skills copilot
35
+ lumia-plugin skills gemini
36
+ lumia-plugin skills cursor
37
+ lumia-plugin skills codex
38
+ lumia-plugin skills codex --codex-home "$CODEX_HOME"
30
39
  `);
31
40
  process.exit(command ? 1 : 0);
32
41
  }
@@ -0,0 +1,364 @@
1
+ #!/usr/bin/env node
2
+ const https = require("https");
3
+ const path = require("path");
4
+ const fs = require("fs");
5
+ const os = require("os");
6
+
7
+ const RAW_BASE = "https://raw.githubusercontent.com/lumiastream/Plugin-SDK/main";
8
+
9
+ const PROJECT_TOOL_FILES = {
10
+ claude: [
11
+ {
12
+ remote:
13
+ "skills/lumia-plugin-claude-skill/lumia-plugin-claude-skill.md",
14
+ local: "CLAUDE.md",
15
+ },
16
+ ],
17
+ copilot: [
18
+ {
19
+ remote: ".github/copilot-instructions.md",
20
+ local: ".github/copilot-instructions.md",
21
+ },
22
+ ],
23
+ gemini: [
24
+ {
25
+ remote: "GEMINI.md",
26
+ local: "GEMINI.md",
27
+ },
28
+ ],
29
+ cursor: [
30
+ {
31
+ remote: ".cursor/rules/lumia-plugin-workflow.mdc",
32
+ local: ".cursor/rules/lumia-plugin-workflow.mdc",
33
+ },
34
+ {
35
+ remote: ".cursor/rules/lumia-plugin-manifest-contracts.mdc",
36
+ local: ".cursor/rules/lumia-plugin-manifest-contracts.mdc",
37
+ },
38
+ {
39
+ remote: "skills/lumia-plugin-codex-skill/scripts/plugin-audit.js",
40
+ local: "scripts/plugin-audit.js",
41
+ },
42
+ ],
43
+ };
44
+
45
+ const CODEX_TOOL_FILES = [
46
+ {
47
+ remote: "skills/lumia-plugin-codex-skill/SKILL.md",
48
+ local: "skills/lumia-plugin-codex-skill/SKILL.md",
49
+ },
50
+ {
51
+ remote: "skills/lumia-plugin-codex-skill/agents/openai.yaml",
52
+ local: "skills/lumia-plugin-codex-skill/agents/openai.yaml",
53
+ },
54
+ {
55
+ remote: "skills/lumia-plugin-codex-skill/scripts/plugin-audit.js",
56
+ local: "skills/lumia-plugin-codex-skill/scripts/plugin-audit.js",
57
+ },
58
+ {
59
+ remote: "skills/lumia-plugin-codex-skill/references/workflow.md",
60
+ local: "skills/lumia-plugin-codex-skill/references/workflow.md",
61
+ },
62
+ {
63
+ remote:
64
+ "skills/lumia-plugin-codex-skill/references/manifest-capability-contracts.md",
65
+ local:
66
+ "skills/lumia-plugin-codex-skill/references/manifest-capability-contracts.md",
67
+ },
68
+ {
69
+ remote: "skills/lumia-plugin-codex-skill/references/sdk-docs/INDEX.md",
70
+ local: "skills/lumia-plugin-codex-skill/references/sdk-docs/INDEX.md",
71
+ },
72
+ {
73
+ remote: "skills/lumia-plugin-codex-skill/references/sdk-docs/README.md",
74
+ local: "skills/lumia-plugin-codex-skill/references/sdk-docs/README.md",
75
+ },
76
+ {
77
+ remote:
78
+ "skills/lumia-plugin-codex-skill/references/sdk-docs/docs__getting-started.md",
79
+ local:
80
+ "skills/lumia-plugin-codex-skill/references/sdk-docs/docs__getting-started.md",
81
+ },
82
+ {
83
+ remote:
84
+ "skills/lumia-plugin-codex-skill/references/sdk-docs/docs__manifest-guide.md",
85
+ local:
86
+ "skills/lumia-plugin-codex-skill/references/sdk-docs/docs__manifest-guide.md",
87
+ },
88
+ {
89
+ remote:
90
+ "skills/lumia-plugin-codex-skill/references/sdk-docs/docs__api-reference.md",
91
+ local:
92
+ "skills/lumia-plugin-codex-skill/references/sdk-docs/docs__api-reference.md",
93
+ },
94
+ {
95
+ remote:
96
+ "skills/lumia-plugin-codex-skill/references/sdk-docs/docs__field-types-reference.md",
97
+ local:
98
+ "skills/lumia-plugin-codex-skill/references/sdk-docs/docs__field-types-reference.md",
99
+ },
100
+ {
101
+ remote:
102
+ "skills/lumia-plugin-codex-skill/references/sdk-docs/docs__custom-overlays-interop.md",
103
+ local:
104
+ "skills/lumia-plugin-codex-skill/references/sdk-docs/docs__custom-overlays-interop.md",
105
+ },
106
+ ];
107
+
108
+ function printHelp() {
109
+ console.log(`Install/update Lumia skills from GitHub.
110
+
111
+ Usage:
112
+ npx lumia-plugin skills [options]
113
+ npx lumia-plugin skills install [options]
114
+ npx lumia-plugin skills list
115
+ npx lumia-plugin skills <tool> [options]
116
+
117
+ Options:
118
+ --target <path> Project path to update (default: current directory)
119
+ --tools <list> Comma-separated: claude,copilot,gemini,cursor,codex
120
+ (default: claude,copilot,gemini,cursor,codex)
121
+ --codex-home <path> Codex home path used when tools include codex
122
+ (default: $CODEX_HOME or ~/.codex)
123
+ --dry-run Print planned updates without writing files
124
+ --help, -h Show this help
125
+
126
+ Examples:
127
+ npx lumia-plugin skills
128
+ npx lumia-plugin skills claude
129
+ npx lumia-plugin skills copilot
130
+ npx lumia-plugin skills gemini
131
+ npx lumia-plugin skills cursor
132
+ npx lumia-plugin skills codex
133
+ npx lumia-plugin skills codex --codex-home "$CODEX_HOME"
134
+ npx lumia-plugin skills --target ./my_plugin
135
+ npx lumia-plugin skills --tools claude,copilot,gemini,cursor
136
+ npx lumia-plugin skills --tools codex --codex-home "$CODEX_HOME"
137
+ `);
138
+ }
139
+
140
+ function printList() {
141
+ console.log(`Available skill bundles:
142
+
143
+ - claude
144
+ - CLAUDE.md
145
+
146
+ - copilot
147
+ - .github/copilot-instructions.md
148
+
149
+ - gemini
150
+ - GEMINI.md
151
+
152
+ - cursor
153
+ - .cursor/rules/lumia-plugin-workflow.mdc
154
+ - .cursor/rules/lumia-plugin-manifest-contracts.mdc
155
+ - scripts/plugin-audit.js
156
+
157
+ - codex (installed into $CODEX_HOME or ~/.codex)
158
+ - skills/lumia-plugin-codex-skill/*
159
+
160
+ Tip: run "npx lumia-plugin skills --dry-run" to preview all file changes.
161
+ `);
162
+ }
163
+
164
+ function parseArgs(argv) {
165
+ const args = argv.slice(2);
166
+ const defaultCodexHome = process.env.CODEX_HOME
167
+ ? path.resolve(process.env.CODEX_HOME)
168
+ : path.join(os.homedir(), ".codex");
169
+ const parsed = {
170
+ action: "install",
171
+ target: process.cwd(),
172
+ tools: ["claude", "copilot", "gemini", "cursor", "codex"],
173
+ codexHome: defaultCodexHome,
174
+ dryRun: false,
175
+ };
176
+
177
+ if (args[0] === "list") {
178
+ parsed.action = "list";
179
+ args.shift();
180
+ } else if (args[0] === "install") {
181
+ args.shift();
182
+ }
183
+
184
+ const validTools = new Set(["claude", "copilot", "gemini", "cursor", "codex"]);
185
+ const positionalTools = [];
186
+ while (args.length && !args[0].startsWith("-") && validTools.has(args[0])) {
187
+ positionalTools.push(args.shift());
188
+ }
189
+ if (positionalTools.length > 0) {
190
+ parsed.tools = positionalTools;
191
+ }
192
+
193
+ for (let i = 0; i < args.length; i += 1) {
194
+ const arg = args[i];
195
+ if (arg === "--help" || arg === "-h") {
196
+ parsed.action = "help";
197
+ continue;
198
+ }
199
+ if (arg === "--dry-run") {
200
+ parsed.dryRun = true;
201
+ continue;
202
+ }
203
+ if (arg === "--target") {
204
+ if (!args[i + 1]) {
205
+ throw new Error("Expected a path after --target");
206
+ }
207
+ parsed.target = path.resolve(args[i + 1]);
208
+ i += 1;
209
+ continue;
210
+ }
211
+ if (arg === "--codex-home") {
212
+ if (!args[i + 1]) {
213
+ throw new Error("Expected a path after --codex-home");
214
+ }
215
+ parsed.codexHome = path.resolve(args[i + 1]);
216
+ i += 1;
217
+ continue;
218
+ }
219
+ if (arg === "--tools") {
220
+ if (!args[i + 1]) {
221
+ throw new Error("Expected a list after --tools");
222
+ }
223
+ parsed.tools = args[i + 1]
224
+ .split(",")
225
+ .map((item) => item.trim().toLowerCase())
226
+ .filter(Boolean);
227
+ i += 1;
228
+ continue;
229
+ }
230
+ throw new Error(`Unknown argument: ${arg}`);
231
+ }
232
+
233
+ return parsed;
234
+ }
235
+
236
+ function fetchText(url, redirectCount = 0) {
237
+ return new Promise((resolve, reject) => {
238
+ const request = https.get(url, (response) => {
239
+ const status = response.statusCode || 0;
240
+ if ([301, 302, 307, 308].includes(status)) {
241
+ if (redirectCount >= 5) {
242
+ reject(new Error(`Too many redirects for ${url}`));
243
+ return;
244
+ }
245
+ const location = response.headers.location;
246
+ if (!location) {
247
+ reject(new Error(`Redirect missing location for ${url}`));
248
+ return;
249
+ }
250
+ response.resume();
251
+ const nextUrl = new URL(location, url).toString();
252
+ resolve(fetchText(nextUrl, redirectCount + 1));
253
+ return;
254
+ }
255
+
256
+ if (status !== 200) {
257
+ let errorBody = "";
258
+ response.setEncoding("utf8");
259
+ response.on("data", (chunk) => {
260
+ errorBody += chunk;
261
+ });
262
+ response.on("end", () => {
263
+ reject(
264
+ new Error(
265
+ `Request failed (${status}) for ${url}${
266
+ errorBody ? `: ${errorBody.slice(0, 200)}` : ""
267
+ }`
268
+ )
269
+ );
270
+ });
271
+ return;
272
+ }
273
+
274
+ let body = "";
275
+ response.setEncoding("utf8");
276
+ response.on("data", (chunk) => {
277
+ body += chunk;
278
+ });
279
+ response.on("end", () => resolve(body));
280
+ });
281
+
282
+ request.on("error", reject);
283
+ });
284
+ }
285
+
286
+ function validateTools(tools) {
287
+ const valid = new Set(["claude", "copilot", "gemini", "cursor", "codex"]);
288
+ for (const tool of tools) {
289
+ if (!valid.has(tool)) {
290
+ throw new Error(
291
+ `Invalid tool "${tool}". Valid values: claude,copilot,gemini,cursor,codex`
292
+ );
293
+ }
294
+ }
295
+ }
296
+
297
+ function buildPlan({ target, tools, codexHome }) {
298
+ const plan = [];
299
+ for (const tool of tools) {
300
+ if (tool === "codex") {
301
+ for (const file of CODEX_TOOL_FILES) {
302
+ plan.push({
303
+ tool,
304
+ url: `${RAW_BASE}/${file.remote}`,
305
+ destination: path.resolve(codexHome, file.local),
306
+ });
307
+ }
308
+ continue;
309
+ }
310
+
311
+ for (const file of PROJECT_TOOL_FILES[tool] || []) {
312
+ plan.push({
313
+ tool,
314
+ url: `${RAW_BASE}/${file.remote}`,
315
+ destination: path.resolve(target, file.local),
316
+ });
317
+ }
318
+ }
319
+ return plan;
320
+ }
321
+
322
+ async function writePlan(plan, dryRun) {
323
+ for (const item of plan) {
324
+ console.log(`[${item.tool}] ${item.url} -> ${item.destination}`);
325
+ if (dryRun) {
326
+ continue;
327
+ }
328
+ const text = await fetchText(item.url);
329
+ await fs.promises.mkdir(path.dirname(item.destination), { recursive: true });
330
+ await fs.promises.writeFile(item.destination, text, "utf8");
331
+ }
332
+ }
333
+
334
+ async function main() {
335
+ const options = parseArgs(process.argv);
336
+
337
+ if (options.action === "help") {
338
+ printHelp();
339
+ return;
340
+ }
341
+ if (options.action === "list") {
342
+ printList();
343
+ return;
344
+ }
345
+
346
+ validateTools(options.tools);
347
+ const plan = buildPlan(options);
348
+ if (!plan.length) {
349
+ throw new Error("No files selected to update.");
350
+ }
351
+
352
+ await writePlan(plan, options.dryRun);
353
+
354
+ if (options.dryRun) {
355
+ console.log(`Dry run complete. ${plan.length} files planned.`);
356
+ return;
357
+ }
358
+ console.log(`Updated ${plan.length} skills.`);
359
+ }
360
+
361
+ main().catch((error) => {
362
+ console.error(`✖ Skills command failed: ${error.message || error}`);
363
+ process.exit(1);
364
+ });