## 1. Project Scaffolding - [x] 1.1 Initialize Go module (`go mod init github.com/dcgsteve/pcli`) - [x] 1.2 Create directory structure: `cmd/`, `client/`, `model/`, `output/`, `logging/` - [x] 1.3 Add Cobra dependency (`go get github.com/spf13/cobra`) - [x] 1.4 Create `main.go` entry point that calls `cmd.Execute()` ## 2. Logging - [x] 2.1 Implement `logging/logging.go` — `NewLogger(level string) *slog.Logger` that parses level string, creates JSON handler writing to stderr ## 3. Model Types - [x] 3.1 Define `model/types.go` — Project, Board, List, Card, Comment, TaskList, Task, Label, Action, CardLabel, CardMembership structs with JSON tags and pointer types for nullable fields - [x] 3.2 Define `Envelope` struct (`Data any`, `Error *string`) in `model/types.go` - [x] 3.3 Define `APIError` struct (StatusCode int, Message string) implementing the `error` interface ## 4. Output Formatting - [x] 4.1 Implement `output/output.go` — `Print(data any, format string, w io.Writer)` function that switches on format - [x] 4.2 Implement JSON output mode — marshal `Envelope{Data: data, Error: nil}` to stdout - [x] 4.3 Implement error output — `PrintError(err error, format string, w io.Writer)` that writes `Envelope{Data: nil, Error: msg}` for JSON, or error to stderr for table - [x] 4.4 Implement table output mode — tabwriter-based rendering with per-resource column definitions ## 5. Base HTTP Client - [x] 5.1 Implement `client/client.go` — `Client` struct with BaseURL, Token, HTTPClient, Logger fields - [x] 5.2 Implement `NewClient(baseURL, token string, logger *slog.Logger) *Client` - [x] 5.3 Implement `Do(ctx, method, path string, body any) (json.RawMessage, error)` — URL construction, bearer header, JSON marshal/unmarshal, DEBUG logging with method/path/status/duration - [x] 5.4 Implement `APIError` handling — map 4xx/5xx responses to APIError, WARN logging on errors - [x] 5.5 Implement `DoNoBody(ctx, method, path string) (json.RawMessage, error)` convenience method for GET/DELETE ## 6. Root Command and Global Flags - [x] 6.1 Implement `cmd/root.go` — root Cobra command with `--format`, `--url`, `--token`, `--log-level` persistent flags - [x] 6.2 Implement `PersistentPreRunE` — resolve URL/token from flags or env vars (flag > env), validate both present, initialize logger, create client instance - [x] 6.3 Wire output format into a shared variable accessible by all subcommands ## 7. Client — Project Operations - [x] 7.1 Implement `client/projects.go` — `ListProjects(ctx) ([]model.Project, error)` - [x] 7.2 Implement `GetProject(ctx, id string) (*model.Project, error)` ## 8. Client — Board Operations - [x] 8.1 Implement `client/boards.go` — `GetBoard(ctx, id string) (*model.Board, error)` - [x] 8.2 Implement `ListBoardActions(ctx, boardId string, limit int) ([]model.Action, error)` with cursor-based pagination using `beforeId` ## 9. Client — Card Operations - [x] 9.1 Implement `client/cards.go` — `GetCard(ctx, id string) (*model.Card, error)` - [x] 9.2 Implement `CreateCard(ctx, listId string, fields) (*model.Card, error)` - [x] 9.3 Implement `UpdateCard(ctx, id string, fields) (*model.Card, error)` - [x] 9.4 Implement `DeleteCard(ctx, id string) error` - [x] 9.5 Implement `DuplicateCard(ctx, id string, name *string, position *float64) (*model.Card, error)` - [x] 9.6 Implement `ListCards(ctx, listId string, limit int) ([]model.Card, error)` with cursor-based pagination using `before` - [x] 9.7 Implement `ListCardActions(ctx, cardId string, limit int) ([]model.Action, error)` with cursor-based pagination using `beforeId` - [x] 9.8 Implement `ListCardsByBoard(ctx, boardId string, limit int) ([]model.CardWithList, error)` — multi-call enrichment: get board → get cards per list → inject listName - [x] 9.9 Implement `AddCardLabel(ctx, cardId, labelId string) error` - [x] 9.10 Implement `RemoveCardLabel(ctx, cardId, labelId string) error` - [x] 9.11 Implement `AddCardMember(ctx, cardId, userId string) error` - [x] 9.12 Implement `RemoveCardMember(ctx, cardId, userId string) error` ## 10. Client — Comment Operations - [x] 10.1 Implement `client/comments.go` — `ListComments(ctx, cardId string, limit int) ([]model.Comment, error)` with cursor-based pagination using `beforeId` - [x] 10.2 Implement `CreateComment(ctx, cardId, text string) (*model.Comment, error)` - [x] 10.3 Implement `UpdateComment(ctx, id, text string) (*model.Comment, error)` - [x] 10.4 Implement `DeleteComment(ctx, id string) error` ## 11. Client — Task List Operations - [x] 11.1 Implement `client/task_lists.go` — `CreateTaskList(ctx, cardId string, fields) (*model.TaskList, error)` - [x] 11.2 Implement `GetTaskList(ctx, id string) (*model.TaskList, error)` - [x] 11.3 Implement `UpdateTaskList(ctx, id string, fields) (*model.TaskList, error)` - [x] 11.4 Implement `DeleteTaskList(ctx, id string) error` ## 12. Client — Task Operations - [x] 12.1 Implement `client/tasks.go` — `CreateTask(ctx, taskListId string, fields) (*model.Task, error)` - [x] 12.2 Implement `UpdateTask(ctx, id string, fields) (*model.Task, error)` - [x] 12.3 Implement `DeleteTask(ctx, id string) error` ## 13. Client — Label Operations - [x] 13.1 Implement `client/labels.go` — `CreateLabel(ctx, boardId string, fields) (*model.Label, error)` - [x] 13.2 Implement `UpdateLabel(ctx, id string, fields) (*model.Label, error)` - [x] 13.3 Implement `DeleteLabel(ctx, id string) error` ## 14. CLI — Project Commands - [x] 14.1 Implement `cmd/project.go` — `project` parent command, `project list` subcommand, `project get ` subcommand ## 15. CLI — Board Commands - [x] 15.1 Implement `cmd/board.go` — `board` parent command, `board get ` subcommand, `board actions ` subcommand with `--limit` flag ## 16. CLI — Card Commands - [x] 16.1 Implement `cmd/card.go` — `card` parent command - [x] 16.2 Implement `card list` subcommand with mutually required `--board` / `--list` flags and `--limit` - [x] 16.3 Implement `card get ` subcommand - [x] 16.4 Implement `card create` subcommand with `--list`, `--name` (required), `--description`, `--type`, `--position`, `--due-date`, `--due-completed` flags - [x] 16.5 Implement `card update ` subcommand with optional update flags - [x] 16.6 Implement `card delete ` subcommand - [x] 16.7 Implement `card duplicate ` subcommand with optional `--name`, `--position` - [x] 16.8 Implement `card move ` subcommand with required `--list` and optional `--position` - [x] 16.9 Implement `card assign ` and `card unassign ` subcommands with required `--user` - [x] 16.10 Implement `card add-label ` and `card remove-label ` subcommands with required `--label` - [x] 16.11 Implement `card actions ` subcommand with `--limit` ## 17. CLI — Comment Commands - [x] 17.1 Implement `cmd/comment.go` — `comment` parent command, `comment list` with `--card` and `--limit`, `comment create` with `--card` and `--text`, `comment update ` with `--text`, `comment delete ` ## 18. CLI — Task List Commands - [x] 18.1 Implement `cmd/task_list.go` — `task-list` parent command, `task-list create` with `--card`, `--name`, optional flags, `task-list get `, `task-list update `, `task-list delete ` ## 19. CLI — Task Commands - [x] 19.1 Implement `cmd/task.go` — `task` parent command, `task create` with `--task-list`, `--name`, optional flags, `task update ` with optional flags, `task delete ` ## 20. CLI — Label Commands - [x] 20.1 Implement `cmd/label.go` — `label` parent command, `label create` with `--board`, `--name`, optional `--color`, `--position`, `label update `, `label delete ` ## 21. Build and Verify - [x] 21.1 Verify `go build` produces a clean binary - [x] 21.2 Verify `pcli --help` displays all commands and global flags - [x] 21.3 Verify error output when `PLANKA_URL` / `PLANKA_TOKEN` are missing - [x] 21.4 Add README.md with installation, configuration, and usage examples