Files
pcli/openspec/specs/api-client/spec.md
T
Steve Cliff 7937266262 feat(status): add --project flag for filtering boards by project name
- Implemented the --project flag in the pcli status command to filter boards based on the specified project name.
- Updated the command to resolve project names to IDs using case-insensitive matching.
- Adjusted the totalBoards count in the output to reflect the number of boards matching the project filter.
- Enhanced command help text and README documentation to include usage examples for the new flag.
- Verified functionality through manual testing and ensured default behavior remains unchanged when the flag is omitted.

feat(board): expand GetBoard response to include labels and card associations

- Modified the Board struct to include Labels, CardLabels, and CardMemberships fields.
- Updated the GetBoard method to parse additional fields from the API response.
- Enhanced ListCardsByBoard to include label names for each card based on the enriched board data.
- Ensured backward compatibility by making new fields optional and preserving existing output structure.
2026-02-18 21:38:41 +00:00

13 KiB

ADDED Requirements

Requirement: Base HTTP client

The system SHALL provide a base HTTP client that sends requests to the Planka API. The client SHALL construct URLs by joining the configured base URL with the API path. The client SHALL attach an x-api-key: <key> header to every request. The client SHALL send and receive JSON (Content-Type: application/json). The client SHALL accept a *slog.Logger and log every request at DEBUG level with method, path, status code, and duration. The client SHALL log errors at WARN level.

Scenario: Successful API request

  • WHEN the client sends a request to a valid endpoint
  • THEN the response body SHALL be returned as parsed JSON
  • AND the request SHALL include the x-api-key header
  • AND a DEBUG log entry SHALL be emitted with method, path, status, and duration

Scenario: API returns error status

  • WHEN the API responds with a 4xx or 5xx status code
  • THEN the client SHALL return an APIError containing the HTTP status code and response message
  • AND a WARN log entry SHALL be emitted

Scenario: Network failure

  • WHEN the HTTP request fails due to a network error (connection refused, timeout, DNS failure)
  • THEN the client SHALL return a Go error wrapping the underlying network error

Requirement: Authentication from environment

The system SHALL read PLANKA_URL from the environment to determine the API base URL. The system SHALL read PLANKA_API_KEY from the environment to determine the API key. Global flags --url and --api-key SHALL override the corresponding environment variables. Flag values SHALL take precedence over environment variables.

Scenario: Auth from environment variables

  • WHEN PLANKA_URL and PLANKA_API_KEY are set in the environment
  • AND no --url or --api-key flags are provided
  • THEN the client SHALL use the environment variable values

Scenario: Flag overrides environment

  • WHEN --url or --api-key flags are provided
  • THEN the flag values SHALL take precedence over environment variables

Scenario: Missing configuration

  • WHEN neither the environment variable nor the flag is set for URL or API key
  • THEN the system SHALL print an error message and exit with code 1

Requirement: Cursor-based pagination

The system SHALL implement cursor-based pagination for all list endpoints that support it. Paginated endpoints are: GET /boards/{boardId}/actions (beforeId), GET /cards/{cardId}/actions (beforeId), GET /lists/{listId}/cards (before), GET /cards/{cardId}/comments (beforeId). List methods SHALL accept a limit parameter. When limit is 0, the client SHALL fetch all pages. When limit is greater than 0, the client SHALL stop fetching after accumulating at least limit items and truncate the result to exactly limit items. Each page fetch SHALL be logged at DEBUG level.

Scenario: Fetch all pages

  • WHEN a list method is called with limit 0
  • THEN the client SHALL fetch pages until an empty page is returned
  • AND all items from all pages SHALL be returned

Scenario: Fetch with limit

  • WHEN a list method is called with limit N (N > 0)
  • THEN the client SHALL stop fetching after accumulating N or more items
  • AND the returned slice SHALL contain exactly N items

Scenario: Single page result

  • WHEN the API returns fewer items than a page size and no more pages exist
  • THEN the client SHALL return those items without making additional requests

Requirement: Project operations

The client SHALL provide methods to list all accessible projects (GET /projects), get a single project by ID (GET /projects/{id}), create a project (POST /projects), and delete a project (DELETE /projects/{id}).

