@zaflun/lumio-cli 0.1.0 → 0.3.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.
- package/dist/index.js +336 -97
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// src/program.ts
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
|
|
4
|
+
// src/commands/login.ts
|
|
5
|
+
import * as http from "http";
|
|
6
|
+
import * as crypto from "crypto";
|
|
7
|
+
|
|
4
8
|
// src/auth/credential-store.ts
|
|
5
9
|
import * as fs from "fs";
|
|
6
10
|
import * as path from "path";
|
|
@@ -54,6 +58,11 @@ var CredentialStore = class {
|
|
|
54
58
|
};
|
|
55
59
|
|
|
56
60
|
// src/commands/login.ts
|
|
61
|
+
function generatePkceChallenge() {
|
|
62
|
+
const verifier = crypto.randomBytes(32).toString("base64url");
|
|
63
|
+
const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");
|
|
64
|
+
return { verifier, challenge };
|
|
65
|
+
}
|
|
57
66
|
function registerLogin(program2) {
|
|
58
67
|
program2.command("login").description("Authenticate with Lumio via browser (PKCE) or token").option("--token <token>", "Authenticate with an API token (for CI/CD)").option("--api-url <url>", "Custom API URL", "https://api.lumio.vision").action(async (options) => {
|
|
59
68
|
const store = new CredentialStore();
|
|
@@ -62,12 +71,69 @@ function registerLogin(program2) {
|
|
|
62
71
|
console.log("Authenticated with token.");
|
|
63
72
|
return;
|
|
64
73
|
}
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
const { verifier, challenge } = generatePkceChallenge();
|
|
75
|
+
const state = crypto.randomBytes(16).toString("hex");
|
|
76
|
+
const port = 9876;
|
|
77
|
+
const redirectUri = `http://localhost:${port}/callback`;
|
|
78
|
+
await new Promise((resolve10, reject) => {
|
|
79
|
+
const server = http.createServer(async (req, res) => {
|
|
80
|
+
if (!req.url?.startsWith("/callback")) {
|
|
81
|
+
res.writeHead(404);
|
|
82
|
+
res.end();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
86
|
+
const code = url.searchParams.get("code");
|
|
87
|
+
const returnedState = url.searchParams.get("state");
|
|
88
|
+
if (returnedState !== state || !code) {
|
|
89
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
90
|
+
res.end("<html><body><h2>Authentication failed.</h2><p>Invalid state or missing code. You can close this window.</p></body></html>");
|
|
91
|
+
server.close();
|
|
92
|
+
reject(new Error("Authentication failed: invalid state or missing code"));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const tokenRes = await fetch(`${options.apiUrl}/v1/auth/cli/token`, {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: { "Content-Type": "application/json" },
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
grant_type: "authorization_code",
|
|
101
|
+
code,
|
|
102
|
+
redirect_uri: redirectUri,
|
|
103
|
+
code_verifier: verifier
|
|
104
|
+
})
|
|
105
|
+
});
|
|
106
|
+
if (!tokenRes.ok) {
|
|
107
|
+
throw new Error(`Token exchange failed: ${tokenRes.status}`);
|
|
108
|
+
}
|
|
109
|
+
const tokenData = await tokenRes.json();
|
|
110
|
+
store.saveToken(tokenData.access_token, options.apiUrl);
|
|
111
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
112
|
+
res.end("<html><body><h2>Authenticated!</h2><p>You can close this window and return to the CLI.</p></body></html>");
|
|
113
|
+
} catch (e) {
|
|
114
|
+
res.writeHead(500, { "Content-Type": "text/html" });
|
|
115
|
+
res.end("<html><body><h2>Authentication failed.</h2><p>Token exchange error. You can close this window.</p></body></html>");
|
|
116
|
+
server.close();
|
|
117
|
+
reject(e);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
server.close();
|
|
121
|
+
resolve10();
|
|
122
|
+
});
|
|
123
|
+
server.listen(port, async () => {
|
|
124
|
+
const pkceUrl = `${options.apiUrl}/v1/auth/cli/authorize?code_challenge=${challenge}&code_challenge_method=S256&redirect_uri=${encodeURIComponent(redirectUri)}&state=${state}`;
|
|
125
|
+
console.log("Opening browser for authentication...");
|
|
126
|
+
const { default: open } = await import("open");
|
|
127
|
+
await open(pkceUrl);
|
|
128
|
+
console.log("Waiting for authentication callback...");
|
|
129
|
+
console.log("If the browser did not open, visit:", pkceUrl);
|
|
130
|
+
});
|
|
131
|
+
setTimeout(() => {
|
|
132
|
+
server.close();
|
|
133
|
+
reject(new Error("Authentication timed out after 120 seconds"));
|
|
134
|
+
}, 12e4);
|
|
135
|
+
});
|
|
136
|
+
console.log("Authenticated successfully.");
|
|
71
137
|
});
|
|
72
138
|
}
|
|
73
139
|
|
|
@@ -222,7 +288,7 @@ function registerInit(program2) {
|
|
|
222
288
|
}
|
|
223
289
|
} catch {
|
|
224
290
|
}
|
|
225
|
-
const
|
|
291
|
+
const baseAnswers = await inquirer.prompt([
|
|
226
292
|
{ type: "input", name: "name", message: "Extension name:", default: name },
|
|
227
293
|
{ type: "input", name: "description", message: "Description:" },
|
|
228
294
|
{
|
|
@@ -230,16 +296,35 @@ function registerInit(program2) {
|
|
|
230
296
|
name: "category",
|
|
231
297
|
message: "Category:",
|
|
232
298
|
choices: ["widget", "extension", "overlay", "bot_module", "integration", "theme"]
|
|
233
|
-
}
|
|
234
|
-
{
|
|
235
|
-
type: "checkbox",
|
|
236
|
-
name: "targets",
|
|
237
|
-
message: "Targets:",
|
|
238
|
-
choices: ["editor", "layer", "interactive"],
|
|
239
|
-
default: ["editor", "layer"]
|
|
240
|
-
},
|
|
241
|
-
{ type: "confirm", name: "server", message: "Server functions?", default: false }
|
|
299
|
+
}
|
|
242
300
|
]);
|
|
301
|
+
const answers = { ...baseAnswers, targets: [], server: false };
|
|
302
|
+
if (answers.category === "bot_module") {
|
|
303
|
+
const { platforms } = await inquirer.prompt([{
|
|
304
|
+
type: "checkbox",
|
|
305
|
+
name: "platforms",
|
|
306
|
+
message: "Which platforms should this bot module support?",
|
|
307
|
+
choices: ["twitch", "youtube", "kick", "trovo", "discord"],
|
|
308
|
+
default: ["twitch"],
|
|
309
|
+
validate: (input) => input.length > 0 || "Select at least one platform"
|
|
310
|
+
}]);
|
|
311
|
+
answers.platforms = platforms;
|
|
312
|
+
answers.targets = [];
|
|
313
|
+
answers.server = true;
|
|
314
|
+
} else {
|
|
315
|
+
const targetAndServer = await inquirer.prompt([
|
|
316
|
+
{
|
|
317
|
+
type: "checkbox",
|
|
318
|
+
name: "targets",
|
|
319
|
+
message: "Targets:",
|
|
320
|
+
choices: ["editor", "layer", "interactive"],
|
|
321
|
+
default: ["editor", "layer"]
|
|
322
|
+
},
|
|
323
|
+
{ type: "confirm", name: "server", message: "Server functions?", default: false }
|
|
324
|
+
]);
|
|
325
|
+
answers.targets = targetAndServer.targets;
|
|
326
|
+
answers.server = targetAndServer.server;
|
|
327
|
+
}
|
|
243
328
|
const createBody = {
|
|
244
329
|
name: answers.name,
|
|
245
330
|
description: answers.description,
|
|
@@ -253,7 +338,7 @@ function registerInit(program2) {
|
|
|
253
338
|
const projectDir = path2.resolve(answers.name.toLowerCase().replace(/\s+/g, "-"));
|
|
254
339
|
fs2.mkdirSync(projectDir, { recursive: true });
|
|
255
340
|
const config = {
|
|
256
|
-
$schema: "https://
|
|
341
|
+
$schema: "https://lumio.vision/schemas/lumio.config.schema.json",
|
|
257
342
|
extensionId: extension.id,
|
|
258
343
|
name: answers.name,
|
|
259
344
|
description: answers.description,
|
|
@@ -261,11 +346,21 @@ function registerInit(program2) {
|
|
|
261
346
|
version: "0.1.0",
|
|
262
347
|
targets: answers.targets,
|
|
263
348
|
server: answers.server,
|
|
264
|
-
permissions: [],
|
|
349
|
+
permissions: answers.category === "bot_module" ? ["chat:read", "chat:send"] : [],
|
|
265
350
|
egress: { allowHosts: [], allowPorts: [443] },
|
|
266
351
|
visibility: "private",
|
|
267
352
|
pricing: { type: "free" }
|
|
268
353
|
};
|
|
354
|
+
if (answers.category === "bot_module") {
|
|
355
|
+
config.platforms = answers.platforms;
|
|
356
|
+
config.triggers = {
|
|
357
|
+
commands: [{ name: "hello", description: "Say hello", cooldownGlobal: 5, cooldownUser: 10, minRole: "everyone" }],
|
|
358
|
+
keywords: [],
|
|
359
|
+
patterns: [],
|
|
360
|
+
events: [],
|
|
361
|
+
timers: []
|
|
362
|
+
};
|
|
363
|
+
}
|
|
269
364
|
fs2.writeFileSync(
|
|
270
365
|
path2.join(projectDir, "lumio.config.json"),
|
|
271
366
|
JSON.stringify(config, null, 2)
|
|
@@ -298,8 +393,15 @@ function registerInit(program2) {
|
|
|
298
393
|
`;
|
|
299
394
|
fs2.writeFileSync(path2.join(projectDir, "CHANGELOG.md"), changelog);
|
|
300
395
|
fs2.mkdirSync(path2.join(projectDir, "src"), { recursive: true });
|
|
301
|
-
|
|
302
|
-
|
|
396
|
+
if (answers.category === "bot_module") {
|
|
397
|
+
fs2.mkdirSync(path2.join(projectDir, "server"), { recursive: true });
|
|
398
|
+
const serverFunctions = 'import { command } from "@zaflun/lumio-sdk/server";\n\nexport const hello = command("hello", async (ctx, args) => {\n return { reply: `Hello ${ctx.user?.name ?? "stranger"}!` };\n});\n';
|
|
399
|
+
fs2.writeFileSync(path2.join(projectDir, "server", "functions.ts"), serverFunctions);
|
|
400
|
+
const schema = 'import { defineSchema } from "@zaflun/lumio-sdk/server";\n\nexport default defineSchema({});\n';
|
|
401
|
+
fs2.writeFileSync(path2.join(projectDir, "server", "schema.ts"), schema);
|
|
402
|
+
} else {
|
|
403
|
+
for (const target of answers.targets) {
|
|
404
|
+
const content = `import { Lumio, CompactView, Text } from "@zaflun/lumio-sdk";
|
|
303
405
|
|
|
304
406
|
function App() {
|
|
305
407
|
return (
|
|
@@ -311,14 +413,15 @@ function App() {
|
|
|
311
413
|
|
|
312
414
|
Lumio.render(<App />, { target: "${target}" });
|
|
313
415
|
`;
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
416
|
+
fs2.writeFileSync(path2.join(projectDir, "src", `${target}.tsx`), content);
|
|
417
|
+
}
|
|
418
|
+
if (answers.server) {
|
|
419
|
+
fs2.mkdirSync(path2.join(projectDir, "server"), { recursive: true });
|
|
420
|
+
const schema = 'import { defineSchema, defineTable, v } from "@zaflun/lumio-sdk/server";\n\nexport default defineSchema({\n items: defineTable({\n text: v.string(),\n completed: v.boolean(),\n }).storage("instance"),\n});\n';
|
|
421
|
+
fs2.writeFileSync(path2.join(projectDir, "server", "schema.ts"), schema);
|
|
422
|
+
const functions = 'import { queryRows, insertRow, patchRow, deleteRow, arg } from "@zaflun/lumio-sdk/server";\n\nexport const getItems = queryRows("items");\nexport const addItem = insertRow("items", { text: arg("text"), completed: false }, { editorOnly: true });\nexport const toggleItem = patchRow("items", arg("id"), { completed: arg("completed") }, { editorOnly: true });\nexport const removeItem = deleteRow("items", arg("id"), { editorOnly: true });\n';
|
|
423
|
+
fs2.writeFileSync(path2.join(projectDir, "server", "functions.ts"), functions);
|
|
424
|
+
}
|
|
322
425
|
}
|
|
323
426
|
console.log(`
|
|
324
427
|
Done! Created ${projectDir}/`);
|
|
@@ -327,17 +430,6 @@ Done! Created ${projectDir}/`);
|
|
|
327
430
|
}
|
|
328
431
|
|
|
329
432
|
// src/commands/dev.ts
|
|
330
|
-
function registerDev(program2) {
|
|
331
|
-
program2.command("dev").description("Start local dev server with hot-reload and mock events").option("-p, --port <port>", "Dev server port", "3100").action(async (options) => {
|
|
332
|
-
console.log(`Starting Lumio dev server on port ${options.port}...`);
|
|
333
|
-
console.log("Mock events enabled. Hot-reload active.");
|
|
334
|
-
console.log("Open http://localhost:" + options.port + " in your browser.");
|
|
335
|
-
console.log("\nDev server implementation requires @zaflun/lumio-sdk and Vite.");
|
|
336
|
-
console.log("Full implementation coming in Phase 12 (Live-Code-Editor).");
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// src/commands/build.ts
|
|
341
433
|
import * as fs3 from "fs";
|
|
342
434
|
import * as path3 from "path";
|
|
343
435
|
|
|
@@ -363,6 +455,8 @@ function parseConfigFile(jsonString) {
|
|
|
363
455
|
allowHosts: raw.egress?.allowHosts ?? [],
|
|
364
456
|
allowPorts: raw.egress?.allowPorts ?? []
|
|
365
457
|
},
|
|
458
|
+
platforms: raw.platforms,
|
|
459
|
+
triggers: raw.triggers,
|
|
366
460
|
fonts: raw.fonts,
|
|
367
461
|
visibility: raw.visibility ?? "private",
|
|
368
462
|
pricing: raw.pricing ?? { type: "free" }
|
|
@@ -375,7 +469,11 @@ function validateConfig(config) {
|
|
|
375
469
|
if (!config.name) {
|
|
376
470
|
throw new ConfigError("name is required in lumio.config.json");
|
|
377
471
|
}
|
|
378
|
-
if (
|
|
472
|
+
if (config.category === "bot_module") {
|
|
473
|
+
if (!config.platforms || config.platforms.length === 0) {
|
|
474
|
+
throw new ConfigError("At least one platform is required for bot_module (twitch, youtube, kick, trovo, discord)");
|
|
475
|
+
}
|
|
476
|
+
} else if (!config.targets || config.targets.length === 0) {
|
|
379
477
|
throw new ConfigError("At least one target is required (editor, layer, or interactive)");
|
|
380
478
|
}
|
|
381
479
|
if (!config.version) {
|
|
@@ -383,28 +481,130 @@ function validateConfig(config) {
|
|
|
383
481
|
}
|
|
384
482
|
}
|
|
385
483
|
|
|
484
|
+
// src/commands/dev-bot-module.ts
|
|
485
|
+
import * as readline from "readline";
|
|
486
|
+
import chalk from "chalk";
|
|
487
|
+
function parseUserPrefix(input) {
|
|
488
|
+
const defaultUser = {
|
|
489
|
+
id: "user-001",
|
|
490
|
+
name: "dev_user",
|
|
491
|
+
displayName: "Dev User",
|
|
492
|
+
platform: "twitch",
|
|
493
|
+
role: "everyone",
|
|
494
|
+
isMod: false,
|
|
495
|
+
isVip: false,
|
|
496
|
+
isSub: false
|
|
497
|
+
};
|
|
498
|
+
if (!input.startsWith("@")) {
|
|
499
|
+
return { user: defaultUser, message: input };
|
|
500
|
+
}
|
|
501
|
+
const spaceIdx = input.indexOf(" ");
|
|
502
|
+
if (spaceIdx === -1) return { user: defaultUser, message: input };
|
|
503
|
+
const prefix = input.substring(1, spaceIdx);
|
|
504
|
+
const message = input.substring(spaceIdx + 1);
|
|
505
|
+
const parts = prefix.split(":");
|
|
506
|
+
const name = parts[parts.length - 1];
|
|
507
|
+
const rolePrefix = parts.length > 1 ? parts[0] : "everyone";
|
|
508
|
+
return {
|
|
509
|
+
user: {
|
|
510
|
+
id: `user-${name}`,
|
|
511
|
+
name,
|
|
512
|
+
displayName: name,
|
|
513
|
+
platform: "twitch",
|
|
514
|
+
role: rolePrefix,
|
|
515
|
+
isMod: rolePrefix === "mod",
|
|
516
|
+
isVip: rolePrefix === "vip",
|
|
517
|
+
isSub: ["sub", "vip", "mod", "broadcaster"].includes(rolePrefix)
|
|
518
|
+
},
|
|
519
|
+
message
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
async function runBotModuleDevServer(config) {
|
|
523
|
+
console.log(chalk.bold("\n\u{1F916} Bot Module Dev Server"));
|
|
524
|
+
console.log(`Extension: ${config.name}`);
|
|
525
|
+
console.log(`Platforms: ${config.platforms?.join(", ") ?? "none"}`);
|
|
526
|
+
console.log(chalk.dim("\nType chat messages. Prefix with @username or @role:username."));
|
|
527
|
+
console.log(chalk.dim("Examples: !hello | @sub:viewer !points 100 | @mod:admin gg"));
|
|
528
|
+
console.log(chalk.dim("Type !quit to exit.\n"));
|
|
529
|
+
const rl = readline.createInterface({
|
|
530
|
+
input: process.stdin,
|
|
531
|
+
output: process.stdout,
|
|
532
|
+
prompt: chalk.cyan("> ")
|
|
533
|
+
});
|
|
534
|
+
rl.prompt();
|
|
535
|
+
rl.on("line", async (line) => {
|
|
536
|
+
const trimmed = line.trim();
|
|
537
|
+
if (!trimmed) {
|
|
538
|
+
rl.prompt();
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (trimmed === "!quit") {
|
|
542
|
+
rl.close();
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const { user, message } = parseUserPrefix(trimmed);
|
|
546
|
+
console.log(chalk.dim(` [${user.role}:${user.name}] ${message}`));
|
|
547
|
+
console.log(chalk.yellow(" \u2192 Handler execution not yet wired (requires esbuild + V8)"));
|
|
548
|
+
rl.prompt();
|
|
549
|
+
});
|
|
550
|
+
rl.on("close", () => {
|
|
551
|
+
console.log(chalk.dim("\nBot Module Dev Server stopped."));
|
|
552
|
+
process.exit(0);
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/commands/dev.ts
|
|
557
|
+
function registerDev(program2) {
|
|
558
|
+
program2.command("dev").description("Start local dev server with hot-reload and mock events").option("-p, --port <port>", "Dev server port", "3100").option("--bot-module", "Start interactive bot module chat simulator").action(async (options) => {
|
|
559
|
+
const configPath = path3.resolve("lumio.config.json");
|
|
560
|
+
if (options.botModule || fs3.existsSync(configPath)) {
|
|
561
|
+
const configExists = fs3.existsSync(configPath);
|
|
562
|
+
if (configExists) {
|
|
563
|
+
const config = parseConfigFile(fs3.readFileSync(configPath, "utf-8"));
|
|
564
|
+
if (options.botModule || config.category === "bot_module") {
|
|
565
|
+
await runBotModuleDevServer(config);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
} else if (options.botModule) {
|
|
569
|
+
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
570
|
+
process.exit(1);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
console.log(`Starting Lumio dev server on port ${options.port}...`);
|
|
574
|
+
console.log("Mock events enabled. Hot-reload active.");
|
|
575
|
+
console.log("Open http://localhost:" + options.port + " in your browser.");
|
|
576
|
+
console.log("\nDev server implementation requires @zaflun/lumio-sdk and Vite.");
|
|
577
|
+
console.log("Full implementation coming in Phase 12 (Live-Code-Editor).");
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
|
|
386
581
|
// src/commands/build.ts
|
|
582
|
+
import * as fs4 from "fs";
|
|
583
|
+
import * as path4 from "path";
|
|
387
584
|
function registerBuild(program2) {
|
|
388
585
|
program2.command("build").description("Bundle extension as standalone HTML + source archive").action(async () => {
|
|
389
|
-
const configPath =
|
|
390
|
-
if (!
|
|
586
|
+
const configPath = path4.resolve("lumio.config.json");
|
|
587
|
+
if (!fs4.existsSync(configPath)) {
|
|
391
588
|
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
392
589
|
process.exit(1);
|
|
393
590
|
}
|
|
394
|
-
const configJson =
|
|
591
|
+
const configJson = fs4.readFileSync(configPath, "utf-8");
|
|
395
592
|
const config = parseConfigFile(configJson);
|
|
396
593
|
validateConfig(config);
|
|
397
|
-
const changelogPath =
|
|
398
|
-
if (!
|
|
594
|
+
const changelogPath = path4.resolve("CHANGELOG.md");
|
|
595
|
+
if (!fs4.existsSync(changelogPath)) {
|
|
399
596
|
console.warn("Warning: No CHANGELOG.md found. It is recommended to maintain a changelog.");
|
|
400
597
|
}
|
|
401
598
|
console.log(`Building ${config.name} v${config.version}...`);
|
|
402
|
-
const distDir =
|
|
403
|
-
|
|
599
|
+
const distDir = path4.resolve("dist");
|
|
600
|
+
fs4.mkdirSync(distDir, { recursive: true });
|
|
404
601
|
const { build } = await import("esbuild");
|
|
602
|
+
if (config.category === "bot_module" && config.targets.length === 0) {
|
|
603
|
+
console.log(" Bot module \u2014 no UI targets to bundle.");
|
|
604
|
+
}
|
|
405
605
|
for (const target of config.targets) {
|
|
406
|
-
const entryPoint =
|
|
407
|
-
if (!
|
|
606
|
+
const entryPoint = path4.resolve("src", `${target}.tsx`);
|
|
607
|
+
if (!fs4.existsSync(entryPoint)) {
|
|
408
608
|
console.warn(`Warning: Entry point ${entryPoint} not found, skipping target "${target}".`);
|
|
409
609
|
continue;
|
|
410
610
|
}
|
|
@@ -413,7 +613,7 @@ function registerBuild(program2) {
|
|
|
413
613
|
bundle: true,
|
|
414
614
|
minify: true,
|
|
415
615
|
format: "esm",
|
|
416
|
-
outfile:
|
|
616
|
+
outfile: path4.join(distDir, `${target}.js`),
|
|
417
617
|
jsx: "automatic",
|
|
418
618
|
target: "es2022",
|
|
419
619
|
external: [],
|
|
@@ -424,17 +624,17 @@ function registerBuild(program2) {
|
|
|
424
624
|
console.log(` Built: dist/${target}.js`);
|
|
425
625
|
}
|
|
426
626
|
const { default: archiver } = await import("archiver");
|
|
427
|
-
const sourceArchivePath =
|
|
428
|
-
const output =
|
|
627
|
+
const sourceArchivePath = path4.join(distDir, "source.tar.gz");
|
|
628
|
+
const output = fs4.createWriteStream(sourceArchivePath);
|
|
429
629
|
const archive = archiver("tar", { gzip: true });
|
|
430
630
|
archive.pipe(output);
|
|
431
631
|
archive.directory("src/", "src");
|
|
432
|
-
if (config.server &&
|
|
632
|
+
if ((config.server || config.category === "bot_module") && fs4.existsSync("server")) {
|
|
433
633
|
archive.directory("server/", "server");
|
|
434
634
|
}
|
|
435
635
|
archive.file("lumio.config.json", { name: "lumio.config.json" });
|
|
436
636
|
archive.file("package.json", { name: "package.json" });
|
|
437
|
-
if (
|
|
637
|
+
if (fs4.existsSync("CHANGELOG.md")) {
|
|
438
638
|
archive.file("CHANGELOG.md", { name: "CHANGELOG.md" });
|
|
439
639
|
}
|
|
440
640
|
await archive.finalize();
|
|
@@ -444,8 +644,8 @@ function registerBuild(program2) {
|
|
|
444
644
|
}
|
|
445
645
|
|
|
446
646
|
// src/commands/deploy.ts
|
|
447
|
-
import * as
|
|
448
|
-
import * as
|
|
647
|
+
import * as fs5 from "fs";
|
|
648
|
+
import * as path5 from "path";
|
|
449
649
|
|
|
450
650
|
// src/changelog/parser.ts
|
|
451
651
|
var VERSION_HEADER_REGEX = /^## \[([^\]]+)\](?:\s*-\s*(\d{4}-\d{2}-\d{2}))?/;
|
|
@@ -491,20 +691,20 @@ function registerDeploy(program2) {
|
|
|
491
691
|
console.error("Not logged in. Run `lumio login` first.");
|
|
492
692
|
process.exit(1);
|
|
493
693
|
}
|
|
494
|
-
const configPath =
|
|
495
|
-
if (!
|
|
694
|
+
const configPath = path5.resolve("lumio.config.json");
|
|
695
|
+
if (!fs5.existsSync(configPath)) {
|
|
496
696
|
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
497
697
|
process.exit(1);
|
|
498
698
|
}
|
|
499
|
-
const configJson =
|
|
699
|
+
const configJson = fs5.readFileSync(configPath, "utf-8");
|
|
500
700
|
const config = parseConfigFile(configJson);
|
|
501
701
|
validateConfig(config);
|
|
502
702
|
const version = options.version ?? config.version;
|
|
503
703
|
let releaseNotes = options.description ?? "";
|
|
504
704
|
if (!releaseNotes) {
|
|
505
|
-
const changelogPath =
|
|
506
|
-
if (
|
|
507
|
-
const changelog =
|
|
705
|
+
const changelogPath = path5.resolve("CHANGELOG.md");
|
|
706
|
+
if (fs5.existsSync(changelogPath)) {
|
|
707
|
+
const changelog = fs5.readFileSync(changelogPath, "utf-8");
|
|
508
708
|
const entry = getVersionEntry(changelog, version);
|
|
509
709
|
if (entry) {
|
|
510
710
|
releaseNotes = entry.content;
|
|
@@ -515,21 +715,23 @@ function registerDeploy(program2) {
|
|
|
515
715
|
}
|
|
516
716
|
}
|
|
517
717
|
}
|
|
518
|
-
const distDir =
|
|
519
|
-
if (!
|
|
718
|
+
const distDir = path5.resolve("dist");
|
|
719
|
+
if (!fs5.existsSync(distDir)) {
|
|
520
720
|
console.error("No dist/ directory found. Run `lumio build` first.");
|
|
521
721
|
process.exit(1);
|
|
522
722
|
}
|
|
523
723
|
console.log(`Deploying ${config.name} v${version}...`);
|
|
524
724
|
const client = new LumioApiClient(store.getApiUrl(), token);
|
|
525
725
|
const formData = new FormData();
|
|
526
|
-
const sourceArchive =
|
|
527
|
-
if (
|
|
528
|
-
const sourceBuffer =
|
|
726
|
+
const sourceArchive = path5.join(distDir, "source.tar.gz");
|
|
727
|
+
if (fs5.existsSync(sourceArchive)) {
|
|
728
|
+
const sourceBuffer = fs5.readFileSync(sourceArchive);
|
|
529
729
|
formData.append("source", new Blob([sourceBuffer]), "source.tar.gz");
|
|
530
730
|
}
|
|
731
|
+
const manifest = JSON.parse(configJson);
|
|
732
|
+
manifest.version = version;
|
|
531
733
|
formData.append("version", version);
|
|
532
|
-
formData.append("manifest",
|
|
734
|
+
formData.append("manifest", JSON.stringify(manifest));
|
|
533
735
|
formData.append("release_notes", releaseNotes);
|
|
534
736
|
formData.append("is_breaking", String(!!options.breaking));
|
|
535
737
|
try {
|
|
@@ -550,8 +752,8 @@ function registerDeploy(program2) {
|
|
|
550
752
|
}
|
|
551
753
|
|
|
552
754
|
// src/commands/status.ts
|
|
553
|
-
import * as
|
|
554
|
-
import * as
|
|
755
|
+
import * as fs6 from "fs";
|
|
756
|
+
import * as path6 from "path";
|
|
555
757
|
function registerStatus(program2) {
|
|
556
758
|
program2.command("status").description("Show review status of the latest version").action(async () => {
|
|
557
759
|
const store = new CredentialStore();
|
|
@@ -560,12 +762,12 @@ function registerStatus(program2) {
|
|
|
560
762
|
console.error("Not logged in. Run `lumio login` first.");
|
|
561
763
|
process.exit(1);
|
|
562
764
|
}
|
|
563
|
-
const configPath =
|
|
564
|
-
if (!
|
|
765
|
+
const configPath = path6.resolve("lumio.config.json");
|
|
766
|
+
if (!fs6.existsSync(configPath)) {
|
|
565
767
|
console.error("lumio.config.json not found.");
|
|
566
768
|
process.exit(1);
|
|
567
769
|
}
|
|
568
|
-
const config = parseConfigFile(
|
|
770
|
+
const config = parseConfigFile(fs6.readFileSync(configPath, "utf-8"));
|
|
569
771
|
const client = new LumioApiClient(store.getApiUrl(), token);
|
|
570
772
|
try {
|
|
571
773
|
const versions = await client.get(
|
|
@@ -594,8 +796,8 @@ function registerStatus(program2) {
|
|
|
594
796
|
}
|
|
595
797
|
|
|
596
798
|
// src/commands/logs.ts
|
|
597
|
-
import * as
|
|
598
|
-
import * as
|
|
799
|
+
import * as fs7 from "fs";
|
|
800
|
+
import * as path7 from "path";
|
|
599
801
|
function registerLogs(program2) {
|
|
600
802
|
program2.command("logs").description("Stream server function logs via SSE").option("-f, --follow", "Follow log output", true).option("-n, --lines <count>", "Number of recent lines", "50").action(async (options) => {
|
|
601
803
|
const store = new CredentialStore();
|
|
@@ -604,12 +806,12 @@ function registerLogs(program2) {
|
|
|
604
806
|
console.error("Not logged in. Run `lumio login` first.");
|
|
605
807
|
process.exit(1);
|
|
606
808
|
}
|
|
607
|
-
const configPath =
|
|
608
|
-
if (!
|
|
809
|
+
const configPath = path7.resolve("lumio.config.json");
|
|
810
|
+
if (!fs7.existsSync(configPath)) {
|
|
609
811
|
console.error("lumio.config.json not found.");
|
|
610
812
|
process.exit(1);
|
|
611
813
|
}
|
|
612
|
-
const config = parseConfigFile(
|
|
814
|
+
const config = parseConfigFile(fs7.readFileSync(configPath, "utf-8"));
|
|
613
815
|
const apiUrl = store.getApiUrl();
|
|
614
816
|
const logsUrl = `${apiUrl}/v1/developer/extensions/${config.extensionId}/logs?lines=${options.lines}`;
|
|
615
817
|
console.log(`Streaming logs for ${config.name}...`);
|
|
@@ -646,8 +848,8 @@ function registerLogs(program2) {
|
|
|
646
848
|
}
|
|
647
849
|
|
|
648
850
|
// src/commands/env.ts
|
|
649
|
-
import * as
|
|
650
|
-
import * as
|
|
851
|
+
import * as fs8 from "fs";
|
|
852
|
+
import * as path8 from "path";
|
|
651
853
|
function validateSecretKey(key) {
|
|
652
854
|
return key.length > 0 && /^[A-Z0-9_]+$/.test(key);
|
|
653
855
|
}
|
|
@@ -656,12 +858,12 @@ function formatSecretList(secrets) {
|
|
|
656
858
|
return secrets.map((s) => ` ${s.key} (created ${s.created_at})`).join("\n");
|
|
657
859
|
}
|
|
658
860
|
function loadConfig() {
|
|
659
|
-
const configPath =
|
|
660
|
-
if (!
|
|
861
|
+
const configPath = path8.resolve("lumio.config.json");
|
|
862
|
+
if (!fs8.existsSync(configPath)) {
|
|
661
863
|
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
662
864
|
process.exit(1);
|
|
663
865
|
}
|
|
664
|
-
return parseConfigFile(
|
|
866
|
+
return parseConfigFile(fs8.readFileSync(configPath, "utf-8"));
|
|
665
867
|
}
|
|
666
868
|
function requireClient() {
|
|
667
869
|
const store = new CredentialStore();
|
|
@@ -703,8 +905,27 @@ function registerEnv(program2) {
|
|
|
703
905
|
}
|
|
704
906
|
|
|
705
907
|
// src/commands/run.ts
|
|
706
|
-
import * as
|
|
707
|
-
import * as
|
|
908
|
+
import * as fs9 from "fs";
|
|
909
|
+
import * as path9 from "path";
|
|
910
|
+
var VALID_FUNCTION_TYPES = [
|
|
911
|
+
"query",
|
|
912
|
+
"mutation",
|
|
913
|
+
"action",
|
|
914
|
+
"command",
|
|
915
|
+
"keyword",
|
|
916
|
+
"pattern",
|
|
917
|
+
"event",
|
|
918
|
+
"timer",
|
|
919
|
+
"moderate"
|
|
920
|
+
];
|
|
921
|
+
var BOT_HANDLER_TYPES = [
|
|
922
|
+
"command",
|
|
923
|
+
"keyword",
|
|
924
|
+
"pattern",
|
|
925
|
+
"event",
|
|
926
|
+
"timer",
|
|
927
|
+
"moderate"
|
|
928
|
+
];
|
|
708
929
|
function parseFunctionRef(ref) {
|
|
709
930
|
const colonIndex = ref.indexOf(":");
|
|
710
931
|
if (colonIndex === -1) {
|
|
@@ -712,20 +933,22 @@ function parseFunctionRef(ref) {
|
|
|
712
933
|
}
|
|
713
934
|
const type = ref.slice(0, colonIndex);
|
|
714
935
|
const name = ref.slice(colonIndex + 1);
|
|
715
|
-
if (!
|
|
716
|
-
throw new Error(
|
|
936
|
+
if (!VALID_FUNCTION_TYPES.includes(type)) {
|
|
937
|
+
throw new Error(
|
|
938
|
+
`Function type must be one of: ${VALID_FUNCTION_TYPES.join(", ")}. Got: "${type}"`
|
|
939
|
+
);
|
|
717
940
|
}
|
|
718
941
|
return { type, name };
|
|
719
942
|
}
|
|
720
943
|
function registerRun(program2) {
|
|
721
|
-
program2.command("run <type:name> [args]").description("Execute a server function locally (e.g. lumio run query:getPlayers)").action(async (typeAndName, argsJson) => {
|
|
944
|
+
program2.command("run <type:name> [args]").description("Execute a server function locally (e.g. lumio run query:getPlayers)").option("--user <name>", "Mock user name for bot handlers", "testuser").option("--role <role>", "Mock user role for bot handlers", "everyone").option("--message <text>", "Mock chat message for bot handlers").option("--platform <platform>", "Mock platform for bot handlers", "twitch").action(async (typeAndName, argsJson, options) => {
|
|
722
945
|
const fnRef = parseFunctionRef(typeAndName);
|
|
723
|
-
const configPath =
|
|
724
|
-
if (!
|
|
946
|
+
const configPath = path9.resolve("lumio.config.json");
|
|
947
|
+
if (!fs9.existsSync(configPath)) {
|
|
725
948
|
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
726
949
|
process.exit(1);
|
|
727
950
|
}
|
|
728
|
-
const config = parseConfigFile(
|
|
951
|
+
const config = parseConfigFile(fs9.readFileSync(configPath, "utf-8"));
|
|
729
952
|
const store = new CredentialStore();
|
|
730
953
|
const token = store.getToken();
|
|
731
954
|
if (!token) {
|
|
@@ -734,10 +957,26 @@ function registerRun(program2) {
|
|
|
734
957
|
}
|
|
735
958
|
const client = new LumioApiClient(store.getApiUrl(), token);
|
|
736
959
|
const args = argsJson ? JSON.parse(argsJson) : {};
|
|
960
|
+
const body = { type: fnRef.type, args };
|
|
961
|
+
if (BOT_HANDLER_TYPES.includes(fnRef.type)) {
|
|
962
|
+
body.user = {
|
|
963
|
+
id: "test-user-001",
|
|
964
|
+
name: options.user,
|
|
965
|
+
displayName: options.user,
|
|
966
|
+
platform: options.platform,
|
|
967
|
+
role: options.role,
|
|
968
|
+
isMod: options.role === "moderator",
|
|
969
|
+
isVip: options.role === "vip",
|
|
970
|
+
isSub: ["subscriber", "vip", "moderator", "broadcaster"].includes(options.role)
|
|
971
|
+
};
|
|
972
|
+
if (options.message) {
|
|
973
|
+
body.message = { text: options.message, platformMessageId: "test-msg-001" };
|
|
974
|
+
}
|
|
975
|
+
}
|
|
737
976
|
try {
|
|
738
977
|
const result = await client.post(
|
|
739
978
|
`/v1/extension-installs/${config.extensionId}/execute/${fnRef.name}`,
|
|
740
|
-
|
|
979
|
+
body
|
|
741
980
|
);
|
|
742
981
|
console.log(JSON.stringify(result, null, 2));
|
|
743
982
|
} catch (err) {
|
|
@@ -748,16 +987,16 @@ function registerRun(program2) {
|
|
|
748
987
|
}
|
|
749
988
|
|
|
750
989
|
// src/commands/dashboard.ts
|
|
751
|
-
import * as
|
|
752
|
-
import * as
|
|
990
|
+
import * as fs10 from "fs";
|
|
991
|
+
import * as path10 from "path";
|
|
753
992
|
function registerDashboard(program2) {
|
|
754
993
|
program2.command("dashboard").description("Open the extension dashboard in browser").action(async () => {
|
|
755
|
-
const configPath =
|
|
756
|
-
if (!
|
|
994
|
+
const configPath = path10.resolve("lumio.config.json");
|
|
995
|
+
if (!fs10.existsSync(configPath)) {
|
|
757
996
|
console.error("lumio.config.json not found. Run `lumio init` first.");
|
|
758
997
|
process.exit(1);
|
|
759
998
|
}
|
|
760
|
-
const config = parseConfigFile(
|
|
999
|
+
const config = parseConfigFile(fs10.readFileSync(configPath, "utf-8"));
|
|
761
1000
|
const baseUrl = process.env.LUMIO_WEB_URL ?? "https://lumio.vision";
|
|
762
1001
|
const url = `${baseUrl}/dashboard/extensions/${config.extensionId}`;
|
|
763
1002
|
console.log(`Opening ${url}...`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zaflun/lumio-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Lumio Extension CLI — Build, test, and deploy Lumio extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"inquirer": "^12.6.0",
|
|
23
23
|
"open": "^10.1.2",
|
|
24
24
|
"ora": "^8.2.0",
|
|
25
|
-
"@zaflun/lumio-extension-types": "0.
|
|
25
|
+
"@zaflun/lumio-extension-types": "0.3.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/archiver": "^6.0.3",
|