package main import ( "context" "fmt" jira "github.com/andygrunwald/go-jira/v2/cloud" "github.com/charmbracelet/huh" "github.com/fatih/color" "github.com/spf13/viper" "os" "strconv" ) var red = color.New(color.FgRed).SprintFunc() var yellow = color.New(color.FgYellow).SprintFunc() var blue = color.New(color.FgBlue).SprintFunc() var green = color.New(color.FgGreen).SprintFunc() func printError(msg string, err error) { if err == nil { fmt.Printf("Error: %s\n", msg) } else { fmt.Printf("Error: %s: %v\n", msg, err) } } func ActionExit(_ []string) { promptRunner.Close() os.Exit(0) } func ActionLogout(_ []string) { viper.Set("token", nil) err := viper.WriteConfig() if err != nil { printError("Failed to write config", err) } ensureLoggedIn() } func ActionListProjects(_ []string) { projects, err := GetAllProjects() if err != nil { printError("Failed to list projects", err) return } for _, project := range *projects { fmt.Printf(" - %s: %s\n", project.Key, project.Name) } } func ActionUseProject(args []string) { var projectId string if len(args) == 0 { println("Fetching projects...") projects, err := GetAllProjects() if err != nil { printError("Failed to list projects", err) return } options := []huh.Option[string]{} for _, p := range *projects { name := fmt.Sprintf("%s - %s", red(p.Key), p.Name) options = append(options, huh.NewOption(name, p.ID)) } err = huh.NewSelect[string](). Title("Choose a project"). Options(options...). Value(&projectId). Run() if err != nil { printError("Failed to choose project", err) return } } else if len(args) == 1 { projectId = args[0] } else { printError("Need a project ID", nil) return } project, _, err := jiraClient.Project.Get(context.Background(), projectId) if err != nil { printError("Failed to get project", err) return } fmt.Printf("Using project %s (%s)\n", project.Name, project.Key) SetProject(project) SaveConfig() UpdatePrompt() } func ActionListBoards(_ []string) { if !HasProject() { printError("Please select a project first", nil) return } list, err := GetAllBoardsForProject(GetProjectId()) if err != nil { printError("Failed to list boards", err) return } for _, board := range list.Values { fmt.Printf(" - %s (%d)\n", board.Name, board.ID) } } func ActionUseBoard(args []string) { if !HasIssue() { printError("Please select a project first", nil) return } var boardId int64 var err error if len(args) == 0 { println("Fetching boards...") boards, err := GetAllBoardsForProject(GetProjectId()) if err != nil { printError("Failed to list boards", err) return } options := []huh.Option[int64]{} for _, board := range boards.Values { name := fmt.Sprintf("%s (%s)", board.Name, board.Type) options = append(options, huh.NewOption(name, int64(board.ID))) } err = huh.NewSelect[int64](). Title("Choose a board"). Options(options...). Value(&boardId). Run() if err != nil { printError("Failed to choose a board", err) return } } else if len(args) == 1 { boardId, err = strconv.ParseInt(args[0], 10, 64) if err != nil { printError("Invalid board ID", nil) return } } else { printError("Invalid number of arguments", nil) } board, err := GetBoardConfiguration(int(boardId)) if err != nil { printError("Failed to get project", err) return } fmt.Printf("Using board %s (%d)\n", board.Name, board.ID) SetBoard(board.ID, board.Name) SaveConfig() UpdatePrompt() } func ActionUseCurrentSprint(_ []string) { boardId := GetBoardId() sprint, err := GetCurrentSprint(boardId) if err != nil { printError("Failed to get current sprint", err) return } fmt.Printf("Using sprint %s (%d)\n", sprint.Name, sprint.ID) SetSprint(sprint) SaveConfig() UpdatePrompt() } func ActionUseIssue(args []string) { actionUseIssueWithFilter(args, func(_ *jira.Issue) bool { return true }) } func ActionUseMyIssue(args []string) { actionUseIssueWithFilter(args, func(issue *jira.Issue) bool { assignee := issue.Fields.Assignee if assignee == nil { return false } return assignee.AccountID == jiraUser.AccountID }) } func actionUseIssueWithFilter(args []string, allowIssue func(issue *jira.Issue) bool) { if !HasSprint() { printError("No sprint selected", nil) return } sprintId := GetSprintId() var issueKey string var err error if len(args) == 0 { issues, err := GetIssuesForSprint(sprintId) if err != nil { printError("Failed to fetch issues", err) return } options := []huh.Option[string]{} for _, issue := range issues { if allowIssue(&issue) { key := issue.Key summary := issue.Fields.Summary status := issue.Fields.Status.Name assignee := "Unassigned" if issue.Fields.Assignee != nil { if issue.Fields.Assignee.AccountID == jiraUser.AccountID { assignee = green(issue.Fields.Assignee.DisplayName) } else { assignee = yellow(issue.Fields.Assignee.DisplayName) } } name := fmt.Sprintf("%s - %s (%s, %s)", red(key), summary, blue(status), assignee) options = append(options, huh.NewOption(name, issue.Key)) } } err = huh.NewSelect[string](). Title("Choose an issue"). Options(options...). Value(&issueKey). Run() if err != nil { printError("Failed to choose an issue", err) return } } else if len(args) == 1 { issueKey = args[0] } else { printError("Invalid number of arguments", nil) } issue, err := GetIssue(issueKey) if err != nil { printError("Failed to get issue", err) return } fmt.Printf("Using issue %s - %s\n", issue.Key, issue.Fields.Summary) SetIssue(issue.Key) SaveConfig() UpdatePrompt() } func ActionListIssues(_ []string) { sprintId := GetSprintId() if sprintId == 0 { printError("No sprint selected", nil) return } issues, err := GetIssuesForSprint(sprintId) if err != nil { printError("Failed to list issues", err) } description := "" for _, issue := range issues { description += fmt.Sprintf("%s - %s (%s)\n", red(issue.Key), issue.Fields.Summary, yellow(issue.Fields.Status.Name)) } } func ActionBack(_ []string) { if HasIssue() { UnsetIssue() } else if HasSprint() { UnsetSprint() } else if HasBoard() { UnsetBoard() } else if HasProject() { UnsetProject() } SaveConfig() UpdatePrompt() } func ActionViewCard(_ []string) { if !HasIssue() { printError("No issue selected", nil) return } issue, err := GetIssue(GetIssueKey()) if err != nil { printError("Failed to get issue", err) return } fmt.Printf("Key: %s\n", issue.Key) fmt.Printf("Summary: %s\n", issue.Fields.Summary) fmt.Printf("Type: %s\n", issue.Fields.Type.Name) fmt.Printf("Status: %s\n", issue.Fields.Status.Name) if issue.Fields.Assignee != nil { fmt.Printf("Assignee: %s\n", issue.Fields.Assignee.DisplayName) } else { fmt.Printf("Assignee: Unassigned\n") } fmt.Printf("Number of comments: %d\n", len(issue.Fields.Comments.Comments)) } func ActionViewDescription(_ []string) { if !HasIssue() { printError("No issue selected", nil) return } issue, err := GetIssue(GetIssueKey()) if err != nil { printError("Failed to get issue", err) return } println(issue.Fields.Description) } func ActionViewComments(_ []string) { if !HasIssue() { printError("No issue selected", nil) return } issue, err := GetIssue(GetIssueKey()) if err != nil { printError("Failed to get issue", err) return } if len(issue.Fields.Comments.Comments) == 0 { println("This issue has no comments") } for i, comment := range issue.Fields.Comments.Comments { if i != 0 { println() } fmt.Printf("%s at %s:\n", red(comment.Author.DisplayName), blue(comment.Created)) fmt.Printf(" %s\n", comment.Body) } } func ActionSetStatus(_ []string) { if !HasIssue() { printError("No issue selected", nil) return } issueKey := GetIssueKey() issue, err := GetIssue(issueKey) if err != nil { printError("Failed to fetch issue", err) return } transitions, err := GetIssueTransitions(issueKey) if err != nil { printError("Could not fetch issue transitions", err) return } options := []huh.Option[string]{} var selected string for _, transition := range transitions { options = append(options, huh.Option[string]{Key: transition.Name, Value: transition.ID}) if transition.To.ID == issue.Fields.Status.ID { selected = transition.ID } } err = huh.NewSelect[string](). Title("Select Option"). Options(options...). Value(&selected). Run() if err != nil { printError("Failed to select status", err) return } err = UpdateIssueTransition(issueKey, selected) if err != nil { printError("Failed to update status", err) } } func ActionSetTitle(_ []string) { if !HasIssue() { printError("No issue selected", nil) return } issue, err := GetIssue(GetIssueKey()) if err != nil { printError("Failed to get issue", err) return } summary := issue.Fields.Summary err = huh.NewInput(). Title("Enter a title:"). Value(&summary). Run() if err != nil { printError("Failed to set title", err) return } err = UpdateIssueTitle(issue.Key, summary) if err != nil { printError("Failed to set title", err) return } println("Title changed!") }