Scenario: List projects

  • WHEN ListProjects is called
  • THEN the client SHALL send GET /projects and return a slice of Project models

Scenario: Get project

  • WHEN GetProject is called with a project ID
  • THEN the client SHALL send GET /projects/{id} and return a Project model

Scenario: Create project

  • WHEN CreateProject is called with project fields (type, name, description)
  • THEN the client SHALL send POST /projects with the provided fields and return the created Project

Scenario: Delete project

  • WHEN DeleteProject is called with a project ID
  • THEN the client SHALL send DELETE /projects/{id}

Requirement: Board operations

The client SHALL provide a method to get a single board by ID (GET /boards/{id}), list board actions (GET /boards/{boardId}/actions) with pagination support, create a board (POST /projects/{projectId}/boards), and delete a board (DELETE /boards/{id}). GetBoard SHALL parse the included object from the response and populate the Board model with lists, cards, labels, cardLabels, and cardMemberships.

Scenario: Get board

  • WHEN GetBoard is called with a board ID
  • THEN the client SHALL send GET /boards/{id} and return a Board model including its included lists, cards, labels, cardLabels, and cardMemberships

Scenario: Get board includes labels

  • WHEN GetBoard is called and the board has labels defined
  • THEN the returned Board SHALL contain a Labels slice with all board labels

Scenario: Get board includes card-label associations

  • WHEN GetBoard is called and cards on the board have labels attached
  • THEN the returned Board SHALL contain a CardLabels slice with all card-label associations
  • AND each CardLabel entry SHALL contain cardId and labelId fields

Scenario: Get board includes card memberships

  • WHEN GetBoard is called and cards on the board have members assigned
  • THEN the returned Board SHALL contain a CardMemberships slice with all card-membership associations

Scenario: List board actions

  • WHEN ListBoardActions is called with a board ID and limit
  • THEN the client SHALL send paginated GET /boards/{boardId}/actions requests and return a slice of Action models

Scenario: Create board

  • WHEN CreateBoard is called with a project ID and board fields (name, position)
  • THEN the client SHALL send POST /projects/{projectId}/boards with the provided fields and return the created Board

Scenario: Delete board

  • WHEN DeleteBoard is called with a board ID
  • THEN the client SHALL send DELETE /boards/{id}

Requirement: Card CRUD operations

The client SHALL provide methods for: get card (GET /cards/{id}), create card (POST /lists/{listId}/cards), update card (PATCH /cards/{id}), delete card (DELETE /cards/{id}), and duplicate card (POST /cards/{id}/duplicate). The client SHALL provide a method to list cards in a list (GET /lists/{listId}/cards) with pagination support. The client SHALL provide a method to list card actions (GET /cards/{cardId}/actions) with pagination support.

Scenario: Get card

  • WHEN GetCard is called with a card ID
  • THEN the client SHALL send GET /cards/{id} and return a Card model

Scenario: Create card

  • WHEN CreateCard is called with a list ID and card fields (name, description, type, position, dueDate, isDueCompleted)
  • THEN the client SHALL send POST /lists/{listId}/cards with the provided fields and return the created Card

Scenario: Update card

  • WHEN UpdateCard is called with a card ID and update fields
  • THEN the client SHALL send PATCH /cards/{id} with only the provided fields and return the updated Card

Scenario: Delete card

  • WHEN DeleteCard is called with a card ID
  • THEN the client SHALL send DELETE /cards/{id}

Scenario: Duplicate card

  • WHEN DuplicateCard is called with a card ID, name, and position
  • THEN the client SHALL send POST /cards/{id}/duplicate and return the new Card

Scenario: List cards in list

  • WHEN ListCards is called with a list ID and limit
  • THEN the client SHALL send paginated GET /lists/{listId}/cards requests and return a slice of Card models

Scenario: List card actions

  • WHEN ListCardActions is called with a card ID and limit
  • THEN the client SHALL send paginated GET /cards/{cardId}/actions requests and return a slice of Action models

Requirement: Comment operations

The client SHALL provide methods for: list comments (GET /cards/{cardId}/comments) with pagination, create comment (POST /cards/{cardId}/comments), update comment (PATCH /comments/{id}), and delete comment (DELETE /comments/{id}).

