u-foo 1.2.16 → 1.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/src/cli.js CHANGED
@@ -6,6 +6,7 @@ const { socketPath, isRunning } = require("./daemon");
6
6
  const { runBusCoreCommand } = require("./cli/busCoreCommands");
7
7
  const { runCtxCommand } = require("./cli/ctxCoreCommands");
8
8
  const { runOnlineCommand } = require("./cli/onlineCoreCommands");
9
+ const { runGroupCoreCommand } = require("./cli/groupCoreCommands");
9
10
 
10
11
  function getPackageRoot() {
11
12
  return path.resolve(__dirname, "..");
@@ -561,6 +562,189 @@ async function runCli(argv) {
561
562
  }
562
563
  });
563
564
 
565
+ const group = program.command("group").description("Agent group template commands");
566
+ group
567
+ .command("templates")
568
+ .description("List available group templates")
569
+ .argument("[action]", "list", "list")
570
+ .option("--json", "Output as JSON")
571
+ .action(async (action, opts) => {
572
+ const normalizedAction = String(action || "list").trim().toLowerCase();
573
+ if (normalizedAction !== "list" && normalizedAction !== "ls") {
574
+ console.error(`Unknown group templates action: ${normalizedAction}`);
575
+ process.exitCode = 1;
576
+ return;
577
+ }
578
+ try {
579
+ await runGroupCoreCommand("templates", [normalizedAction], {
580
+ cwd: process.cwd(),
581
+ json: opts.json,
582
+ });
583
+ } catch (err) {
584
+ console.error(err.message || String(err));
585
+ process.exitCode = 1;
586
+ }
587
+ });
588
+
589
+ group
590
+ .command("template")
591
+ .description("Group template operations")
592
+ .argument("<action>", "list|show|validate|new")
593
+ .argument("[target]", "Template alias (or path for validate)")
594
+ .option("--from <alias>", "Builtin template alias (for template new)")
595
+ .option("--global", "Create template in ~/.ufoo/templates/groups")
596
+ .option("--project", "Create template in .ufoo/templates/groups (default)")
597
+ .option("--force", "Overwrite existing file (for template new)")
598
+ .option("--json", "Output as JSON")
599
+ .action(async (action, target, opts) => {
600
+ const args = [action];
601
+ if (target) args.push(target);
602
+ if (opts.from) args.push("--from", opts.from);
603
+ if (opts.global) args.push("--global");
604
+ if (opts.project) args.push("--project");
605
+ if (opts.force) args.push("--force");
606
+ if (opts.json) args.push("--json");
607
+
608
+ try {
609
+ await runGroupCoreCommand("template", args, {
610
+ cwd: process.cwd(),
611
+ json: opts.json,
612
+ });
613
+ } catch (err) {
614
+ console.error(err.message || String(err));
615
+ process.exitCode = 1;
616
+ }
617
+ });
618
+
619
+ group
620
+ .command("run")
621
+ .description("Launch an agent group from template")
622
+ .argument("<alias>", "Template alias")
623
+ .option("--instance <name>", "Group instance ID")
624
+ .option("--dry-run", "Validate and compile launch plan without starting agents")
625
+ .option("--json", "Output daemon response as JSON")
626
+ .action(async (alias, opts) => {
627
+ try {
628
+ const projectRoot = process.cwd();
629
+ await ensureDaemonRunning(projectRoot);
630
+ const resp = await sendDaemonRequest(projectRoot, {
631
+ type: "launch_group",
632
+ alias,
633
+ instance: opts.instance || "",
634
+ dry_run: opts.dryRun === true,
635
+ });
636
+ if (opts.json) {
637
+ console.log(JSON.stringify(resp?.data || {}, null, 2));
638
+ return;
639
+ }
640
+ const reply = resp?.data?.reply || "Group run requested";
641
+ console.log(reply);
642
+ if (resp?.data?.group?.ok === false) {
643
+ process.exitCode = 1;
644
+ }
645
+ } catch (err) {
646
+ console.error(err.message || String(err));
647
+ process.exitCode = 1;
648
+ }
649
+ });
650
+
651
+ group
652
+ .command("status")
653
+ .description("Show group runtime status (single group or list)")
654
+ .argument("[groupId]", "Group ID (optional)")
655
+ .option("--json", "Output daemon response as JSON")
656
+ .action(async (groupId, opts) => {
657
+ try {
658
+ const projectRoot = process.cwd();
659
+ await ensureDaemonRunning(projectRoot);
660
+ const resp = await sendDaemonRequest(projectRoot, {
661
+ type: "group_status",
662
+ group_id: groupId || "",
663
+ });
664
+ if (opts.json) {
665
+ console.log(JSON.stringify(resp?.data || {}, null, 2));
666
+ return;
667
+ }
668
+ const reply = resp?.data?.reply || "Group status requested";
669
+ console.log(reply);
670
+ const group = resp?.data?.group || {};
671
+ if (groupId && group?.group) {
672
+ console.log(JSON.stringify(group.group, null, 2));
673
+ } else if (!groupId && Array.isArray(group?.groups)) {
674
+ group.groups.forEach((item) => {
675
+ console.log(`- ${item.group_id} [${item.status}] ${item.template_alias} active=${item.members_active}/${item.members_total}`);
676
+ });
677
+ }
678
+ } catch (err) {
679
+ console.error(err.message || String(err));
680
+ process.exitCode = 1;
681
+ }
682
+ });
683
+
684
+ group
685
+ .command("diagram")
686
+ .description("Render group diagram from template alias or group runtime ID")
687
+ .argument("<target>", "Template alias or group ID")
688
+ .option("--ascii", "Render ASCII diagram (default)")
689
+ .option("--mermaid", "Render Mermaid flowchart")
690
+ .option("--json", "Output daemon response as JSON")
691
+ .action(async (target, opts) => {
692
+ try {
693
+ const projectRoot = process.cwd();
694
+ await ensureDaemonRunning(projectRoot);
695
+ const format = opts.mermaid ? "mermaid" : "ascii";
696
+ const resp = await sendDaemonRequest(projectRoot, {
697
+ type: "group_diagram",
698
+ alias: target,
699
+ group_id: target,
700
+ format,
701
+ });
702
+ if (opts.json) {
703
+ console.log(JSON.stringify(resp?.data || {}, null, 2));
704
+ return;
705
+ }
706
+ const reply = resp?.data?.reply || "Group diagram requested";
707
+ console.log(reply);
708
+ if (resp?.data?.group?.diagram) {
709
+ console.log(resp.data.group.diagram);
710
+ }
711
+ if (resp?.data?.group?.ok === false) {
712
+ process.exitCode = 1;
713
+ }
714
+ } catch (err) {
715
+ console.error(err.message || String(err));
716
+ process.exitCode = 1;
717
+ }
718
+ });
719
+
720
+ group
721
+ .command("stop")
722
+ .description("Stop a running group instance")
723
+ .argument("<groupId>", "Group ID")
724
+ .option("--json", "Output daemon response as JSON")
725
+ .action(async (groupId, opts) => {
726
+ try {
727
+ const projectRoot = process.cwd();
728
+ await ensureDaemonRunning(projectRoot);
729
+ const resp = await sendDaemonRequest(projectRoot, {
730
+ type: "stop_group",
731
+ group_id: groupId,
732
+ });
733
+ if (opts.json) {
734
+ console.log(JSON.stringify(resp?.data || {}, null, 2));
735
+ return;
736
+ }
737
+ const reply = resp?.data?.reply || "Group stop requested";
738
+ console.log(reply);
739
+ if (resp?.data?.group?.ok === false) {
740
+ process.exitCode = 1;
741
+ }
742
+ } catch (err) {
743
+ console.error(err.message || String(err));
744
+ process.exitCode = 1;
745
+ }
746
+ });
747
+
564
748
  const online = program.command("online").description("ufoo online helpers");
