From 983b5c668c1c18ab1d1634423c73daaad85a615d Mon Sep 17 00:00:00 2001 From: operator Date: Mon, 5 Oct 2026 16:04:56 +0000 Subject: [PATCH] feat(cli): add 'box swarm' domain (list, status, spawn, prune) --- bin/super-cli.py | 125 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/bin/super-cli.py b/bin/super-cli.py index faac13c..1983044 100755 --- a/bin/super-cli.py +++ b/bin/super-cli.py @@ -3109,6 +3109,101 @@ def cmd_ssh_show(args): except Exception: sys.stdout.write(res.stdout) + +def cmd_swarm_list(args): + cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "swarm-list"] + res = subprocess.run(cmd, capture_output=True, text=True) + if args.json: + sys.stdout.write(res.stdout) + return + try: + d = json.loads(res.stdout) + if d.get("ok"): + swarms = d.get("swarms", []) + print("\n" + c_bold(f"=== SWARMS ({len(swarms)}) ===") + "\n") + headers = ["ID", "STATUS", "LABEL", "TOTAL", "DONE", "RUNNING", "FAILED", "CREATED"] + rows = [] + for sw in swarms: + st = sw.get("status") + st_color = c_green(st) if st == "completed" else (c_yellow(st) if st in ("running", "partial") else c_dim(st)) + rows.append([ + c_cyan(sw.get("swarm_id", "")[:18]), + st_color, + (sw.get("label") or "-")[:20], + str(sw.get("count", 0)), + str(sw.get("done", 0)), + str(sw.get("running", 0)), + str(sw.get("failed", 0)), + (sw.get("created_ts") or "")[:16] + ]) + print_table(headers, rows) + print("\n" + c_dim(" Spawn swarm: box swarm spawn [--label L]") + "\n") + else: + print(c_red(f"✗ Failed: {d.get('error')}")) + except Exception: + sys.stdout.write(res.stdout) + + +def cmd_swarm_status(args): + cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "swarm-status", args.swarm_id] + res = subprocess.run(cmd, capture_output=True, text=True) + if args.json: + sys.stdout.write(res.stdout) + return + try: + d = json.loads(res.stdout) + if d.get("ok"): + print(c_bold(f"\n=== SWARM STATUS: {args.swarm_id} ===")) + print(f" Status: {d.get('status')}") + print(f" Counts: {d.get('counts')}") + slots = d.get("slots", []) + print(f"\n Slots ({len(slots)}):") + for s in slots: + res_txt = s.get("result") + res_prev = (str(res_txt)[:50] + "...") if res_txt and len(str(res_txt)) > 50 else str(res_txt) + print(f" Slot {s.get('slot')}: [{s.get('status')}] agent={s.get('agent_id')} subagent={s.get('subagent_session_id')} res={res_prev}") + print() + else: + print(c_red(f"✗ Failed: {d.get('error')}")) + except Exception: + sys.stdout.write(res.stdout) + + +def cmd_swarm_spawn(args): + cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "swarm-spawn", str(args.count), args.task] + if args.label: + cmd.extend(["--label", args.label]) + res = subprocess.run(cmd, capture_output=True, text=True) + if args.json: + sys.stdout.write(res.stdout) + return + try: + d = json.loads(res.stdout) + if d.get("ok"): + print(c_green(f"✓ Spawned swarm: {d.get('swarm_id')} ({d.get('count')} slots)")) + else: + print(c_red(f"✗ Failed: {d.get('error')} ({d.get('code')})")) + except Exception: + sys.stdout.write(res.stdout) + + +def cmd_swarm_prune(args): + cmd = ["python3", str(BIN_DIR / "box-ctl.py"), "swarm-prune", "--stale-hours", str(args.stale_hours)] + if args.confirm: + cmd.append("--confirm") + res = subprocess.run(cmd, capture_output=True, text=True) + if args.json: + sys.stdout.write(res.stdout) + return + try: + d = json.loads(res.stdout) + if d.get("ok"): + print(c_green(f"✓ Pruned {d.get('archived_count')} swarms older than {d.get('stale_hours')}h to 'archived'")) + else: + print(c_yellow(f"! {d.get('error')} (use --confirm)")) + except Exception: + sys.stdout.write(res.stdout) + def build_parser(): common = argparse.ArgumentParser(add_help=False) common.add_argument("--json", action="store_true", help="Output machine-readable JSON") @@ -3499,6 +3594,24 @@ def build_parser(): p_dep_sub.add_argument("prompt", help="Task prompt for the subagent") p_dep_sub.add_argument("--wait", type=int, default=30, help="Seconds to wait for subagent response") + + # Domain: SWARM + p_swarm = subparsers.add_parser("swarm", parents=[common], help="Manage multi-agent swarm tasks and worker allocations") + swarm_sub = p_swarm.add_subparsers(dest="action") + + p_sw_list = swarm_sub.add_parser("list", parents=[common], help="List all swarms and slot rollups") + p_sw_status = swarm_sub.add_parser("status", parents=[common], help="Show status and slot details for a swarm") + p_sw_status.add_argument("swarm_id", help="Swarm identifier (sw-...)") + + p_sw_spawn = swarm_sub.add_parser("spawn", parents=[common], help="Spawn a new multi-slot worker swarm") + p_sw_spawn.add_argument("count", type=int, help="Number of worker slots (1-50)") + p_sw_spawn.add_argument("task", help="Task prompt for the swarm workers") + p_sw_spawn.add_argument("--label", default=None, help="Optional tracking label") + + p_sw_prune = swarm_sub.add_parser("prune", parents=[common], help="Archive stale partial/terminal swarms") + p_sw_prune.add_argument("--stale-hours", type=float, default=6.0, help="Hours of inactivity before pruning (default: 6)") + p_sw_prune.add_argument("--confirm", action="store_true", help="Confirm execution of pruning") + # Domain: subagent (Direct alias for spawning & managing subagents) p_subagent = subparsers.add_parser("subagent", parents=[common], help="Spawn sub-agent session and dispatch task") subagent_sub = p_subagent.add_subparsers(dest="action") @@ -3691,6 +3804,18 @@ def main(): cmd_loop_strat(args) elif args.domain == "vars": cmd_loop_vars(args) + elif args.domain == "swarm": + act = getattr(args, "action", None) + if not act or act == "list": + cmd_swarm_list(args) + elif act == "status": + cmd_swarm_status(args) + elif act == "spawn": + cmd_swarm_spawn(args) + elif act == "prune": + cmd_swarm_prune(args) + else: + p_swarm.print_help() elif args.domain in ("deploy", "subagent"): if args.domain == "subagent": args.action = "subagent"