Released v1
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"git.franklin.lab/steve.cliff/pcli/output"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var boardCmd = &cobra.Command{
|
||||
Use: "board",
|
||||
Short: "Manage boards",
|
||||
Long: "Commands for managing Planka boards",
|
||||
}
|
||||
|
||||
var boardListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all accessible boards",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
boards, err := getClient().ListBoards(getContext())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(boards, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var boardGetCmd = &cobra.Command{
|
||||
Use: "get <id>",
|
||||
Short: "Get a board by ID",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
board, err := getClient().GetBoard(getContext(), args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(board, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var boardActionsCmd = &cobra.Command{
|
||||
Use: "actions <id>",
|
||||
Short: "List actions for a board",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
limit, _ := cmd.Flags().GetInt("limit")
|
||||
|
||||
actions, err := getClient().ListBoardActions(getContext(), args[0], limit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(actions, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(boardCmd)
|
||||
boardCmd.AddCommand(boardListCmd)
|
||||
boardCmd.AddCommand(boardGetCmd)
|
||||
boardCmd.AddCommand(boardActionsCmd)
|
||||
|
||||
boardActionsCmd.Flags().Int("limit", 0, "Limit number of actions (0 = no limit)")
|
||||
}
|
||||
+357
@@ -0,0 +1,357 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.franklin.lab/steve.cliff/pcli/output"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cardCmd = &cobra.Command{
|
||||
Use: "card",
|
||||
Short: "Manage cards",
|
||||
Long: "Commands for managing Planka cards",
|
||||
}
|
||||
|
||||
var cardListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List cards",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
boardId, _ := cmd.Flags().GetString("board")
|
||||
listId, _ := cmd.Flags().GetString("list")
|
||||
limit, _ := cmd.Flags().GetInt("limit")
|
||||
|
||||
if boardId == "" && listId == "" {
|
||||
return fmt.Errorf("either --board or --list must be specified")
|
||||
}
|
||||
|
||||
if boardId != "" && listId != "" {
|
||||
return fmt.Errorf("--board and --list are mutually exclusive")
|
||||
}
|
||||
|
||||
if boardId != "" {
|
||||
cards, err := getClient().ListCardsByBoard(getContext(), boardId, limit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return output.Print(cards, getFormat(), os.Stdout)
|
||||
}
|
||||
|
||||
cards, err := getClient().ListCards(getContext(), listId, limit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(cards, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var cardGetCmd = &cobra.Command{
|
||||
Use: "get <id>",
|
||||
Short: "Get a card by ID",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
card, err := getClient().GetCard(getContext(), args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(card, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var cardCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a new card",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
listId, _ := cmd.Flags().GetString("list")
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
|
||||
if listId == "" {
|
||||
return fmt.Errorf("--list is required")
|
||||
}
|
||||
if name == "" {
|
||||
return fmt.Errorf("--name is required")
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"name": name,
|
||||
"type": "project",
|
||||
"position": float64(65536),
|
||||
}
|
||||
|
||||
if desc, _ := cmd.Flags().GetString("description"); desc != "" {
|
||||
fields["description"] = desc
|
||||
}
|
||||
if cardType, _ := cmd.Flags().GetString("type"); cardType != "" {
|
||||
fields["type"] = cardType
|
||||
}
|
||||
if pos, _ := cmd.Flags().GetFloat64("position"); cmd.Flags().Changed("position") {
|
||||
fields["position"] = pos
|
||||
}
|
||||
if dueDate, _ := cmd.Flags().GetString("due-date"); dueDate != "" {
|
||||
fields["dueDate"] = dueDate
|
||||
}
|
||||
if dueCompleted, _ := cmd.Flags().GetBool("due-completed"); cmd.Flags().Changed("due-completed") {
|
||||
fields["isDueCompleted"] = dueCompleted
|
||||
}
|
||||
|
||||
card, err := getClient().CreateCard(getContext(), listId, fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(card, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var cardUpdateCmd = &cobra.Command{
|
||||
Use: "update <id>",
|
||||
Short: "Update a card",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
fields := make(map[string]any)
|
||||
|
||||
if name, _ := cmd.Flags().GetString("name"); name != "" {
|
||||
fields["name"] = name
|
||||
}
|
||||
if desc, _ := cmd.Flags().GetString("description"); cmd.Flags().Changed("description") {
|
||||
fields["description"] = desc
|
||||
}
|
||||
if cardType, _ := cmd.Flags().GetString("type"); cardType != "" {
|
||||
fields["type"] = cardType
|
||||
}
|
||||
if pos, _ := cmd.Flags().GetFloat64("position"); cmd.Flags().Changed("position") {
|
||||
fields["position"] = pos
|
||||
}
|
||||
if dueDate, _ := cmd.Flags().GetString("due-date"); cmd.Flags().Changed("due-date") {
|
||||
fields["dueDate"] = dueDate
|
||||
}
|
||||
if dueCompleted, _ := cmd.Flags().GetBool("due-completed"); cmd.Flags().Changed("due-completed") {
|
||||
fields["isDueCompleted"] = dueCompleted
|
||||
}
|
||||
|
||||
if len(fields) == 0 {
|
||||
return fmt.Errorf("at least one field must be specified for update")
|
||||
}
|
||||
|
||||
card, err := getClient().UpdateCard(getContext(), args[0], fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(card, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var cardDeleteCmd = &cobra.Command{
|
||||
Use: "delete <id>",
|
||||
Short: "Delete a card",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := getClient().DeleteCard(getContext(), args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(map[string]string{"status": "deleted"}, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var cardDuplicateCmd = &cobra.Command{
|
||||
Use: "duplicate <id>",
|
||||
Short: "Duplicate a card",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
if name == "" {
|
||||
return fmt.Errorf("--name is required")
|
||||
}
|
||||
pos := float64(65536)
|
||||
if cmd.Flags().Changed("position") {
|
||||
pos, _ = cmd.Flags().GetFloat64("position")
|
||||
}
|
||||
|
||||
card, err := getClient().DuplicateCard(getContext(), args[0], &name, &pos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(card, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var cardMoveCmd = &cobra.Command{
|
||||
Use: "move <id>",
|
||||
Short: "Move a card to a different list",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
listId, _ := cmd.Flags().GetString("list")
|
||||
|
||||
if listId == "" {
|
||||
return fmt.Errorf("--list is required")
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"listId": listId,
|
||||
"position": float64(65536),
|
||||
}
|
||||
|
||||
if pos, _ := cmd.Flags().GetFloat64("position"); cmd.Flags().Changed("position") {
|
||||
fields["position"] = pos
|
||||
}
|
||||
|
||||
card, err := getClient().UpdateCard(getContext(), args[0], fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(card, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var cardAssignCmd = &cobra.Command{
|
||||
Use: "assign <id>",
|
||||
Short: "Assign a user to a card",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
userId, _ := cmd.Flags().GetString("user")
|
||||
|
||||
if userId == "" {
|
||||
return fmt.Errorf("--user is required")
|
||||
}
|
||||
|
||||
err := getClient().AddCardMember(getContext(), args[0], userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(map[string]string{"status": "assigned"}, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var cardUnassignCmd = &cobra.Command{
|
||||
Use: "unassign <id>",
|
||||
Short: "Unassign a user from a card",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
userId, _ := cmd.Flags().GetString("user")
|
||||
|
||||
if userId == "" {
|
||||
return fmt.Errorf("--user is required")
|
||||
}
|
||||
|
||||
err := getClient().RemoveCardMember(getContext(), args[0], userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(map[string]string{"status": "unassigned"}, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var cardAddLabelCmd = &cobra.Command{
|
||||
Use: "add-label <id>",
|
||||
Short: "Add a label to a card",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
labelId, _ := cmd.Flags().GetString("label")
|
||||
|
||||
if labelId == "" {
|
||||
return fmt.Errorf("--label is required")
|
||||
}
|
||||
|
||||
err := getClient().AddCardLabel(getContext(), args[0], labelId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(map[string]string{"status": "label added"}, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var cardRemoveLabelCmd = &cobra.Command{
|
||||
Use: "remove-label <id>",
|
||||
Short: "Remove a label from a card",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
labelId, _ := cmd.Flags().GetString("label")
|
||||
|
||||
if labelId == "" {
|
||||
return fmt.Errorf("--label is required")
|
||||
}
|
||||
|
||||
err := getClient().RemoveCardLabel(getContext(), args[0], labelId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(map[string]string{"status": "label removed"}, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var cardActionsCmd = &cobra.Command{
|
||||
Use: "actions <id>",
|
||||
Short: "List actions for a card",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
limit, _ := cmd.Flags().GetInt("limit")
|
||||
|
||||
actions, err := getClient().ListCardActions(getContext(), args[0], limit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(actions, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(cardCmd)
|
||||
cardCmd.AddCommand(cardListCmd)
|
||||
cardCmd.AddCommand(cardGetCmd)
|
||||
cardCmd.AddCommand(cardCreateCmd)
|
||||
cardCmd.AddCommand(cardUpdateCmd)
|
||||
cardCmd.AddCommand(cardDeleteCmd)
|
||||
cardCmd.AddCommand(cardDuplicateCmd)
|
||||
cardCmd.AddCommand(cardMoveCmd)
|
||||
cardCmd.AddCommand(cardAssignCmd)
|
||||
cardCmd.AddCommand(cardUnassignCmd)
|
||||
cardCmd.AddCommand(cardAddLabelCmd)
|
||||
cardCmd.AddCommand(cardRemoveLabelCmd)
|
||||
cardCmd.AddCommand(cardActionsCmd)
|
||||
|
||||
cardListCmd.Flags().String("board", "", "Board ID to list cards from")
|
||||
cardListCmd.Flags().String("list", "", "List ID to list cards from")
|
||||
cardListCmd.Flags().Int("limit", 0, "Limit number of cards (0 = no limit)")
|
||||
|
||||
cardCreateCmd.Flags().String("list", "", "List ID (required)")
|
||||
cardCreateCmd.Flags().String("name", "", "Card name (required)")
|
||||
cardCreateCmd.Flags().String("description", "", "Card description")
|
||||
cardCreateCmd.Flags().String("type", "", "Card type (project or story)")
|
||||
cardCreateCmd.Flags().Float64("position", 0, "Card position")
|
||||
cardCreateCmd.Flags().String("due-date", "", "Due date (ISO 8601 format)")
|
||||
cardCreateCmd.Flags().Bool("due-completed", false, "Whether due date is completed")
|
||||
|
||||
cardUpdateCmd.Flags().String("name", "", "Card name")
|
||||
cardUpdateCmd.Flags().String("description", "", "Card description")
|
||||
cardUpdateCmd.Flags().String("type", "", "Card type (project or story)")
|
||||
cardUpdateCmd.Flags().Float64("position", 0, "Card position")
|
||||
cardUpdateCmd.Flags().String("due-date", "", "Due date (ISO 8601 format)")
|
||||
cardUpdateCmd.Flags().Bool("due-completed", false, "Whether due date is completed")
|
||||
|
||||
cardDuplicateCmd.Flags().String("name", "", "Name for duplicated card")
|
||||
cardDuplicateCmd.Flags().Float64("position", 0, "Position for duplicated card")
|
||||
|
||||
cardMoveCmd.Flags().String("list", "", "Target list ID (required)")
|
||||
cardMoveCmd.Flags().Float64("position", 0, "Position in target list")
|
||||
|
||||
cardAssignCmd.Flags().String("user", "", "User ID (required)")
|
||||
cardUnassignCmd.Flags().String("user", "", "User ID (required)")
|
||||
|
||||
cardAddLabelCmd.Flags().String("label", "", "Label ID (required)")
|
||||
cardRemoveLabelCmd.Flags().String("label", "", "Label ID (required)")
|
||||
|
||||
cardActionsCmd.Flags().Int("limit", 0, "Limit number of actions (0 = no limit)")
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.franklin.lab/steve.cliff/pcli/output"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var commentCmd = &cobra.Command{
|
||||
Use: "comment",
|
||||
Short: "Manage comments",
|
||||
Long: "Commands for managing Planka card comments",
|
||||
}
|
||||
|
||||
var commentListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List comments for a card",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cardId, _ := cmd.Flags().GetString("card")
|
||||
limit, _ := cmd.Flags().GetInt("limit")
|
||||
|
||||
if cardId == "" {
|
||||
return fmt.Errorf("--card is required")
|
||||
}
|
||||
|
||||
comments, err := getClient().ListComments(getContext(), cardId, limit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(comments, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var commentCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a new comment",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cardId, _ := cmd.Flags().GetString("card")
|
||||
text, _ := cmd.Flags().GetString("text")
|
||||
|
||||
if cardId == "" {
|
||||
return fmt.Errorf("--card is required")
|
||||
}
|
||||
if text == "" {
|
||||
return fmt.Errorf("--text is required")
|
||||
}
|
||||
|
||||
comment, err := getClient().CreateComment(getContext(), cardId, text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(comment, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var commentUpdateCmd = &cobra.Command{
|
||||
Use: "update <id>",
|
||||
Short: "Update a comment",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
text, _ := cmd.Flags().GetString("text")
|
||||
|
||||
if text == "" {
|
||||
return fmt.Errorf("--text is required")
|
||||
}
|
||||
|
||||
comment, err := getClient().UpdateComment(getContext(), args[0], text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(comment, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var commentDeleteCmd = &cobra.Command{
|
||||
Use: "delete <id>",
|
||||
Short: "Delete a comment",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := getClient().DeleteComment(getContext(), args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(map[string]string{"status": "deleted"}, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(commentCmd)
|
||||
commentCmd.AddCommand(commentListCmd)
|
||||
commentCmd.AddCommand(commentCreateCmd)
|
||||
commentCmd.AddCommand(commentUpdateCmd)
|
||||
commentCmd.AddCommand(commentDeleteCmd)
|
||||
|
||||
commentListCmd.Flags().String("card", "", "Card ID (required)")
|
||||
commentListCmd.Flags().Int("limit", 0, "Limit number of comments (0 = no limit)")
|
||||
|
||||
commentCreateCmd.Flags().String("card", "", "Card ID (required)")
|
||||
commentCreateCmd.Flags().String("text", "", "Comment text (required)")
|
||||
|
||||
commentUpdateCmd.Flags().String("text", "", "Comment text (required)")
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.franklin.lab/steve.cliff/pcli/output"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var labelCmd = &cobra.Command{
|
||||
Use: "label",
|
||||
Short: "Manage labels",
|
||||
Long: "Commands for managing Planka labels",
|
||||
}
|
||||
|
||||
var labelCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a new label",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
boardId, _ := cmd.Flags().GetString("board")
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
|
||||
if boardId == "" {
|
||||
return fmt.Errorf("--board is required")
|
||||
}
|
||||
if name == "" {
|
||||
return fmt.Errorf("--name is required")
|
||||
}
|
||||
|
||||
color, _ := cmd.Flags().GetString("color")
|
||||
if color == "" {
|
||||
return fmt.Errorf("--color is required")
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"name": name,
|
||||
"color": color,
|
||||
"position": float64(65536),
|
||||
}
|
||||
|
||||
if pos, _ := cmd.Flags().GetFloat64("position"); cmd.Flags().Changed("position") {
|
||||
fields["position"] = pos
|
||||
}
|
||||
|
||||
label, err := getClient().CreateLabel(getContext(), boardId, fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(label, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var labelUpdateCmd = &cobra.Command{
|
||||
Use: "update <id>",
|
||||
Short: "Update a label",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
fields := make(map[string]any)
|
||||
|
||||
if name, _ := cmd.Flags().GetString("name"); name != "" {
|
||||
fields["name"] = name
|
||||
}
|
||||
if color, _ := cmd.Flags().GetString("color"); color != "" {
|
||||
fields["color"] = color
|
||||
}
|
||||
if pos, _ := cmd.Flags().GetFloat64("position"); cmd.Flags().Changed("position") {
|
||||
fields["position"] = pos
|
||||
}
|
||||
|
||||
if len(fields) == 0 {
|
||||
return fmt.Errorf("at least one field must be specified for update")
|
||||
}
|
||||
|
||||
label, err := getClient().UpdateLabel(getContext(), args[0], fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(label, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var labelDeleteCmd = &cobra.Command{
|
||||
Use: "delete <id>",
|
||||
Short: "Delete a label",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := getClient().DeleteLabel(getContext(), args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(map[string]string{"status": "deleted"}, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(labelCmd)
|
||||
labelCmd.AddCommand(labelCreateCmd)
|
||||
labelCmd.AddCommand(labelUpdateCmd)
|
||||
labelCmd.AddCommand(labelDeleteCmd)
|
||||
|
||||
labelCreateCmd.Flags().String("board", "", "Board ID (required)")
|
||||
labelCreateCmd.Flags().String("name", "", "Label name (required)")
|
||||
labelCreateCmd.Flags().String("color", "", "Label color")
|
||||
labelCreateCmd.Flags().Float64("position", 0, "Position")
|
||||
|
||||
labelUpdateCmd.Flags().String("name", "", "Label name")
|
||||
labelUpdateCmd.Flags().String("color", "", "Label color")
|
||||
labelUpdateCmd.Flags().Float64("position", 0, "Position")
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"git.franklin.lab/steve.cliff/pcli/output"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var projectCmd = &cobra.Command{
|
||||
Use: "project",
|
||||
Short: "Manage projects",
|
||||
Long: "Commands for managing Planka projects",
|
||||
}
|
||||
|
||||
var projectListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all projects",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
projects, err := getClient().ListProjects(getContext())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(projects, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var projectGetCmd = &cobra.Command{
|
||||
Use: "get <id>",
|
||||
Short: "Get a project by ID",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
project, err := getClient().GetProject(getContext(), args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(project, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(projectCmd)
|
||||
projectCmd.AddCommand(projectListCmd)
|
||||
projectCmd.AddCommand(projectGetCmd)
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"git.franklin.lab/steve.cliff/pcli/client"
|
||||
"git.franklin.lab/steve.cliff/pcli/logging"
|
||||
"git.franklin.lab/steve.cliff/pcli/output"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
flagFormat string
|
||||
flagURL string
|
||||
flagAPIKey string
|
||||
flagLogLevel string
|
||||
|
||||
apiClient *client.Client
|
||||
logger *slog.Logger
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "pcli",
|
||||
Short: "CLI for Planka API",
|
||||
Long: "A command-line interface for interacting with the Planka project management API",
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
url := flagURL
|
||||
if url == "" {
|
||||
url = os.Getenv("PLANKA_URL")
|
||||
}
|
||||
if url == "" {
|
||||
return fmt.Errorf("PLANKA_URL must be set via --url flag or PLANKA_URL environment variable")
|
||||
}
|
||||
|
||||
// Validate URL format
|
||||
if err := validateURL(url); err != nil {
|
||||
return fmt.Errorf("invalid PLANKA_URL: %w", err)
|
||||
}
|
||||
|
||||
apiKey := flagAPIKey
|
||||
if apiKey == "" {
|
||||
apiKey = os.Getenv("PLANKA_API_KEY")
|
||||
}
|
||||
if apiKey == "" {
|
||||
return fmt.Errorf("PLANKA_API_KEY must be set via --api-key flag or PLANKA_API_KEY environment variable")
|
||||
}
|
||||
|
||||
logger = logging.NewLogger(flagLogLevel)
|
||||
|
||||
logger.Debug("Initializing client",
|
||||
slog.String("url", url),
|
||||
slog.String("log_level", flagLogLevel),
|
||||
)
|
||||
|
||||
apiClient = client.NewClient(url, apiKey, logger)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVar(&flagFormat, "format", "json", "Output format (json or table)")
|
||||
rootCmd.PersistentFlags().StringVar(&flagURL, "url", "", "Planka API URL (overrides PLANKA_URL env var)")
|
||||
rootCmd.PersistentFlags().StringVar(&flagAPIKey, "api-key", "", "Planka API key (overrides PLANKA_API_KEY env var)")
|
||||
rootCmd.PersistentFlags().StringVar(&flagLogLevel, "log-level", "warn", "Log level (debug, info, warn, error)")
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
output.PrintError(err, flagFormat, os.Stdout)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func getClient() *client.Client {
|
||||
return apiClient
|
||||
}
|
||||
|
||||
func getContext() context.Context {
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
func getFormat() string {
|
||||
return flagFormat
|
||||
}
|
||||
|
||||
func validateURL(urlStr string) error {
|
||||
parsedURL, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse URL: %w", err)
|
||||
}
|
||||
|
||||
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
|
||||
return fmt.Errorf("URL must use http or https scheme, got: %s", parsedURL.Scheme)
|
||||
}
|
||||
|
||||
if parsedURL.Host == "" {
|
||||
return fmt.Errorf("URL must have a valid host")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.franklin.lab/steve.cliff/pcli/model"
|
||||
"git.franklin.lab/steve.cliff/pcli/output"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var statusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Show status summary of all boards and their lists",
|
||||
Long: "Displays a summary of all boards, their lists, and the number of cards in each list",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Get all boards
|
||||
boards, err := getClient().ListBoards(getContext())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list boards: %w", err)
|
||||
}
|
||||
|
||||
// Build status summary with error collection
|
||||
summary := model.StatusSummary{
|
||||
TotalBoards: len(boards),
|
||||
Boards: make([]model.BoardSummary, 0, len(boards)),
|
||||
}
|
||||
|
||||
var boardErrors []string
|
||||
successfulBoards := 0
|
||||
|
||||
// For each board, get details and aggregate card counts
|
||||
for _, board := range boards {
|
||||
boardDetail, err := getClient().GetBoard(getContext(), board.ID)
|
||||
if err != nil {
|
||||
boardErrors = append(boardErrors, fmt.Sprintf("Board %s (%s): %v", board.Name, board.ID, err))
|
||||
continue
|
||||
}
|
||||
|
||||
// Initialize list summaries with zero counts
|
||||
listCounts := make(map[string]*model.ListSummary)
|
||||
listOrder := make([]string, 0, len(boardDetail.Lists))
|
||||
for _, list := range boardDetail.Lists {
|
||||
listName := ""
|
||||
if list.Name != nil {
|
||||
listName = *list.Name
|
||||
}
|
||||
listCounts[list.ID] = &model.ListSummary{
|
||||
ID: list.ID,
|
||||
Name: listName,
|
||||
OpenCards: 0,
|
||||
ClosedCards: 0,
|
||||
}
|
||||
listOrder = append(listOrder, list.ID)
|
||||
}
|
||||
|
||||
// Count cards per list
|
||||
for _, card := range boardDetail.Cards {
|
||||
if listSummary, exists := listCounts[card.ListID]; exists {
|
||||
if card.IsClosed {
|
||||
listSummary.ClosedCards++
|
||||
} else {
|
||||
listSummary.OpenCards++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build list summaries in original order
|
||||
listSummaries := make([]model.ListSummary, 0, len(listOrder))
|
||||
for _, id := range listOrder {
|
||||
listSummaries = append(listSummaries, *listCounts[id])
|
||||
}
|
||||
|
||||
// Add board summary
|
||||
boardSummary := model.BoardSummary{
|
||||
ID: board.ID,
|
||||
Name: board.Name,
|
||||
Lists: listSummaries,
|
||||
}
|
||||
summary.Boards = append(summary.Boards, boardSummary)
|
||||
successfulBoards++
|
||||
}
|
||||
|
||||
// Update total boards to reflect successful loads
|
||||
summary.TotalBoards = successfulBoards
|
||||
|
||||
// If we have errors but some successful boards, add error info to output
|
||||
if len(boardErrors) > 0 && successfulBoards > 0 {
|
||||
fmt.Fprintf(os.Stderr, "Warning: %d board(s) failed to load:\n", len(boardErrors))
|
||||
for _, errMsg := range boardErrors {
|
||||
fmt.Fprintf(os.Stderr, " - %s\n", errMsg)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\nShowing %d successful board(s):\n\n", successfulBoards)
|
||||
} else if len(boardErrors) > 0 && successfulBoards == 0 {
|
||||
return fmt.Errorf("failed to load any boards: %s", boardErrors[0])
|
||||
}
|
||||
|
||||
// Output the summary
|
||||
return output.Print(summary, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(statusCmd)
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.franklin.lab/steve.cliff/pcli/output"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var taskCmd = &cobra.Command{
|
||||
Use: "task",
|
||||
Short: "Manage tasks",
|
||||
Long: "Commands for managing Planka tasks",
|
||||
}
|
||||
|
||||
var taskCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a new task",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
taskListId, _ := cmd.Flags().GetString("task-list")
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
|
||||
if taskListId == "" {
|
||||
return fmt.Errorf("--task-list is required")
|
||||
}
|
||||
if name == "" {
|
||||
return fmt.Errorf("--name is required")
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"name": name,
|
||||
"position": float64(65536),
|
||||
}
|
||||
|
||||
if pos, _ := cmd.Flags().GetFloat64("position"); cmd.Flags().Changed("position") {
|
||||
fields["position"] = pos
|
||||
}
|
||||
if completed, _ := cmd.Flags().GetBool("completed"); cmd.Flags().Changed("completed") {
|
||||
fields["isCompleted"] = completed
|
||||
}
|
||||
|
||||
task, err := getClient().CreateTask(getContext(), taskListId, fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(task, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var taskUpdateCmd = &cobra.Command{
|
||||
Use: "update <id>",
|
||||
Short: "Update a task",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
fields := make(map[string]any)
|
||||
|
||||
if name, _ := cmd.Flags().GetString("name"); name != "" {
|
||||
fields["name"] = name
|
||||
}
|
||||
if pos, _ := cmd.Flags().GetFloat64("position"); cmd.Flags().Changed("position") {
|
||||
fields["position"] = pos
|
||||
}
|
||||
if completed, _ := cmd.Flags().GetBool("completed"); cmd.Flags().Changed("completed") {
|
||||
fields["isCompleted"] = completed
|
||||
}
|
||||
|
||||
if len(fields) == 0 {
|
||||
return fmt.Errorf("at least one field must be specified for update")
|
||||
}
|
||||
|
||||
task, err := getClient().UpdateTask(getContext(), args[0], fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(task, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var taskDeleteCmd = &cobra.Command{
|
||||
Use: "delete <id>",
|
||||
Short: "Delete a task",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := getClient().DeleteTask(getContext(), args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(map[string]string{"status": "deleted"}, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(taskCmd)
|
||||
taskCmd.AddCommand(taskCreateCmd)
|
||||
taskCmd.AddCommand(taskUpdateCmd)
|
||||
taskCmd.AddCommand(taskDeleteCmd)
|
||||
|
||||
taskCreateCmd.Flags().String("task-list", "", "Task list ID (required)")
|
||||
taskCreateCmd.Flags().String("name", "", "Task name (required)")
|
||||
taskCreateCmd.Flags().Float64("position", 0, "Position")
|
||||
taskCreateCmd.Flags().Bool("completed", false, "Whether task is completed")
|
||||
|
||||
taskUpdateCmd.Flags().String("name", "", "Task name")
|
||||
taskUpdateCmd.Flags().Float64("position", 0, "Position")
|
||||
taskUpdateCmd.Flags().Bool("completed", false, "Whether task is completed")
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.franklin.lab/steve.cliff/pcli/output"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var taskListCmd = &cobra.Command{
|
||||
Use: "task-list",
|
||||
Short: "Manage task lists",
|
||||
Long: "Commands for managing Planka task lists",
|
||||
}
|
||||
|
||||
var taskListCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a new task list",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cardId, _ := cmd.Flags().GetString("card")
|
||||
name, _ := cmd.Flags().GetString("name")
|
||||
|
||||
if cardId == "" {
|
||||
return fmt.Errorf("--card is required")
|
||||
}
|
||||
if name == "" {
|
||||
return fmt.Errorf("--name is required")
|
||||
}
|
||||
|
||||
fields := map[string]any{
|
||||
"name": name,
|
||||
"position": float64(65536),
|
||||
}
|
||||
|
||||
if pos, _ := cmd.Flags().GetFloat64("position"); cmd.Flags().Changed("position") {
|
||||
fields["position"] = pos
|
||||
}
|
||||
if showOnFront, _ := cmd.Flags().GetBool("show-on-front"); cmd.Flags().Changed("show-on-front") {
|
||||
fields["showOnFrontOfCard"] = showOnFront
|
||||
}
|
||||
if hideCompleted, _ := cmd.Flags().GetBool("hide-completed"); cmd.Flags().Changed("hide-completed") {
|
||||
fields["hideCompletedTasks"] = hideCompleted
|
||||
}
|
||||
|
||||
taskList, err := getClient().CreateTaskList(getContext(), cardId, fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(taskList, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var taskListGetCmd = &cobra.Command{
|
||||
Use: "get <id>",
|
||||
Short: "Get a task list by ID",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
taskList, err := getClient().GetTaskList(getContext(), args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(taskList, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var taskListUpdateCmd = &cobra.Command{
|
||||
Use: "update <id>",
|
||||
Short: "Update a task list",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
fields := make(map[string]any)
|
||||
|
||||
if name, _ := cmd.Flags().GetString("name"); name != "" {
|
||||
fields["name"] = name
|
||||
}
|
||||
if pos, _ := cmd.Flags().GetFloat64("position"); cmd.Flags().Changed("position") {
|
||||
fields["position"] = pos
|
||||
}
|
||||
if showOnFront, _ := cmd.Flags().GetBool("show-on-front"); cmd.Flags().Changed("show-on-front") {
|
||||
fields["showOnFrontOfCard"] = showOnFront
|
||||
}
|
||||
if hideCompleted, _ := cmd.Flags().GetBool("hide-completed"); cmd.Flags().Changed("hide-completed") {
|
||||
fields["hideCompletedTasks"] = hideCompleted
|
||||
}
|
||||
|
||||
if len(fields) == 0 {
|
||||
return fmt.Errorf("at least one field must be specified for update")
|
||||
}
|
||||
|
||||
taskList, err := getClient().UpdateTaskList(getContext(), args[0], fields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(taskList, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
var taskListDeleteCmd = &cobra.Command{
|
||||
Use: "delete <id>",
|
||||
Short: "Delete a task list",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := getClient().DeleteTaskList(getContext(), args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return output.Print(map[string]string{"status": "deleted"}, getFormat(), os.Stdout)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(taskListCmd)
|
||||
taskListCmd.AddCommand(taskListCreateCmd)
|
||||
taskListCmd.AddCommand(taskListGetCmd)
|
||||
taskListCmd.AddCommand(taskListUpdateCmd)
|
||||
taskListCmd.AddCommand(taskListDeleteCmd)
|
||||
|
||||
taskListCreateCmd.Flags().String("card", "", "Card ID (required)")
|
||||
taskListCreateCmd.Flags().String("name", "", "Task list name (required)")
|
||||
taskListCreateCmd.Flags().Float64("position", 0, "Position")
|
||||
taskListCreateCmd.Flags().Bool("show-on-front", true, "Show on front of card")
|
||||
taskListCreateCmd.Flags().Bool("hide-completed", false, "Hide completed tasks")
|
||||
|
||||
taskListUpdateCmd.Flags().String("name", "", "Task list name")
|
||||
taskListUpdateCmd.Flags().Float64("position", 0, "Position")
|
||||
taskListUpdateCmd.Flags().Bool("show-on-front", true, "Show on front of card")
|
||||
taskListUpdateCmd.Flags().Bool("hide-completed", false, "Hide completed tasks")
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Show version information",
|
||||
Long: "Display the current version of pcli",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
version := getVersion()
|
||||
fmt.Println(version)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
}
|
||||
|
||||
func getVersion() string {
|
||||
// Try to get version from git tag
|
||||
version, err := exec.Command("git", "describe", "--tags", "--exact-match").Output()
|
||||
if err == nil && len(version) > 0 {
|
||||
return string(version)
|
||||
}
|
||||
|
||||
// Fallback to timestamp-based version
|
||||
dateCmd := exec.Command("date", "+%Y%m%d-%H%M%S")
|
||||
date, err := dateCmd.Output()
|
||||
if err != nil {
|
||||
// Ultimate fallback
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
return "v" + string(date)
|
||||
}
|
||||
Reference in New Issue
Block a user