mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-05 09:30:05 +02:00
* feat(cli): add central error translation layer (PR1) Introduce server/internal/cli/errors.go, a single user-facing error translation layer that collapses raw transport errors, HTTP status errors, and internal verb-wrapped chains into clear, localized messages. - ErrorKind classification (network timeout/DNS/refused/TLS/offline, 401/403/404/409/400+422/429/5xx, unknown) - NetworkError wraps transport errors and strips the raw URL from the user-facing message; classifyNetworkError categorizes via errors.As/Is with string fallbacks - HTTPError.Kind() maps status codes onto ErrorKind - FormatError: bilingual output (English default, auto-switch to Chinese on a zh LC_ALL/LC_MESSAGES/LANG locale), validation errors surface the server message; --debug / MULTICA_DEBUG appends the full raw chain - ExitCodeFor: tiered exit codes (network=2, auth=3, 404=4, validation=5, other=1) - client.go: default HTTP timeout 15s -> 30s, overridable via MULTICA_HTTP_TIMEOUT; wrap every transport Do() error as *NetworkError - main.go: route errors through FormatError + ExitCodeFor, add persistent --debug flag Unit tests cover every ErrorKind, classification, language detection, exit codes, server-message extraction, and timeout parsing. Refs MUL-3104. PR1 of 3; PR2/PR3 (status-code copy refinement and per-command customization) follow separately. Co-authored-by: multica-agent <github@multica.ai> * fix(cli): address review — unify command timeouts and classify all helper errors Must-fix 1: command-level contexts no longer truncate MULTICA_HTTP_TIMEOUT. Added cli.APITimeout/AtLeastAPITimeout/APIContext (budget = transport timeout + small grace, honoring MULTICA_HTTP_TIMEOUT) and replaced the hardcoded 15s context.WithTimeout in every API command (14 files, 92 sites) with cli.APIContext. The issue-create/comment path now uses APITimeout() with a 60s floor for attachment uploads. Must-fix 2: all API helpers now return *HTTPError on status >= 400. Added a shared newHTTPError(method, path, resp) and routed GetJSON, GetJSONWithHeaders, PostJSON, PutJSON, PatchJSON, DeleteJSON, DeleteJSONWithBody, UploadFile, UploadFileWithURL, DownloadFile (and HealthCheck) through it, so issue update/status/metadata (PUT), comment list (GetJSONWithHeaders), project/label/ comment delete (DELETE) and agent/workspace/autopilot update (PUT/PATCH) all get HTTPError.Kind() classification, friendly copy, and the tiered exit code instead of the raw string + exit 1. Tests: new errors_integration_test.go drives the real helpers against a fake server and asserts FormatError copy + ExitCodeFor for 401/403/404/422/500 across all 10 helpers, plus a slow-server test proving the command context does not cancel before the transport timeout. Updated the UploadFileWithURL assertion to check for *HTTPError. Refs MUL-3104, PR #3892. Co-authored-by: multica-agent <github@multica.ai> * fix(cli): make remaining fixed-timeout API commands honor MULTICA_HTTP_TIMEOUT Closes out the timeout work: the last API command paths still used a hardcoded context deadline that capped MULTICA_HTTP_TIMEOUT. Converted them to cli.AtLeastAPITimeout(<original floor>) so the env override scales them up while preserving each original lower bound: - cmd_autopilot.go autopilot trigger 30s -> AtLeastAPITimeout(30s) - cmd_attachment.go attachment download 60s -> AtLeastAPITimeout(60s) - cmd_agent.go avatar upload 60s -> AtLeastAPITimeout(60s) - cmd_skill.go skill import / search 60s -> AtLeastAPITimeout(60s) - cmd_runtime.go runtime update 150s -> AtLeastAPITimeout(150s) - cmd_login.go workspace-creation poll 10s -> AtLeastAPITimeout(10s) The login poll keeps a short 10s floor to stay responsive within its 5-minute loop, but it is NOT a silent exception: AtLeastAPITimeout means it still scales with MULTICA_HTTP_TIMEOUT. Documented in code and covered by a new subtest in TestAPITimeoutRespectsEnv. Refs MUL-3104, PR #3892. Co-authored-by: multica-agent <github@multica.ai> * style(cli): gofmt cmd_attachment.go to unblock backend CI Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai>
562 lines
17 KiB
Go
562 lines
17 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"text/tabwriter"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/multica-ai/multica/server/internal/cli"
|
|
)
|
|
|
|
var squadCmd = &cobra.Command{
|
|
Use: "squad",
|
|
Short: "Work with squads",
|
|
}
|
|
|
|
// ── List ────────────────────────────────────────────────────────────────────
|
|
|
|
var squadListCmd = &cobra.Command{
|
|
Use: "list",
|
|
Short: "List squads in the workspace",
|
|
Args: cobra.NoArgs,
|
|
RunE: runSquadList,
|
|
}
|
|
|
|
func runSquadList(cmd *cobra.Command, _ []string) error {
|
|
client, err := newAPIClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx, cancel := cli.APIContext(context.Background())
|
|
defer cancel()
|
|
|
|
var squads []map[string]any
|
|
if err := client.GetJSON(ctx, "/api/squads", &squads); err != nil {
|
|
return fmt.Errorf("list squads: %w", err)
|
|
}
|
|
|
|
output, _ := cmd.Flags().GetString("output")
|
|
if output == "json" {
|
|
return cli.PrintJSON(os.Stdout, squads)
|
|
}
|
|
|
|
if len(squads) == 0 {
|
|
fmt.Fprintln(os.Stderr, "No squads found.")
|
|
return nil
|
|
}
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
|
|
fmt.Fprintln(w, "ID\tNAME\tLEADER ID\tMEMBERS")
|
|
for _, s := range squads {
|
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n",
|
|
strVal(s, "id"), strVal(s, "name"), strVal(s, "leader_id"),
|
|
memberCountDisplay(s))
|
|
}
|
|
return w.Flush()
|
|
}
|
|
|
|
func memberCountDisplay(m map[string]any) string {
|
|
v, ok := m["member_count"]
|
|
if !ok || v == nil {
|
|
return "-"
|
|
}
|
|
n, ok := v.(float64)
|
|
if !ok || n <= 0 {
|
|
return "-"
|
|
}
|
|
return strconv.Itoa(int(n))
|
|
}
|
|
|
|
// ── Get ─────────────────────────────────────────────────────────────────────
|
|
|
|
var squadGetCmd = &cobra.Command{
|
|
Use: "get <squad-id>",
|
|
Short: "Get squad details",
|
|
Args: exactArgs(1),
|
|
RunE: runSquadGet,
|
|
}
|
|
|
|
func runSquadGet(cmd *cobra.Command, args []string) error {
|
|
client, err := newAPIClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx, cancel := cli.APIContext(context.Background())
|
|
defer cancel()
|
|
|
|
var squad map[string]any
|
|
if err := client.GetJSON(ctx, "/api/squads/"+args[0], &squad); err != nil {
|
|
return fmt.Errorf("get squad: %w", err)
|
|
}
|
|
|
|
output, _ := cmd.Flags().GetString("output")
|
|
if output == "json" {
|
|
return cli.PrintJSON(os.Stdout, squad)
|
|
}
|
|
|
|
fmt.Printf("ID: %s\n", strVal(squad, "id"))
|
|
fmt.Printf("Name: %s\n", strVal(squad, "name"))
|
|
fmt.Printf("Description: %s\n", strVal(squad, "description"))
|
|
fmt.Printf("Leader ID: %s\n", strVal(squad, "leader_id"))
|
|
fmt.Printf("Created: %s\n", strVal(squad, "created_at"))
|
|
if inst := strVal(squad, "instructions"); inst != "" {
|
|
fmt.Printf("Instructions: %s\n", inst)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ── Create ──────────────────────────────────────────────────────────────────
|
|
|
|
var squadCreateCmd = &cobra.Command{
|
|
Use: "create",
|
|
Short: "Create a new squad",
|
|
Args: cobra.NoArgs,
|
|
RunE: runSquadCreate,
|
|
}
|
|
|
|
func runSquadCreate(cmd *cobra.Command, _ []string) error {
|
|
name, _ := cmd.Flags().GetString("name")
|
|
if name == "" {
|
|
return fmt.Errorf("--name is required")
|
|
}
|
|
leader, _ := cmd.Flags().GetString("leader")
|
|
if leader == "" {
|
|
return fmt.Errorf("--leader is required (agent name or ID)")
|
|
}
|
|
|
|
client, err := newAPIClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx, cancel := cli.APIContext(context.Background())
|
|
defer cancel()
|
|
|
|
leaderID, err := resolveAgent(ctx, client, leader)
|
|
if err != nil {
|
|
return fmt.Errorf("resolve leader: %w", err)
|
|
}
|
|
|
|
body := map[string]any{
|
|
"name": name,
|
|
"leader_id": leaderID,
|
|
}
|
|
if v, _ := cmd.Flags().GetString("description"); v != "" {
|
|
body["description"] = v
|
|
}
|
|
|
|
var result map[string]any
|
|
if err := client.PostJSON(ctx, "/api/squads", body, &result); err != nil {
|
|
return fmt.Errorf("create squad: %w", err)
|
|
}
|
|
|
|
output, _ := cmd.Flags().GetString("output")
|
|
if output == "json" {
|
|
return cli.PrintJSON(os.Stdout, result)
|
|
}
|
|
fmt.Printf("Squad created: %s (%s)\n", strVal(result, "name"), strVal(result, "id"))
|
|
return nil
|
|
}
|
|
|
|
// ── Update ──────────────────────────────────────────────────────────────────
|
|
|
|
var squadUpdateCmd = &cobra.Command{
|
|
Use: "update <squad-id>",
|
|
Short: "Update a squad",
|
|
Args: exactArgs(1),
|
|
RunE: runSquadUpdate,
|
|
}
|
|
|
|
func runSquadUpdate(cmd *cobra.Command, args []string) error {
|
|
client, err := newAPIClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx, cancel := cli.APIContext(context.Background())
|
|
defer cancel()
|
|
|
|
body := map[string]any{}
|
|
if cmd.Flags().Changed("name") {
|
|
v, _ := cmd.Flags().GetString("name")
|
|
body["name"] = v
|
|
}
|
|
if cmd.Flags().Changed("description") {
|
|
v, _ := cmd.Flags().GetString("description")
|
|
body["description"] = v
|
|
}
|
|
if cmd.Flags().Changed("instructions") {
|
|
v, _ := cmd.Flags().GetString("instructions")
|
|
body["instructions"] = v
|
|
}
|
|
if cmd.Flags().Changed("leader") {
|
|
v, _ := cmd.Flags().GetString("leader")
|
|
leaderID, err := resolveAgent(ctx, client, v)
|
|
if err != nil {
|
|
return fmt.Errorf("resolve leader: %w", err)
|
|
}
|
|
body["leader_id"] = leaderID
|
|
}
|
|
if cmd.Flags().Changed("avatar-url") {
|
|
v, _ := cmd.Flags().GetString("avatar-url")
|
|
body["avatar_url"] = v
|
|
}
|
|
|
|
if len(body) == 0 {
|
|
return fmt.Errorf("no fields to update; use flags like --name, --description, --instructions, --leader")
|
|
}
|
|
|
|
var result map[string]any
|
|
if err := client.PutJSON(ctx, "/api/squads/"+args[0], body, &result); err != nil {
|
|
return fmt.Errorf("update squad: %w", err)
|
|
}
|
|
|
|
output, _ := cmd.Flags().GetString("output")
|
|
if output == "json" {
|
|
return cli.PrintJSON(os.Stdout, result)
|
|
}
|
|
fmt.Printf("Squad updated: %s (%s)\n", strVal(result, "name"), strVal(result, "id"))
|
|
return nil
|
|
}
|
|
|
|
// ── Delete ──────────────────────────────────────────────────────────────────
|
|
|
|
var squadDeleteCmd = &cobra.Command{
|
|
Use: "delete <squad-id>",
|
|
Short: "Delete (archive) a squad",
|
|
Args: exactArgs(1),
|
|
RunE: runSquadDelete,
|
|
}
|
|
|
|
func runSquadDelete(cmd *cobra.Command, args []string) error {
|
|
client, err := newAPIClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx, cancel := cli.APIContext(context.Background())
|
|
defer cancel()
|
|
|
|
if err := client.DeleteJSON(ctx, "/api/squads/"+args[0]); err != nil {
|
|
return fmt.Errorf("delete squad: %w", err)
|
|
}
|
|
|
|
output, _ := cmd.Flags().GetString("output")
|
|
if output == "json" {
|
|
return cli.PrintJSON(os.Stdout, map[string]any{"id": args[0], "deleted": true})
|
|
}
|
|
fmt.Fprintf(os.Stderr, "Squad %s deleted.\n", args[0])
|
|
return nil
|
|
}
|
|
|
|
// ── Members ─────────────────────────────────────────────────────────────────
|
|
|
|
var squadMemberCmd = &cobra.Command{
|
|
Use: "member",
|
|
Short: "Work with squad members",
|
|
}
|
|
|
|
var squadMemberListCmd = &cobra.Command{
|
|
Use: "list <squad-id>",
|
|
Short: "List members of a squad",
|
|
Args: exactArgs(1),
|
|
RunE: runSquadMemberList,
|
|
}
|
|
|
|
func runSquadMemberList(cmd *cobra.Command, args []string) error {
|
|
client, err := newAPIClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx, cancel := cli.APIContext(context.Background())
|
|
defer cancel()
|
|
|
|
var members []map[string]any
|
|
if err := client.GetJSON(ctx, "/api/squads/"+args[0]+"/members", &members); err != nil {
|
|
return fmt.Errorf("list members: %w", err)
|
|
}
|
|
|
|
output, _ := cmd.Flags().GetString("output")
|
|
if output == "json" {
|
|
return cli.PrintJSON(os.Stdout, members)
|
|
}
|
|
|
|
if len(members) == 0 {
|
|
fmt.Fprintln(os.Stderr, "No members found.")
|
|
return nil
|
|
}
|
|
|
|
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
|
|
fmt.Fprintln(w, "MEMBER ID\tTYPE\tROLE")
|
|
for _, m := range members {
|
|
fmt.Fprintf(w, "%s\t%s\t%s\n",
|
|
strVal(m, "member_id"), strVal(m, "member_type"), strVal(m, "role"))
|
|
}
|
|
return w.Flush()
|
|
}
|
|
|
|
// ── Member Add ──────────────────────────────────────────────────────────────
|
|
|
|
var squadMemberAddCmd = &cobra.Command{
|
|
Use: "add <squad-id>",
|
|
Short: "Add a member to a squad",
|
|
Args: exactArgs(1),
|
|
RunE: runSquadMemberAdd,
|
|
}
|
|
|
|
func runSquadMemberAdd(cmd *cobra.Command, args []string) error {
|
|
memberID, _ := cmd.Flags().GetString("member-id")
|
|
memberType, _ := cmd.Flags().GetString("type")
|
|
role, _ := cmd.Flags().GetString("role")
|
|
|
|
if memberID == "" {
|
|
return fmt.Errorf("--member-id is required")
|
|
}
|
|
if memberType != "agent" && memberType != "member" {
|
|
return fmt.Errorf("--type must be 'agent' or 'member'")
|
|
}
|
|
|
|
client, err := newAPIClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx, cancel := cli.APIContext(context.Background())
|
|
defer cancel()
|
|
|
|
body := map[string]any{
|
|
"member_type": memberType,
|
|
"member_id": memberID,
|
|
"role": role,
|
|
}
|
|
|
|
var result map[string]any
|
|
if err := client.PostJSON(ctx, "/api/squads/"+args[0]+"/members", body, &result); err != nil {
|
|
return fmt.Errorf("add member: %w", err)
|
|
}
|
|
|
|
output, _ := cmd.Flags().GetString("output")
|
|
if output == "json" {
|
|
return cli.PrintJSON(os.Stdout, result)
|
|
}
|
|
fmt.Printf("Member %s added to squad.\n", memberID)
|
|
return nil
|
|
}
|
|
|
|
// ── Member Set Role ─────────────────────────────────────────────────────────
|
|
|
|
var squadMemberSetRoleCmd = &cobra.Command{
|
|
Use: "set-role <squad-id>",
|
|
Short: "Change a squad member's role",
|
|
Args: exactArgs(1),
|
|
RunE: runSquadMemberSetRole,
|
|
}
|
|
|
|
func runSquadMemberSetRole(cmd *cobra.Command, args []string) error {
|
|
memberID, _ := cmd.Flags().GetString("member-id")
|
|
memberType, _ := cmd.Flags().GetString("member-type")
|
|
role, _ := cmd.Flags().GetString("role")
|
|
|
|
if memberID == "" {
|
|
return fmt.Errorf("--member-id is required")
|
|
}
|
|
if memberType != "agent" && memberType != "member" {
|
|
return fmt.Errorf("--member-type must be 'agent' or 'member'")
|
|
}
|
|
if role == "" {
|
|
return fmt.Errorf("--role is required")
|
|
}
|
|
|
|
client, err := newAPIClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx, cancel := cli.APIContext(context.Background())
|
|
defer cancel()
|
|
|
|
body := map[string]any{
|
|
"member_type": memberType,
|
|
"member_id": memberID,
|
|
"role": role,
|
|
}
|
|
|
|
var result map[string]any
|
|
if err := client.PatchJSON(ctx, "/api/squads/"+args[0]+"/members/role", body, &result); err != nil {
|
|
return fmt.Errorf("set member role: %w", err)
|
|
}
|
|
|
|
output, _ := cmd.Flags().GetString("output")
|
|
if output == "json" {
|
|
return cli.PrintJSON(os.Stdout, result)
|
|
}
|
|
fmt.Fprintf(os.Stderr, "Member %s role updated to %s.\n", memberID, role)
|
|
return nil
|
|
}
|
|
|
|
// ── Member Remove ───────────────────────────────────────────────────────────
|
|
|
|
var squadMemberRemoveCmd = &cobra.Command{
|
|
Use: "remove <squad-id>",
|
|
Short: "Remove a member from a squad",
|
|
Args: exactArgs(1),
|
|
RunE: runSquadMemberRemove,
|
|
}
|
|
|
|
func runSquadMemberRemove(cmd *cobra.Command, args []string) error {
|
|
memberID, _ := cmd.Flags().GetString("member-id")
|
|
memberType, _ := cmd.Flags().GetString("type")
|
|
|
|
if memberID == "" {
|
|
return fmt.Errorf("--member-id is required")
|
|
}
|
|
if memberType != "agent" && memberType != "member" {
|
|
return fmt.Errorf("--type must be 'agent' or 'member'")
|
|
}
|
|
|
|
client, err := newAPIClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx, cancel := cli.APIContext(context.Background())
|
|
defer cancel()
|
|
|
|
body := map[string]any{
|
|
"member_type": memberType,
|
|
"member_id": memberID,
|
|
}
|
|
|
|
if err := client.DeleteJSONWithBody(ctx, "/api/squads/"+args[0]+"/members", body); err != nil {
|
|
return fmt.Errorf("remove member: %w", err)
|
|
}
|
|
|
|
output, _ := cmd.Flags().GetString("output")
|
|
if output == "json" {
|
|
return cli.PrintJSON(os.Stdout, map[string]any{"squad_id": args[0], "member_id": memberID, "removed": true})
|
|
}
|
|
fmt.Fprintf(os.Stderr, "Member %s removed from squad.\n", memberID)
|
|
return nil
|
|
}
|
|
|
|
// ── Activity ────────────────────────────────────────────────────────────────
|
|
|
|
var squadActivityCmd = &cobra.Command{
|
|
Use: "activity <issue-id> <outcome>",
|
|
Short: "Record a squad leader evaluation on an issue",
|
|
Long: `Record the squad leader's evaluation decision for an issue.
|
|
|
|
Outcome must be one of:
|
|
action — leader delegated or took action
|
|
no_action — leader evaluated and decided no action needed
|
|
failed — leader encountered an error
|
|
|
|
This command is intended to be called by squad leader agents after each
|
|
trigger to record their decision in the issue timeline.`,
|
|
Args: exactArgs(2),
|
|
RunE: runSquadActivity,
|
|
}
|
|
|
|
func runSquadActivity(cmd *cobra.Command, args []string) error {
|
|
issueID := args[0]
|
|
outcome := args[1]
|
|
|
|
if outcome != "action" && outcome != "no_action" && outcome != "failed" {
|
|
return fmt.Errorf("invalid outcome %q; valid values: action, no_action, failed", outcome)
|
|
}
|
|
|
|
reason, _ := cmd.Flags().GetString("reason")
|
|
|
|
client, err := newAPIClient(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx, cancel := cli.APIContext(context.Background())
|
|
defer cancel()
|
|
|
|
issueRef, err := resolveIssueRef(ctx, client, issueID)
|
|
if err != nil {
|
|
return fmt.Errorf("resolve issue: %w", err)
|
|
}
|
|
|
|
body := map[string]any{
|
|
"outcome": outcome,
|
|
"reason": reason,
|
|
}
|
|
var result map[string]any
|
|
if err := client.PostJSON(ctx, "/api/issues/"+issueRef.ID+"/squad-evaluated", body, &result); err != nil {
|
|
return fmt.Errorf("record evaluation: %w", err)
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, "Squad evaluation recorded: %s (issue %s)\n", outcome, issueRef.Display)
|
|
|
|
output, _ := cmd.Flags().GetString("output")
|
|
if output == "json" {
|
|
return cli.PrintJSON(os.Stdout, result)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ── Init ────────────────────────────────────────────────────────────────────
|
|
|
|
func init() {
|
|
// list
|
|
squadListCmd.Flags().String("output", "table", "Output format: table or json")
|
|
|
|
// get
|
|
squadGetCmd.Flags().String("output", "table", "Output format: table or json")
|
|
|
|
// create
|
|
squadCreateCmd.Flags().String("name", "", "Squad name (required)")
|
|
squadCreateCmd.Flags().String("description", "", "Squad description")
|
|
squadCreateCmd.Flags().String("leader", "", "Leader agent (name or ID) — required")
|
|
squadCreateCmd.Flags().String("output", "json", "Output format: table or json")
|
|
|
|
// update
|
|
squadUpdateCmd.Flags().String("name", "", "New name")
|
|
squadUpdateCmd.Flags().String("description", "", "New description")
|
|
squadUpdateCmd.Flags().String("instructions", "", "New instructions")
|
|
squadUpdateCmd.Flags().String("leader", "", "New leader agent (name or ID)")
|
|
squadUpdateCmd.Flags().String("avatar-url", "", "New avatar URL")
|
|
squadUpdateCmd.Flags().String("output", "json", "Output format: table or json")
|
|
|
|
// delete
|
|
squadDeleteCmd.Flags().String("output", "table", "Output format: table or json")
|
|
|
|
// member list
|
|
squadMemberListCmd.Flags().String("output", "table", "Output format: table or json")
|
|
|
|
// member add
|
|
squadMemberAddCmd.Flags().String("member-id", "", "Member or agent ID (required)")
|
|
squadMemberAddCmd.Flags().String("type", "agent", "Member type: agent or member")
|
|
squadMemberAddCmd.Flags().String("role", "member", "Role in the squad")
|
|
squadMemberAddCmd.Flags().String("output", "json", "Output format: table or json")
|
|
|
|
// member remove
|
|
squadMemberRemoveCmd.Flags().String("member-id", "", "Member or agent ID (required)")
|
|
squadMemberRemoveCmd.Flags().String("type", "agent", "Member type: agent or member")
|
|
squadMemberRemoveCmd.Flags().String("output", "table", "Output format: table or json")
|
|
|
|
// member set-role
|
|
squadMemberSetRoleCmd.Flags().String("member-id", "", "Member or agent ID (required)")
|
|
squadMemberSetRoleCmd.Flags().String("member-type", "agent", "Member type: agent or member")
|
|
squadMemberSetRoleCmd.Flags().String("role", "", "New role in the squad (required)")
|
|
squadMemberSetRoleCmd.Flags().String("output", "json", "Output format: table or json")
|
|
|
|
// activity
|
|
squadActivityCmd.Flags().String("reason", "", "Short explanation of the decision")
|
|
squadActivityCmd.Flags().String("output", "table", "Output format: table or json")
|
|
|
|
squadMemberCmd.AddCommand(squadMemberListCmd)
|
|
squadMemberCmd.AddCommand(squadMemberAddCmd)
|
|
squadMemberCmd.AddCommand(squadMemberRemoveCmd)
|
|
squadMemberCmd.AddCommand(squadMemberSetRoleCmd)
|
|
|
|
squadCmd.AddCommand(squadListCmd)
|
|
squadCmd.AddCommand(squadGetCmd)
|
|
squadCmd.AddCommand(squadCreateCmd)
|
|
squadCmd.AddCommand(squadUpdateCmd)
|
|
squadCmd.AddCommand(squadDeleteCmd)
|
|
squadCmd.AddCommand(squadMemberCmd)
|
|
squadCmd.AddCommand(squadActivityCmd)
|
|
}
|