Released v1

This commit is contained in:
Steve Cliff
2026-02-12 10:37:19 +00:00
commit b07572fed5
77 changed files with 19518 additions and 0 deletions
+66
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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")
}
+47
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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")
}
+133
View File
@@ -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")
}
+40
View File
@@ -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)
}