Scenario: List comments

  • WHEN ListComments is called with a card ID and limit
  • THEN the client SHALL send paginated GET /cards/{cardId}/comments requests and return a slice of Comment models

Scenario: Create comment

  • WHEN CreateComment is called with a card ID and text
  • THEN the client SHALL send POST /cards/{cardId}/comments with the text and return the created Comment

Scenario: Update comment

  • WHEN UpdateComment is called with a comment ID and text
  • THEN the client SHALL send PATCH /comments/{id} and return the updated Comment

Scenario: Delete comment

  • WHEN DeleteComment is called with a comment ID
  • THEN the client SHALL send DELETE /comments/{id}

Requirement: Task list operations

The client SHALL provide methods for: create task list (POST /cards/{cardId}/task-lists), get task list (GET /task-lists/{id}), update task list (PATCH /task-lists/{id}), and delete task list (DELETE /task-lists/{id}).

Scenario: Create task list

  • WHEN CreateTaskList is called with a card ID and fields (name, position, showOnFrontOfCard, hideCompletedTasks)
  • THEN the client SHALL send POST /cards/{cardId}/task-lists and return the created TaskList

Scenario: Get task list

  • WHEN GetTaskList is called with a task list ID
  • THEN the client SHALL send GET /task-lists/{id} and return a TaskList model

Scenario: Update task list

  • WHEN UpdateTaskList is called with a task list ID and update fields
  • THEN the client SHALL send PATCH /task-lists/{id} and return the updated TaskList

Scenario: Delete task list

  • WHEN DeleteTaskList is called with a task list ID
  • THEN the client SHALL send DELETE /task-lists/{id}

Requirement: Task operations

The client SHALL provide methods for: create task (POST /task-lists/{taskListId}/tasks), update task (PATCH /tasks/{id}), and delete task (DELETE /tasks/{id}).

Scenario: Create task

  • WHEN CreateTask is called with a task list ID and fields (name, position, isCompleted, assigneeUserId, linkedCardId)
  • THEN the client SHALL send POST /task-lists/{taskListId}/tasks and return the created Task

Scenario: Update task

  • WHEN UpdateTask is called with a task ID and update fields (name, position, isCompleted, assigneeUserId, taskListId)
  • THEN the client SHALL send PATCH /tasks/{id} and return the updated Task

Scenario: Delete task

  • WHEN DeleteTask is called with a task ID
  • THEN the client SHALL send DELETE /tasks/{id}

Requirement: Label operations

The client SHALL provide methods for: create label (POST /boards/{boardId}/labels), update label (PATCH /labels/{id}), and delete label (DELETE /labels/{id}).

Scenario: Create label

  • WHEN CreateLabel is called with a board ID and fields (name, color, position)
  • THEN the client SHALL send POST /boards/{boardId}/labels and return the created Label

Scenario: Update label

  • WHEN UpdateLabel is called with a label ID and update fields (name, color, position)
  • THEN the client SHALL send PATCH /labels/{id} and return the updated Label

Scenario: Delete label

  • WHEN DeleteLabel is called with a label ID
  • THEN the client SHALL send DELETE /labels/{id}

Requirement: Card label operations

The client SHALL provide methods for: add label to card (POST /cards/{cardId}/card-labels) and remove label from card (DELETE /cards/{cardId}/card-labels/labelId:{labelId}).

Scenario: Add label to card

  • WHEN AddCardLabel is called with a card ID and label ID
  • THEN the client SHALL send POST /cards/{cardId}/card-labels with the label ID

Scenario: Remove label from card

  • WHEN RemoveCardLabel is called with a card ID and label ID
  • THEN the client SHALL send DELETE /cards/{cardId}/card-labels/labelId:{labelId}

Requirement: Card membership operations

The client SHALL provide methods for: assign user to card (POST /cards/{cardId}/card-memberships) and unassign user from card (DELETE /cards/{cardId}/card-memberships/userId:{userId}).

Scenario: Assign user to card

  • WHEN AddCardMember is called with a card ID and user ID
  • THEN the client SHALL send POST /cards/{cardId}/card-memberships with the user ID

Scenario: Unassign user from card

  • WHEN RemoveCardMember is called with a card ID and user ID
  • THEN the client SHALL send DELETE /cards/{cardId}/card-memberships/userId:{userId}