565
749
  online
566
750
  .command("server")
@@ -616,6 +800,7 @@ async function runCli(argv) {
616
800
  .option("--name <room>", "Room name (optional)")
617
801
  .option("--type <type>", "Room type (public|private)")
618
802
  .option("--password <pwd>", "Room password (private only)")
803
+ .option("--created-by <name>", "Room creator nickname metadata")
619
804
  .action(async (action, opts) => {
620
805
  try {
621
806
  await runOnlineCommand("room", { action, opts }, {
@@ -640,6 +825,7 @@ async function runCli(argv) {
640
825
  .option("--nickname <name>", "Nickname to resolve token")
641
826
  .option("--name <name>", "Channel name (unique)")
642
827
  .option("--type <type>", "Channel type (world|public)")
828
+ .option("--created-by <name>", "Channel creator nickname metadata")
643
829
  .action(async (action, opts) => {
644
830
  try {
645
831
  await runOnlineCommand("channel", { action, opts }, {
@@ -934,11 +1120,17 @@ async function runCli(argv) {
934
1120
  console.log(" ufoo init [--modules <list>] [--project <dir>]");
935
1121
  console.log(" ufoo skills list");
936
1122
  console.log(" ufoo skills install <name|all> [--target <dir> | --codex | --agents]");
1123
+ console.log(" ufoo group templates [list|ls] [--json]");
1124
+ console.log(" ufoo group template <list|show|validate|new> [target] [--from <builtin>] [--global] [--force] [--json]");
1125
+ console.log(" ufoo group run <alias> [--instance <name>] [--dry-run] [--json]");
1126
+ console.log(" ufoo group status [groupId] [--json]");
1127
+ console.log(" ufoo group diagram <alias|groupId> [--ascii|--mermaid] [--json]");
1128
+ console.log(" ufoo group stop <groupId> [--json]");
937
1129
  console.log(" ufoo online server [--port 8787] [--host 127.0.0.1] [--token-file <path>]");
938
1130
  console.log(" ufoo online token <subscriber> [--nickname <name>] [--server <url>] [--file <path>]");
939
- console.log(" ufoo online room create [--name <room>] --type public|private [--password <pwd>] [--server <url>]");
1131
+ console.log(" ufoo online room create [--name <room>] --type public|private [--password <pwd>] [--created-by <name>] [--server <url>]");
940
1132
  console.log(" ufoo online room list [--server <url>]");
941
- console.log(" ufoo online channel create --name <name> [--type world|public] [--server <url>]");
1133
+ console.log(" ufoo online channel create --name <name> [--type world|public] [--created-by <name>] [--server <url>]");
942
1134
  console.log(" ufoo online channel list [--server <url>]");
943
1135
  console.log(" ufoo online connect --nickname <name> [--join <ch>] [--room <id> --room-password <pwd>] [...]");
944
1136
  console.log(" ufoo online send --nickname <name> --text <msg> [--channel <ch>] [--room <id>]");
@@ -1271,6 +1463,137 @@ async function runCli(argv) {
1271
1463
  process.exitCode = 1;
1272
1464
  return;
1273
1465
  }
1466
+ if (cmd === "group") {
1467
+ const sub = String(rest[0] || "").trim().toLowerCase();
1468
+
1469
+ (async () => {
1470
+ try {
1471
+ if (sub === "templates") {
1472
+ const action = String(rest[1] || "list").trim().toLowerCase();
1473
+ if (action !== "list" && action !== "ls") {
1474
+ throw new Error(`Unknown group templates action: ${action}`);
1475
+ }
1476
+ const json = rest.includes("--json");
1477
+ await runGroupCoreCommand("templates", [action], {
1478
+ cwd: process.cwd(),
1479
+ json,
1480
+ });
1481
+ return;
1482
+ }
1483
+
1484
+ if (sub === "template") {
1485
+ const action = String(rest[1] || "list").trim().toLowerCase();
1486
+ const args = [action, ...rest.slice(2)];
1487
+ const json = rest.includes("--json");
1488
+ await runGroupCoreCommand("template", args, {
1489
+ cwd: process.cwd(),
1490
+ json,
1491
+ });
1492
+ return;
1493
+ }
1494
+
1495
+ if (sub === "run") {
1496
+ const alias = String(rest[1] || "").trim();
1497
+ if (!alias) throw new Error("group run requires <alias>");
1498
+ const instanceIdx = rest.indexOf("--instance");
1499
+ const instance = instanceIdx !== -1 ? String(rest[instanceIdx + 1] || "").trim() : "";
1500
+ const dryRun = rest.includes("--dry-run");
1501
+ const outputJson = rest.includes("--json");
1502
+
1503
+ const projectRoot = process.cwd();
1504
+ await ensureDaemonRunning(projectRoot);
1505
+ const resp = await sendDaemonRequest(projectRoot, {
1506
+ type: "launch_group",
1507
+ alias,
1508
+ instance,
1509
+ dry_run: dryRun,
1510
+ });
1511
+ if (outputJson) {
1512
+ console.log(JSON.stringify(resp?.data || {}, null, 2));
1513
+ return;
1514
+ }
1515
+ console.log(resp?.data?.reply || "Group run requested");
1516
+ if (resp?.data?.group?.ok === false) process.exitCode = 1;
1517
+ return;
1518
+ }
1519
+
1520
+ if (sub === "status") {
1521
+ const groupId = rest[1] && !rest[1].startsWith("--") ? rest[1] : "";
1522
+ const outputJson = rest.includes("--json");
1523
+ const projectRoot = process.cwd();
1524
+ await ensureDaemonRunning(projectRoot);
1525
+ const resp = await sendDaemonRequest(projectRoot, {
1526
+ type: "group_status",
1527
+ group_id: groupId,
1528
+ });
1529
+ if (outputJson) {
1530
+ console.log(JSON.stringify(resp?.data || {}, null, 2));
1531
+ return;
1532
+ }
1533
+ console.log(resp?.data?.reply || "Group status requested");
1534
+ const group = resp?.data?.group || {};
1535
+ if (groupId && group?.group) {
1536
+ console.log(JSON.stringify(group.group, null, 2));
1537
+ } else if (!groupId && Array.isArray(group?.groups)) {
1538
+ group.groups.forEach((item) => {
1539
+ console.log(`- ${item.group_id} [${item.status}] ${item.template_alias} active=${item.members_active}/${item.members_total}`);
1540
+ });
1541
+ }
1542
+ return;
1543
+ }
1544
+
1545
+ if (sub === "stop") {
1546
+ const groupId = String(rest[1] || "").trim();
1547
+ if (!groupId) throw new Error("group stop requires <groupId>");
1548
+ const outputJson = rest.includes("--json");
1549
+ const projectRoot = process.cwd();
1550
+ await ensureDaemonRunning(projectRoot);
1551
+ const resp = await sendDaemonRequest(projectRoot, {
1552
+ type: "stop_group",
1553
+ group_id: groupId,
1554
+ });
1555
+ if (outputJson) {
1556
+ console.log(JSON.stringify(resp?.data || {}, null, 2));
1557
+ return;
1558
+ }
1559
+ console.log(resp?.data?.reply || "Group stop requested");
1560
+ if (resp?.data?.group?.ok === false) process.exitCode = 1;
1561
+ return;
1562
+ }
1563
+
1564
+ if (sub === "diagram") {
1565
+ const target = String(rest[1] || "").trim();
1566
+ if (!target) throw new Error("group diagram requires <alias|groupId>");
1567
+ const outputJson = rest.includes("--json");
1568
+ const format = rest.includes("--mermaid") ? "mermaid" : "ascii";
1569
+ const projectRoot = process.cwd();
1570
+ await ensureDaemonRunning(projectRoot);
1571
+ const resp = await sendDaemonRequest(projectRoot, {
1572
+ type: "group_diagram",
1573
+ alias: target,
1574
+ group_id: target,
1575
+ format,
1576
+ });
1577
+ if (outputJson) {
1578
+ console.log(JSON.stringify(resp?.data || {}, null, 2));
1579
+ return;
1580
+ }
1581
+ console.log(resp?.data?.reply || "Group diagram requested");
1582
+ if (resp?.data?.group?.diagram) {
1583
+ console.log(resp.data.group.diagram);
1584
+ }
1585
+ if (resp?.data?.group?.ok === false) process.exitCode = 1;
1586
+ return;
1587
+ }
1588
+
1589
+ throw new Error("group requires templates|template|run|status|diagram|stop subcommand");
1590
+ } catch (err) {
1591
+ console.error(err.message || String(err));
1592
+ process.exitCode = 1;
1593
+ }
1594
+ })();
1595
+ return;
1596
+ }
1274
1597
  if (cmd === "online") {
1275
1598
  const sub = rest[0] || "";
1276
1599
  if (!sub) {