@zaflun/lumio-cli 0.2.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.
Files changed (2) hide show
  1. package/dist/index.js +262 -91
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -75,7 +75,7 @@ function registerLogin(program2) {
75
75
  const state = crypto.randomBytes(16).toString("hex");
76
76
  const port = 9876;
77
77
  const redirectUri = `http://localhost:${port}/callback`;
78
- await new Promise((resolve9, reject) => {
78
+ await new Promise((resolve10, reject) => {
79
79
  const server = http.createServer(async (req, res) => {
80
80
  if (!req.url?.startsWith("/callback")) {
81
81
  res.writeHead(404);
@@ -118,7 +118,7 @@ function registerLogin(program2) {
118
118
  return;
119
119
  }
120
120
  server.close();
121
- resolve9();
121
+ resolve10();
122
122
  });
123
123
  server.listen(port, async () => {
124
124
  const pkceUrl = `${options.apiUrl}/v1/auth/cli/authorize?code_challenge=${challenge}&code_challenge_method=S256&redirect_uri=${encodeURIComponent(redirectUri)}&state=${state}`;
@@ -288,7 +288,7 @@ function registerInit(program2) {
288
288
  }
289
289
  } catch {
290
290
  }
291
- const answers = await inquirer.prompt([
291
+ const baseAnswers = await inquirer.prompt([
292
292
  { type: "input", name: "name", message: "Extension name:", default: name },
293
293
  { type: "input", name: "description", message: "Description:" },
294
294
  {
@@ -296,16 +296,35 @@ function registerInit(program2) {
296
296
  name: "category",
297
297
  message: "Category:",
298
298
  choices: ["widget", "extension", "overlay", "bot_module", "integration", "theme"]
299
- },
300
- {
301
- type: "checkbox",
302
- name: "targets",
303
- message: "Targets:",
304
- choices: ["editor", "layer", "interactive"],
305
- default: ["editor", "layer"]
306
- },
307
- { type: "confirm", name: "server", message: "Server functions?", default: false }
299
+ }
308
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
+ }
309
328
  const createBody = {
310
329
  name: answers.name,
311
330
  description: answers.description,
@@ -327,11 +346,21 @@ function registerInit(program2) {
327
346
  version: "0.1.0",
328
347
  targets: answers.targets,
329
348
  server: answers.server,
330
- permissions: [],
349
+ permissions: answers.category === "bot_module" ? ["chat:read", "chat:send"] : [],
331
350
  egress: { allowHosts: [], allowPorts: [443] },
332
351
  visibility: "private",
333
352
  pricing: { type: "free" }
334
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
+ }
335
364
  fs2.writeFileSync(
336
365
  path2.join(projectDir, "lumio.config.json"),
337
366
  JSON.stringify(config, null, 2)
@@ -364,8 +393,15 @@ function registerInit(program2) {
364
393
  `;
365
394
  fs2.writeFileSync(path2.join(projectDir, "CHANGELOG.md"), changelog);
366
395
  fs2.mkdirSync(path2.join(projectDir, "src"), { recursive: true });
367
- for (const target of answers.targets) {
368
- const content = `import { Lumio, CompactView, Text } from "@zaflun/lumio-sdk";
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";
369
405
 
370
406
  function App() {
371
407
  return (
@@ -377,14 +413,15 @@ function App() {
377
413
 
378
414
  Lumio.render(<App />, { target: "${target}" });
379
415
  `;
380
- fs2.writeFileSync(path2.join(projectDir, "src", `${target}.tsx`), content);
381
- }
382
- if (answers.server) {
383
- fs2.mkdirSync(path2.join(projectDir, "server"), { recursive: true });
384
- 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';
385
- fs2.writeFileSync(path2.join(projectDir, "server", "schema.ts"), schema);
386
- 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';
387
- fs2.writeFileSync(path2.join(projectDir, "server", "functions.ts"), functions);
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
+ }
388
425
  }
389
426
  console.log(`
390
427
  Done! Created ${projectDir}/`);
@@ -393,17 +430,6 @@ Done! Created ${projectDir}/`);
393
430
  }
394
431
 
395
432
  // src/commands/dev.ts
396
- function registerDev(program2) {
397
- 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) => {
398
- console.log(`Starting Lumio dev server on port ${options.port}...`);
399
- console.log("Mock events enabled. Hot-reload active.");
400
- console.log("Open http://localhost:" + options.port + " in your browser.");
401
- console.log("\nDev server implementation requires @zaflun/lumio-sdk and Vite.");
402
- console.log("Full implementation coming in Phase 12 (Live-Code-Editor).");
403
- });
404
- }
405
-
406
- // src/commands/build.ts
407
433
  import * as fs3 from "fs";
408
434
  import * as path3 from "path";
409
435
 
@@ -429,6 +455,8 @@ function parseConfigFile(jsonString) {
429
455
  allowHosts: raw.egress?.allowHosts ?? [],
430
456
  allowPorts: raw.egress?.allowPorts ?? []
431
457
  },
458
+ platforms: raw.platforms,
459
+ triggers: raw.triggers,
432
460
  fonts: raw.fonts,
433
461
  visibility: raw.visibility ?? "private",
434
462
  pricing: raw.pricing ?? { type: "free" }
@@ -441,7 +469,11 @@ function validateConfig(config) {
441
469
  if (!config.name) {
442
470
  throw new ConfigError("name is required in lumio.config.json");
443
471
  }
444
- if (!config.targets || config.targets.length === 0) {
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) {
445
477
  throw new ConfigError("At least one target is required (editor, layer, or interactive)");
446
478
  }
447
479
  if (!config.version) {
@@ -449,28 +481,130 @@ function validateConfig(config) {
449
481
  }
450
482
  }
451
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
+
452
581
  // src/commands/build.ts
582
+ import * as fs4 from "fs";
583
+ import * as path4 from "path";
453
584
  function registerBuild(program2) {
454
585
  program2.command("build").description("Bundle extension as standalone HTML + source archive").action(async () => {
455
- const configPath = path3.resolve("lumio.config.json");
456
- if (!fs3.existsSync(configPath)) {
586
+ const configPath = path4.resolve("lumio.config.json");
587
+ if (!fs4.existsSync(configPath)) {
457
588
  console.error("lumio.config.json not found. Run `lumio init` first.");
458
589
  process.exit(1);
459
590
  }
460
- const configJson = fs3.readFileSync(configPath, "utf-8");
591
+ const configJson = fs4.readFileSync(configPath, "utf-8");
461
592
  const config = parseConfigFile(configJson);
462
593
  validateConfig(config);
463
- const changelogPath = path3.resolve("CHANGELOG.md");
464
- if (!fs3.existsSync(changelogPath)) {
594
+ const changelogPath = path4.resolve("CHANGELOG.md");
595
+ if (!fs4.existsSync(changelogPath)) {
465
596
  console.warn("Warning: No CHANGELOG.md found. It is recommended to maintain a changelog.");
466
597
  }
467
598
  console.log(`Building ${config.name} v${config.version}...`);
468
- const distDir = path3.resolve("dist");
469
- fs3.mkdirSync(distDir, { recursive: true });
599
+ const distDir = path4.resolve("dist");
600
+ fs4.mkdirSync(distDir, { recursive: true });
470
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
+ }
471
605
  for (const target of config.targets) {
472
- const entryPoint = path3.resolve("src", `${target}.tsx`);
473
- if (!fs3.existsSync(entryPoint)) {
606
+ const entryPoint = path4.resolve("src", `${target}.tsx`);
607
+ if (!fs4.existsSync(entryPoint)) {
474
608
  console.warn(`Warning: Entry point ${entryPoint} not found, skipping target "${target}".`);
475
609
  continue;
476
610
  }
@@ -479,7 +613,7 @@ function registerBuild(program2) {
479
613
  bundle: true,
480
614
  minify: true,
481
615
  format: "esm",
482
- outfile: path3.join(distDir, `${target}.js`),
616
+ outfile: path4.join(distDir, `${target}.js`),
483
617
  jsx: "automatic",
484
618
  target: "es2022",
485
619
  external: [],
@@ -490,17 +624,17 @@ function registerBuild(program2) {
490
624
  console.log(` Built: dist/${target}.js`);
491
625
  }
492
626
  const { default: archiver } = await import("archiver");
493
- const sourceArchivePath = path3.join(distDir, "source.tar.gz");
494
- const output = fs3.createWriteStream(sourceArchivePath);
627
+ const sourceArchivePath = path4.join(distDir, "source.tar.gz");
628
+ const output = fs4.createWriteStream(sourceArchivePath);
495
629
  const archive = archiver("tar", { gzip: true });
496
630
  archive.pipe(output);
497
631
  archive.directory("src/", "src");
498
- if (config.server && fs3.existsSync("server")) {
632
+ if ((config.server || config.category === "bot_module") && fs4.existsSync("server")) {
499
633
  archive.directory("server/", "server");
500
634
  }
501
635
  archive.file("lumio.config.json", { name: "lumio.config.json" });
502
636
  archive.file("package.json", { name: "package.json" });
503
- if (fs3.existsSync("CHANGELOG.md")) {
637
+ if (fs4.existsSync("CHANGELOG.md")) {
504
638
  archive.file("CHANGELOG.md", { name: "CHANGELOG.md" });
505
639
  }
506
640
  await archive.finalize();
@@ -510,8 +644,8 @@ function registerBuild(program2) {
510
644
  }
511
645
 
512
646
  // src/commands/deploy.ts
513
- import * as fs4 from "fs";
514
- import * as path4 from "path";
647
+ import * as fs5 from "fs";
648
+ import * as path5 from "path";
515
649
 
516
650
  // src/changelog/parser.ts
517
651
  var VERSION_HEADER_REGEX = /^## \[([^\]]+)\](?:\s*-\s*(\d{4}-\d{2}-\d{2}))?/;
@@ -557,20 +691,20 @@ function registerDeploy(program2) {
557
691
  console.error("Not logged in. Run `lumio login` first.");
558
692
  process.exit(1);
559
693
  }
560
- const configPath = path4.resolve("lumio.config.json");
561
- if (!fs4.existsSync(configPath)) {
694
+ const configPath = path5.resolve("lumio.config.json");
695
+ if (!fs5.existsSync(configPath)) {
562
696
  console.error("lumio.config.json not found. Run `lumio init` first.");
563
697
  process.exit(1);
564
698
  }
565
- const configJson = fs4.readFileSync(configPath, "utf-8");
699
+ const configJson = fs5.readFileSync(configPath, "utf-8");
566
700
  const config = parseConfigFile(configJson);
567
701
  validateConfig(config);
568
702
  const version = options.version ?? config.version;
569
703
  let releaseNotes = options.description ?? "";
570
704
  if (!releaseNotes) {
571
- const changelogPath = path4.resolve("CHANGELOG.md");
572
- if (fs4.existsSync(changelogPath)) {
573
- const changelog = fs4.readFileSync(changelogPath, "utf-8");
705
+ const changelogPath = path5.resolve("CHANGELOG.md");
706
+ if (fs5.existsSync(changelogPath)) {
707
+ const changelog = fs5.readFileSync(changelogPath, "utf-8");
574
708
  const entry = getVersionEntry(changelog, version);
575
709
  if (entry) {
576
710
  releaseNotes = entry.content;
@@ -581,17 +715,17 @@ function registerDeploy(program2) {
581
715
  }
582
716
  }
583
717
  }
584
- const distDir = path4.resolve("dist");
585
- if (!fs4.existsSync(distDir)) {
718
+ const distDir = path5.resolve("dist");
719
+ if (!fs5.existsSync(distDir)) {
586
720
  console.error("No dist/ directory found. Run `lumio build` first.");
587
721
  process.exit(1);
588
722
  }
589
723
  console.log(`Deploying ${config.name} v${version}...`);
590
724
  const client = new LumioApiClient(store.getApiUrl(), token);
591
725
  const formData = new FormData();
592
- const sourceArchive = path4.join(distDir, "source.tar.gz");
593
- if (fs4.existsSync(sourceArchive)) {
594
- const sourceBuffer = fs4.readFileSync(sourceArchive);
726
+ const sourceArchive = path5.join(distDir, "source.tar.gz");
727
+ if (fs5.existsSync(sourceArchive)) {
728
+ const sourceBuffer = fs5.readFileSync(sourceArchive);
595
729
  formData.append("source", new Blob([sourceBuffer]), "source.tar.gz");
596
730
  }
597
731
  const manifest = JSON.parse(configJson);
@@ -618,8 +752,8 @@ function registerDeploy(program2) {
618
752
  }
619
753
 
620
754
  // src/commands/status.ts
621
- import * as fs5 from "fs";
622
- import * as path5 from "path";
755
+ import * as fs6 from "fs";
756
+ import * as path6 from "path";
623
757
  function registerStatus(program2) {
624
758
  program2.command("status").description("Show review status of the latest version").action(async () => {
625
759
  const store = new CredentialStore();
@@ -628,12 +762,12 @@ function registerStatus(program2) {
628
762
  console.error("Not logged in. Run `lumio login` first.");
629
763
  process.exit(1);
630
764
  }
631
- const configPath = path5.resolve("lumio.config.json");
632
- if (!fs5.existsSync(configPath)) {
765
+ const configPath = path6.resolve("lumio.config.json");
766
+ if (!fs6.existsSync(configPath)) {
633
767
  console.error("lumio.config.json not found.");
634
768
  process.exit(1);
635
769
  }
636
- const config = parseConfigFile(fs5.readFileSync(configPath, "utf-8"));
770
+ const config = parseConfigFile(fs6.readFileSync(configPath, "utf-8"));
637
771
  const client = new LumioApiClient(store.getApiUrl(), token);
638
772
  try {
639
773
  const versions = await client.get(
@@ -662,8 +796,8 @@ function registerStatus(program2) {
662
796
  }
663
797
 
664
798
  // src/commands/logs.ts
665
- import * as fs6 from "fs";
666
- import * as path6 from "path";
799
+ import * as fs7 from "fs";
800
+ import * as path7 from "path";
667
801
  function registerLogs(program2) {
668
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) => {
669
803
  const store = new CredentialStore();
@@ -672,12 +806,12 @@ function registerLogs(program2) {
672
806
  console.error("Not logged in. Run `lumio login` first.");
673
807
  process.exit(1);
674
808
  }
675
- const configPath = path6.resolve("lumio.config.json");
676
- if (!fs6.existsSync(configPath)) {
809
+ const configPath = path7.resolve("lumio.config.json");
810
+ if (!fs7.existsSync(configPath)) {
677
811
  console.error("lumio.config.json not found.");
678
812
  process.exit(1);
679
813
  }
680
- const config = parseConfigFile(fs6.readFileSync(configPath, "utf-8"));
814
+ const config = parseConfigFile(fs7.readFileSync(configPath, "utf-8"));
681
815
  const apiUrl = store.getApiUrl();
682
816
  const logsUrl = `${apiUrl}/v1/developer/extensions/${config.extensionId}/logs?lines=${options.lines}`;
683
817
  console.log(`Streaming logs for ${config.name}...`);
@@ -714,8 +848,8 @@ function registerLogs(program2) {
714
848
  }
715
849
 
716
850
  // src/commands/env.ts
717
- import * as fs7 from "fs";
718
- import * as path7 from "path";
851
+ import * as fs8 from "fs";
852
+ import * as path8 from "path";
719
853
  function validateSecretKey(key) {
720
854
  return key.length > 0 && /^[A-Z0-9_]+$/.test(key);
721
855
  }
@@ -724,12 +858,12 @@ function formatSecretList(secrets) {
724
858
  return secrets.map((s) => ` ${s.key} (created ${s.created_at})`).join("\n");
725
859
  }
726
860
  function loadConfig() {
727
- const configPath = path7.resolve("lumio.config.json");
728
- if (!fs7.existsSync(configPath)) {
861
+ const configPath = path8.resolve("lumio.config.json");
862
+ if (!fs8.existsSync(configPath)) {
729
863
  console.error("lumio.config.json not found. Run `lumio init` first.");
730
864
  process.exit(1);
731
865
  }
732
- return parseConfigFile(fs7.readFileSync(configPath, "utf-8"));
866
+ return parseConfigFile(fs8.readFileSync(configPath, "utf-8"));
733
867
  }
734
868
  function requireClient() {
735
869
  const store = new CredentialStore();
@@ -771,8 +905,27 @@ function registerEnv(program2) {
771
905
  }
772
906
 
773
907
  // src/commands/run.ts
774
- import * as fs8 from "fs";
775
- import * as path8 from "path";
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
+ ];
776
929
  function parseFunctionRef(ref) {
777
930
  const colonIndex = ref.indexOf(":");
778
931
  if (colonIndex === -1) {
@@ -780,20 +933,22 @@ function parseFunctionRef(ref) {
780
933
  }
781
934
  const type = ref.slice(0, colonIndex);
782
935
  const name = ref.slice(colonIndex + 1);
783
- if (!["query", "mutation", "action"].includes(type)) {
784
- throw new Error(`Function type must be query, mutation, or action. Got: "${type}"`);
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
+ );
785
940
  }
786
941
  return { type, name };
787
942
  }
788
943
  function registerRun(program2) {
789
- 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) => {
790
945
  const fnRef = parseFunctionRef(typeAndName);
791
- const configPath = path8.resolve("lumio.config.json");
792
- if (!fs8.existsSync(configPath)) {
946
+ const configPath = path9.resolve("lumio.config.json");
947
+ if (!fs9.existsSync(configPath)) {
793
948
  console.error("lumio.config.json not found. Run `lumio init` first.");
794
949
  process.exit(1);
795
950
  }
796
- const config = parseConfigFile(fs8.readFileSync(configPath, "utf-8"));
951
+ const config = parseConfigFile(fs9.readFileSync(configPath, "utf-8"));
797
952
  const store = new CredentialStore();
798
953
  const token = store.getToken();
799
954
  if (!token) {
@@ -802,10 +957,26 @@ function registerRun(program2) {
802
957
  }
803
958
  const client = new LumioApiClient(store.getApiUrl(), token);
804
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
+ }
805
976
  try {
806
977
  const result = await client.post(
807
978
  `/v1/extension-installs/${config.extensionId}/execute/${fnRef.name}`,
808
- { type: fnRef.type, args }
979
+ body
809
980
  );
810
981
  console.log(JSON.stringify(result, null, 2));
811
982
  } catch (err) {
@@ -816,16 +987,16 @@ function registerRun(program2) {
816
987
  }
817
988
 
818
989
  // src/commands/dashboard.ts
819
- import * as fs9 from "fs";
820
- import * as path9 from "path";
990
+ import * as fs10 from "fs";
991
+ import * as path10 from "path";
821
992
  function registerDashboard(program2) {
822
993
  program2.command("dashboard").description("Open the extension dashboard in browser").action(async () => {
823
- const configPath = path9.resolve("lumio.config.json");
824
- if (!fs9.existsSync(configPath)) {
994
+ const configPath = path10.resolve("lumio.config.json");
995
+ if (!fs10.existsSync(configPath)) {
825
996
  console.error("lumio.config.json not found. Run `lumio init` first.");
826
997
  process.exit(1);
827
998
  }
828
- const config = parseConfigFile(fs9.readFileSync(configPath, "utf-8"));
999
+ const config = parseConfigFile(fs10.readFileSync(configPath, "utf-8"));
829
1000
  const baseUrl = process.env.LUMIO_WEB_URL ?? "https://lumio.vision";
830
1001
  const url = `${baseUrl}/dashboard/extensions/${config.extensionId}`;
831
1002
  console.log(`Opening ${url}...`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zaflun/lumio-cli",
3
- "version": "0.2.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.1.0"
25
+ "@zaflun/lumio-extension-types": "0.3.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/archiver": "^6.0.3",