1066 lines
27 KiB
Go
1066 lines
27 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
"github.com/charmbracelet/log"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
// Version info - set by ldflags during build
|
|
version = "dev"
|
|
buildTime = "unknown"
|
|
gitCommit = "unknown"
|
|
|
|
// CLI flags
|
|
projectName string
|
|
phase string
|
|
teamID int
|
|
roleName string
|
|
person string
|
|
output string
|
|
|
|
// Styles
|
|
titleStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#7C3AED"))
|
|
textStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#E5E7EB"))
|
|
successStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#10B981"))
|
|
errorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#EF4444"))
|
|
warnStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#F59E0B"))
|
|
infoStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#3B82F6"))
|
|
)
|
|
|
|
func main() {
|
|
rootCmd := &cobra.Command{
|
|
Use: "team",
|
|
Short: "Team Manager CLI - Manage standardized team layouts",
|
|
Long: `Team Manager CLI
|
|
|
|
A command-line tool for managing standardized team structures across projects.
|
|
Integrates with the team_manager.py backend to provide team initialization,
|
|
role assignments, and status tracking.`,
|
|
Version: fmt.Sprintf("%s (built: %s, commit: %s)", version, buildTime, gitCommit),
|
|
}
|
|
|
|
// Global flags
|
|
rootCmd.PersistentFlags().StringVarP(&projectName, "project", "p", "", "Project name (required for most commands)")
|
|
rootCmd.PersistentFlags().StringVarP(&output, "output", "o", "text", "Output format: text, json")
|
|
|
|
// Add subcommands
|
|
rootCmd.AddCommand(initCmd())
|
|
rootCmd.AddCommand(listCmd())
|
|
rootCmd.AddCommand(assignCmd())
|
|
rootCmd.AddCommand(unassignCmd())
|
|
rootCmd.AddCommand(startCmd())
|
|
rootCmd.AddCommand(completeCmd())
|
|
rootCmd.AddCommand(statusCmd())
|
|
rootCmd.AddCommand(validateCmd())
|
|
rootCmd.AddCommand(phaseGateCmd())
|
|
rootCmd.AddCommand(queryCmd())
|
|
rootCmd.AddCommand(reassignCmd())
|
|
rootCmd.AddCommand(exportCmd())
|
|
rootCmd.AddCommand(importCmd())
|
|
rootCmd.AddCommand(backupCmd())
|
|
rootCmd.AddCommand(restoreCmd())
|
|
rootCmd.AddCommand(deleteCmd())
|
|
rootCmd.AddCommand(auditCmd())
|
|
rootCmd.AddCommand(historyCmd())
|
|
rootCmd.AddCommand(templateCmd())
|
|
rootCmd.AddCommand(healthCmd())
|
|
|
|
if err := rootCmd.Execute(); err != nil {
|
|
log.Error(err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// getTeamManagerPath returns the path to the team_manager.py script
|
|
func getTeamManagerPath() string {
|
|
// Check if TEAM_MANAGER_PATH env var is set
|
|
if path := os.Getenv("TEAM_MANAGER_PATH"); path != "" {
|
|
return path
|
|
}
|
|
|
|
// Try to find relative to executable
|
|
exe, err := os.Executable()
|
|
if err == nil {
|
|
dir := filepath.Dir(exe)
|
|
// Try multiple relative paths
|
|
candidates := []string{
|
|
filepath.Join(dir, "..", "..", "..", "scripts", "team_manager.py"),
|
|
filepath.Join(dir, "..", "..", "scripts", "team_manager.py"),
|
|
filepath.Join(dir, "..", "scripts", "team_manager.py"),
|
|
filepath.Join(dir, "scripts", "team_manager.py"),
|
|
}
|
|
for _, candidate := range candidates {
|
|
if _, err := os.Stat(candidate); err == nil {
|
|
return candidate
|
|
}
|
|
}
|
|
}
|
|
|
|
// Default path
|
|
return "scripts/team_manager.py"
|
|
}
|
|
|
|
// runTeamManager executes the team_manager.py script with the given arguments
|
|
// The Python script expects: --project PROJECT COMMAND [args...]
|
|
func runTeamManager(project string, command string, args ...string) ([]byte, error) {
|
|
scriptPath := getTeamManagerPath()
|
|
|
|
// Check if Python is available
|
|
pythonCmd := "python3"
|
|
if _, err := exec.LookPath("python3"); err != nil {
|
|
pythonCmd = "python"
|
|
if _, err := exec.LookPath("python"); err != nil {
|
|
return nil, fmt.Errorf("Python not found. Please install Python 3")
|
|
}
|
|
}
|
|
|
|
// Build command: script --project PROJECT command [args...]
|
|
cmdArgs := []string{scriptPath}
|
|
// Always include --project since Python script requires it
|
|
if project == "" {
|
|
project = "_cli_health_check_"
|
|
}
|
|
cmdArgs = append(cmdArgs, "--project", project)
|
|
cmdArgs = append(cmdArgs, command)
|
|
cmdArgs = append(cmdArgs, args...)
|
|
|
|
cmd := exec.Command(pythonCmd, cmdArgs...)
|
|
cmd.Stderr = os.Stderr
|
|
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
return nil, fmt.Errorf("team_manager.py failed: %s", string(exitErr.Stderr))
|
|
}
|
|
return nil, fmt.Errorf("failed to run team_manager.py: %w", err)
|
|
}
|
|
|
|
return output, nil
|
|
}
|
|
|
|
// initCmd creates the init command
|
|
func initCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "init [project-name]",
|
|
Short: "Initialize a new project with team structure",
|
|
Long: `Initialize a new project with the standardized 12-team structure.`,
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
project := args[0]
|
|
|
|
if output == "json" {
|
|
result, err := runTeamManager(project, "init", "--format", "json")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(result))
|
|
return nil
|
|
}
|
|
|
|
fmt.Println(titleStyle.Render("Initializing Team Structure"))
|
|
fmt.Printf("Project: %s\n\n", textStyle.Render(project))
|
|
|
|
result, err := runTeamManager(project, "init")
|
|
if err != nil {
|
|
return fmt.Errorf("%s %v", errorStyle.Render("Failed to initialize project:"), err)
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
fmt.Println()
|
|
fmt.Println(successStyle.Render("✓ Project initialized successfully"))
|
|
fmt.Println(infoStyle.Render(fmt.Sprintf("Config: .teams/%s.json", project)))
|
|
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
// listCmd creates the list command
|
|
func listCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "list",
|
|
Short: "List all teams for a project",
|
|
Long: `List all teams and their role assignments for a project.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
|
|
listExtraArgs := []string{}
|
|
if phase != "" {
|
|
listExtraArgs = append(listExtraArgs, "--phase", phase)
|
|
}
|
|
|
|
if output == "json" {
|
|
listExtraArgs = append(listExtraArgs, "--format", "json")
|
|
result, err := runTeamManager(projectName, "list", listExtraArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(result))
|
|
return nil
|
|
}
|
|
|
|
fmt.Println(titleStyle.Render("Team List"))
|
|
fmt.Printf("Project: %s\n\n", textStyle.Render(projectName))
|
|
|
|
result, err := runTeamManager(projectName, "list", listExtraArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVar(&phase, "phase", "", "Filter by phase (e.g., 'Phase 1')")
|
|
return cmd
|
|
}
|
|
|
|
// assignCmd creates the assign command
|
|
func assignCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "assign",
|
|
Short: "Assign a person to a role",
|
|
Long: `Assign a person to a specific role within a team.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
if teamID == 0 {
|
|
return fmt.Errorf("--team flag is required")
|
|
}
|
|
if roleName == "" {
|
|
return fmt.Errorf("--role flag is required")
|
|
}
|
|
if person == "" {
|
|
return fmt.Errorf("--person flag is required")
|
|
}
|
|
|
|
assignArgs := []string{
|
|
"--team", fmt.Sprintf("%d", teamID),
|
|
"--role", roleName,
|
|
"--person", person,
|
|
}
|
|
|
|
if output == "json" {
|
|
assignArgs = append(assignArgs, "--format", "json")
|
|
result, err := runTeamManager(projectName, "assign", assignArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(result))
|
|
return nil
|
|
}
|
|
|
|
fmt.Println(titleStyle.Render("Assigning Role"))
|
|
fmt.Printf("Project: %s\n", textStyle.Render(projectName))
|
|
fmt.Printf("Team: %s\n", textStyle.Render(fmt.Sprintf("Team %d", teamID)))
|
|
fmt.Printf("Role: %s\n", textStyle.Render(roleName))
|
|
fmt.Printf("Person: %s\n\n", textStyle.Render(person))
|
|
|
|
result, err := runTeamManager(projectName, "assign", assignArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().IntVarP(&teamID, "team", "t", 0, "Team ID (1-12)")
|
|
cmd.Flags().StringVarP(&roleName, "role", "r", "", "Role name to assign")
|
|
cmd.Flags().StringVar(&person, "person", "", "Person to assign")
|
|
|
|
cmd.MarkFlagRequired("team")
|
|
cmd.MarkFlagRequired("role")
|
|
cmd.MarkFlagRequired("person")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// unassignCmd creates the unassign command
|
|
func unassignCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "unassign",
|
|
Short: "Remove a person from a role",
|
|
Long: `Remove a person from a specific role within a team.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
if teamID == 0 {
|
|
return fmt.Errorf("--team flag is required")
|
|
}
|
|
if roleName == "" {
|
|
return fmt.Errorf("--role flag is required")
|
|
}
|
|
|
|
unassignArgs := []string{
|
|
"--team", fmt.Sprintf("%d", teamID),
|
|
"--role", roleName,
|
|
}
|
|
|
|
if output == "json" {
|
|
unassignArgs = append(unassignArgs, "--format", "json")
|
|
result, err := runTeamManager(projectName, "unassign", unassignArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(result))
|
|
return nil
|
|
}
|
|
|
|
fmt.Println(titleStyle.Render("Unassigning Role"))
|
|
fmt.Printf("Project: %s\n", textStyle.Render(projectName))
|
|
fmt.Printf("Team: %s\n", textStyle.Render(fmt.Sprintf("Team %d", teamID)))
|
|
fmt.Printf("Role: %s\n\n", textStyle.Render(roleName))
|
|
|
|
result, err := runTeamManager(projectName, "unassign", unassignArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().IntVarP(&teamID, "team", "t", 0, "Team ID (1-12)")
|
|
cmd.Flags().StringVarP(&roleName, "role", "r", "", "Role name to unassign")
|
|
|
|
cmd.MarkFlagRequired("team")
|
|
cmd.MarkFlagRequired("role")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// startCmd creates the start command
|
|
func startCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "start",
|
|
Short: "Start a team",
|
|
Long: `Mark a team as started/in-progress.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
if teamID == 0 {
|
|
return fmt.Errorf("--team flag is required")
|
|
}
|
|
|
|
startArgs := []string{"--team", fmt.Sprintf("%d", teamID)}
|
|
|
|
if output == "json" {
|
|
startArgs = append(startArgs, "--format", "json")
|
|
result, err := runTeamManager(projectName, "start", startArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(result))
|
|
return nil
|
|
}
|
|
|
|
fmt.Println(titleStyle.Render("Starting Team"))
|
|
fmt.Printf("Project: %s\n", textStyle.Render(projectName))
|
|
fmt.Printf("Team: %s\n\n", textStyle.Render(fmt.Sprintf("Team %d", teamID)))
|
|
|
|
result, err := runTeamManager(projectName, "start", startArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().IntVarP(&teamID, "team", "t", 0, "Team ID (1-12)")
|
|
cmd.MarkFlagRequired("team")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// completeCmd creates the complete command
|
|
func completeCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "complete",
|
|
Short: "Complete a team",
|
|
Long: `Mark a team as completed.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
if teamID == 0 {
|
|
return fmt.Errorf("--team flag is required")
|
|
}
|
|
|
|
completeArgs := []string{"--team", fmt.Sprintf("%d", teamID)}
|
|
|
|
if output == "json" {
|
|
completeArgs = append(completeArgs, "--format", "json")
|
|
result, err := runTeamManager(projectName, "complete", completeArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(result))
|
|
return nil
|
|
}
|
|
|
|
fmt.Println(titleStyle.Render("Completing Team"))
|
|
fmt.Printf("Project: %s\n", textStyle.Render(projectName))
|
|
fmt.Printf("Team: %s\n\n", textStyle.Render(fmt.Sprintf("Team %d", teamID)))
|
|
|
|
result, err := runTeamManager(projectName, "complete", completeArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().IntVarP(&teamID, "team", "t", 0, "Team ID (1-12)")
|
|
cmd.MarkFlagRequired("team")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// statusCmd creates the status command
|
|
func statusCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "status",
|
|
Short: "Show project or phase status",
|
|
Long: `Display the current status of teams in a project or phase.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
|
|
statusArgs := []string{}
|
|
if phase != "" {
|
|
statusArgs = append(statusArgs, "--phase", phase)
|
|
}
|
|
|
|
if output == "json" {
|
|
statusArgs = append(statusArgs, "--format", "json")
|
|
result, err := runTeamManager(projectName, "status", statusArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(result))
|
|
return nil
|
|
}
|
|
|
|
fmt.Println(titleStyle.Render("Project Status"))
|
|
fmt.Printf("Project: %s\n\n", textStyle.Render(projectName))
|
|
|
|
result, err := runTeamManager(projectName, "status", statusArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVar(&phase, "phase", "", "Show status for specific phase")
|
|
return cmd
|
|
}
|
|
|
|
// validateCmd creates the validate command
|
|
func validateCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "validate",
|
|
Short: "Validate team sizes meet requirements",
|
|
Long: `Validate that all teams have 4-6 members as required by TEAM-007.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
|
|
if output == "json" {
|
|
result, err := runTeamManager(projectName, "validate-size", "--format", "json")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(result))
|
|
return nil
|
|
}
|
|
|
|
fmt.Println(titleStyle.Render("Team Size Validation"))
|
|
fmt.Printf("Project: %s\n\n", textStyle.Render(projectName))
|
|
|
|
result, err := runTeamManager(projectName, "validate-size")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
// phaseGateCmd creates the phase-gate command
|
|
func phaseGateCmd() *cobra.Command {
|
|
var fromPhase, toPhase int
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "phase-gate",
|
|
Short: "Check phase gate requirements",
|
|
Long: `Check if requirements are met for transitioning between phases.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
if fromPhase == 0 || toPhase == 0 {
|
|
return fmt.Errorf("--from and --to flags are required")
|
|
}
|
|
|
|
phaseGateArgs := []string{
|
|
"--from", fmt.Sprintf("%d", fromPhase),
|
|
"--to", fmt.Sprintf("%d", toPhase),
|
|
}
|
|
|
|
if output == "json" {
|
|
phaseGateArgs = append(phaseGateArgs, "--format", "json")
|
|
result, err := runTeamManager(projectName, "phase-gate-check", phaseGateArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(result))
|
|
return nil
|
|
}
|
|
|
|
fmt.Println(titleStyle.Render("Phase Gate Check"))
|
|
fmt.Printf("Project: %s\n", textStyle.Render(projectName))
|
|
fmt.Printf("From: Phase %d → To: Phase %d\n\n", fromPhase, toPhase)
|
|
|
|
result, err := runTeamManager(projectName, "phase-gate-check", phaseGateArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().IntVar(&fromPhase, "from", 0, "Source phase number (1-4)")
|
|
cmd.Flags().IntVar(&toPhase, "to", 0, "Target phase number (2-5)")
|
|
|
|
cmd.MarkFlagRequired("from")
|
|
cmd.MarkFlagRequired("to")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// queryCmd creates the query command
|
|
func queryCmd() *cobra.Command {
|
|
var statusFilter, assigneeFilter, roleFilter string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "query",
|
|
Short: "Query teams with filters",
|
|
Long: `Query teams with filters for status, assignee, or role.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
|
|
queryArgs := []string{}
|
|
if statusFilter != "" {
|
|
queryArgs = append(queryArgs, "--status", statusFilter)
|
|
}
|
|
if assigneeFilter != "" {
|
|
queryArgs = append(queryArgs, "--assignee", assigneeFilter)
|
|
}
|
|
if roleFilter != "" {
|
|
queryArgs = append(queryArgs, "--role", roleFilter)
|
|
}
|
|
|
|
if output == "json" {
|
|
queryArgs = append(queryArgs, "--format", "json")
|
|
}
|
|
|
|
result, err := runTeamManager(projectName, "query", queryArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVar(&statusFilter, "status", "", "Filter by status (not_started, active, completed, blocked)")
|
|
cmd.Flags().StringVar(&assigneeFilter, "assignee", "", "Filter by assignee")
|
|
cmd.Flags().StringVar(&roleFilter, "role", "", "Filter by role")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// reassignCmd creates the reassign command
|
|
func reassignCmd() *cobra.Command {
|
|
var fromTeam, toTeam int
|
|
var fromRole, toRole, personName string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "reassign",
|
|
Short: "Reassign person from one role to another",
|
|
Long: `Move a person from one role/team to another role/team.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
|
|
reassignArgs := []string{
|
|
"--from-team", fmt.Sprintf("%d", fromTeam),
|
|
"--from-role", fromRole,
|
|
"--to-team", fmt.Sprintf("%d", toTeam),
|
|
"--to-role", toRole,
|
|
"--person", personName,
|
|
}
|
|
|
|
if output == "json" {
|
|
reassignArgs = append(reassignArgs, "--format", "json")
|
|
}
|
|
|
|
result, err := runTeamManager(projectName, "reassign", reassignArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().IntVar(&fromTeam, "from-team", 0, "Source team ID")
|
|
cmd.Flags().StringVar(&fromRole, "from-role", "", "Source role")
|
|
cmd.Flags().IntVar(&toTeam, "to-team", 0, "Target team ID")
|
|
cmd.Flags().StringVar(&toRole, "to-role", "", "Target role")
|
|
cmd.Flags().StringVar(&personName, "person", "", "Person to reassign")
|
|
|
|
cmd.MarkFlagRequired("from-team")
|
|
cmd.MarkFlagRequired("from-role")
|
|
cmd.MarkFlagRequired("to-team")
|
|
cmd.MarkFlagRequired("to-role")
|
|
cmd.MarkFlagRequired("person")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// auditCmd creates the audit command
|
|
func auditCmd() *cobra.Command {
|
|
var limit int
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "audit",
|
|
Short: "Query audit log",
|
|
Long: `Query the audit log for project changes.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
|
|
auditArgs := []string{}
|
|
if limit > 0 {
|
|
auditArgs = append(auditArgs, "--limit", fmt.Sprintf("%d", limit))
|
|
}
|
|
|
|
if output == "json" {
|
|
auditArgs = append(auditArgs, "--format", "json")
|
|
}
|
|
|
|
result, err := runTeamManager(projectName, "audit", auditArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().IntVar(&limit, "limit", 50, "Number of entries to show")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// historyCmd creates the history command
|
|
func historyCmd() *cobra.Command {
|
|
var startDate, endDate string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "history",
|
|
Short: "Show team history",
|
|
Long: `Show history for a specific team.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
if teamID == 0 {
|
|
return fmt.Errorf("--team flag is required")
|
|
}
|
|
|
|
historyArgs := []string{
|
|
"--team", fmt.Sprintf("%d", teamID),
|
|
}
|
|
if startDate != "" {
|
|
historyArgs = append(historyArgs, "--start-date", startDate)
|
|
}
|
|
if endDate != "" {
|
|
historyArgs = append(historyArgs, "--end-date", endDate)
|
|
}
|
|
|
|
if output == "json" {
|
|
historyArgs = append(historyArgs, "--format", "json")
|
|
}
|
|
|
|
result, err := runTeamManager(projectName, "team-history", historyArgs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().IntVarP(&teamID, "team", "t", 0, "Team ID")
|
|
cmd.Flags().StringVar(&startDate, "start-date", "", "Start date (ISO format)")
|
|
cmd.Flags().StringVar(&endDate, "end-date", "", "End date (ISO format)")
|
|
|
|
cmd.MarkFlagRequired("team")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// templateCmd creates the template command
|
|
func templateCmd() *cobra.Command {
|
|
var format, outputFile string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "template",
|
|
Short: "Create template for bulk assignments",
|
|
Long: `Create a CSV or JSON template for bulk role assignments.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
|
|
var cmdName string
|
|
var fileFlag string
|
|
switch format {
|
|
case "csv":
|
|
cmdName = "template-csv"
|
|
fileFlag = "--file"
|
|
case "json":
|
|
cmdName = "template-json"
|
|
fileFlag = "--file"
|
|
default:
|
|
return fmt.Errorf("unsupported format: %s (use csv or json)", format)
|
|
}
|
|
|
|
result, err := runTeamManager(projectName, cmdName, fileFlag, outputFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVarP(&format, "format", "f", "csv", "Template format (csv or json)")
|
|
cmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output file path")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// healthCmd creates the health command
|
|
func healthCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "health",
|
|
Short: "Check team manager health status",
|
|
Long: `Check the health status of the team manager.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
// Health check doesn't require a project
|
|
result, err := runTeamManager("", "health")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
// exportCmd creates the export command
|
|
func exportCmd() *cobra.Command {
|
|
var format string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "export",
|
|
Short: "Export project data",
|
|
Long: `Export team assignments and project data to a file.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
|
|
var result []byte
|
|
var err error
|
|
|
|
switch format {
|
|
case "json":
|
|
result, err = runTeamManager(projectName, "export-json")
|
|
case "csv":
|
|
result, err = runTeamManager(projectName, "export-csv")
|
|
default:
|
|
return fmt.Errorf("unsupported export format: %s", format)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write to stdout or file
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVarP(&format, "format", "f", "json", "Export format (json, csv)")
|
|
return cmd
|
|
}
|
|
|
|
// importCmd creates the import command
|
|
func importCmd() *cobra.Command {
|
|
var filePath string
|
|
var format string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "import",
|
|
Short: "Import project data",
|
|
Long: `Import team assignments from a file.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
if filePath == "" {
|
|
return fmt.Errorf("--file flag is required")
|
|
}
|
|
|
|
var result []byte
|
|
var err error
|
|
|
|
switch format {
|
|
case "json":
|
|
result, err = runTeamManager(projectName, "import-json", "--file", filePath)
|
|
case "csv":
|
|
result, err = runTeamManager(projectName, "import-csv", "--file", filePath)
|
|
default:
|
|
return fmt.Errorf("unsupported import format: %s", format)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVarP(&filePath, "file", "f", "", "Path to import file")
|
|
cmd.Flags().StringVar(&format, "format", "json", "Import format (json, csv)")
|
|
|
|
cmd.MarkFlagRequired("file")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// backupCmd creates the backup command
|
|
func backupCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "backup",
|
|
Short: "List available backups",
|
|
Long: `List all available backups for the project.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
|
|
if output == "json" {
|
|
result, err := runTeamManager(projectName, "list-backups", "--format", "json")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(result))
|
|
return nil
|
|
}
|
|
|
|
fmt.Println(titleStyle.Render("Available Backups"))
|
|
fmt.Printf("Project: %s\n\n", textStyle.Render(projectName))
|
|
|
|
result, err := runTeamManager(projectName, "list-backups")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
// restoreCmd creates the restore command
|
|
func restoreCmd() *cobra.Command {
|
|
var backupFile string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "restore",
|
|
Short: "Restore from backup",
|
|
Long: `Restore project data from a backup file.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
if backupFile == "" {
|
|
return fmt.Errorf("--backup flag is required")
|
|
}
|
|
|
|
fmt.Println(titleStyle.Render("Restoring from Backup"))
|
|
fmt.Printf("Project: %s\n", textStyle.Render(projectName))
|
|
fmt.Printf("Backup: %s\n\n", textStyle.Render(backupFile))
|
|
|
|
result, err := runTeamManager(projectName, "restore", "--backup", backupFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVarP(&backupFile, "backup", "b", "", "Path to backup file")
|
|
cmd.MarkFlagRequired("backup")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// deleteCmd creates the delete command
|
|
func deleteCmd() *cobra.Command {
|
|
var force bool
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "delete",
|
|
Short: "Delete project or team",
|
|
Long: `Delete a project or a specific team from the project.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if projectName == "" {
|
|
return fmt.Errorf("--project flag is required")
|
|
}
|
|
|
|
var result []byte
|
|
var err error
|
|
|
|
if teamID > 0 {
|
|
// Delete specific team
|
|
if !force {
|
|
fmt.Printf("Delete team %d from project '%s'? [y/N]: ", teamID, projectName)
|
|
var response string
|
|
fmt.Scanln(&response)
|
|
if !strings.EqualFold(response, "y") {
|
|
fmt.Println("Aborted.")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
fmt.Println(titleStyle.Render("Deleting Team"))
|
|
fmt.Printf("Project: %s\n", textStyle.Render(projectName))
|
|
fmt.Printf("Team: %d\n\n", teamID)
|
|
|
|
result, err = runTeamManager(projectName, "delete-team", "--team", fmt.Sprintf("%d", teamID))
|
|
} else {
|
|
// Delete entire project
|
|
if !force {
|
|
fmt.Printf("Delete entire project '%s'? This cannot be undone! [y/N]: ", projectName)
|
|
var response string
|
|
fmt.Scanln(&response)
|
|
if !strings.EqualFold(response, "y") {
|
|
fmt.Println("Aborted.")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
fmt.Println(titleStyle.Render("Deleting Project"))
|
|
fmt.Printf("Project: %s\n\n", textStyle.Render(projectName))
|
|
|
|
result, err = runTeamManager(projectName, "delete-project")
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println(string(result))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().IntVarP(&teamID, "team", "t", 0, "Team ID to delete (optional - if not specified, deletes entire project)")
|
|
cmd.Flags().BoolVar(&force, "force", false, "Skip confirmation prompt")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// Helper function to pretty print JSON
|
|
func prettyPrintJSON(data []byte) error {
|
|
var v interface{}
|
|
if err := json.Unmarshal(data, &v); err != nil {
|
|
return err
|
|
}
|
|
pretty, err := json.MarshalIndent(v, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(pretty))
|
|
return nil
|
|
}
|
|
|
|
// CheckPython returns an error if Python is not available
|
|
func CheckPython() error {
|
|
pythonCmd := "python3"
|
|
if runtime.GOOS == "windows" {
|
|
pythonCmd = "python"
|
|
}
|
|
_, err := exec.LookPath(pythonCmd)
|
|
if err != nil {
|
|
_, err = exec.LookPath("python")
|
|
if err != nil {
|
|
return fmt.Errorf("Python is required but not found. Please install Python 3")
|
|
}
|
|
}
|
|
return nil
|
|
}
|