Files
Steve Cliff 22d5848e1a feat: Add openspec-sync-specs and openspec-verify-change skills
- Introduced `openspec-sync-specs` skill to sync delta specs to main specs, allowing intelligent merging of requirements.
- Added `openspec-verify-change` skill to verify implementation against change artifacts, ensuring completeness, correctness, and coherence before archiving.

docs: Create CLAUDE.md for project guidance

- Added CLAUDE.md to provide an overview of the PCLI project, including build, test commands, architecture, and resource addition guidelines.

chore: Add new change and design documents for project filter in status command

- Created `.openspec.yaml`, `design.md`, `proposal.md`, and `tasks.md` for the `add-project-filter-to-status` change.
- Updated specs for CLI commands and status command to include project filtering functionality.

feat: Expand board included parsing in API client

- Added parsing for `labels`, `cardLabels`, and `cardMemberships` in the `GetBoard` response.
- Updated `ListCardsByBoard` to enrich card output with label names, enhancing usability in kanban sync workflows.
2026-02-18 21:27:02 +00:00

133 lines
3.3 KiB
Go

package client
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"git.franklin.lab/steve.cliff/pcli/model"
)
// BoardCreateFields represents the fields required to create a board
type BoardCreateFields struct {
Name string `json:"name"`
Position float64 `json:"position"`
}
func (c *Client) ListBoards(ctx context.Context) ([]model.Board, error) {
data, err := c.DoNoBody(ctx, "GET", "/api/projects")
if err != nil {
return nil, err
}
var response struct {
Included struct {
Boards []model.Board `json:"boards"`
} `json:"included"`
}
if err := json.Unmarshal(data, &response); err != nil {
return nil, fmt.Errorf("failed to unmarshal projects response: %w", err)
}
return response.Included.Boards, nil
}
func (c *Client) GetBoard(ctx context.Context, id string) (*model.Board, error) {
data, err := c.DoNoBody(ctx, "GET", fmt.Sprintf("/api/boards/%s", id))
if err != nil {
return nil, err
}
var response struct {
Item model.Board `json:"item"`
Included struct {
Lists []model.List `json:"lists"`
Cards []model.Card `json:"cards"`
Labels []model.Label `json:"labels"`
CardLabels []model.CardLabel `json:"cardLabels"`
CardMemberships []model.CardMembership `json:"cardMemberships"`
} `json:"included"`
}
if err := json.Unmarshal(data, &response); err != nil {
return nil, fmt.Errorf("failed to unmarshal board response: %w", err)
}
response.Item.Lists = response.Included.Lists
response.Item.Cards = response.Included.Cards
response.Item.Labels = response.Included.Labels
response.Item.CardLabels = response.Included.CardLabels
response.Item.CardMemberships = response.Included.CardMemberships
return &response.Item, nil
}
func (c *Client) ListBoardActions(ctx context.Context, boardId string, limit int) ([]model.Action, error) {
var all []model.Action
var beforeId string
for {
path := fmt.Sprintf("/api/boards/%s/actions", boardId)
if beforeId != "" {
path = fmt.Sprintf("%s?beforeId=%s", path, beforeId)
}
c.Logger.Debug("Fetching board actions page",
slog.String("boardId", boardId),
slog.String("beforeId", beforeId),
)
data, err := c.DoNoBody(ctx, "GET", path)
if err != nil {
return nil, err
}
var response struct {
Items []model.Action `json:"items"`
}
if err := json.Unmarshal(data, &response); err != nil {
return nil, fmt.Errorf("failed to unmarshal board actions response: %w", err)
}
if len(response.Items) == 0 {
break
}
all = append(all, response.Items...)
if limit > 0 && len(all) >= limit {
all = all[:limit]
break
}
beforeId = response.Items[len(response.Items)-1].ID
}
return all, nil
}
func (c *Client) CreateBoard(ctx context.Context, projectId string, fields BoardCreateFields) (*model.Board, error) {
data, err := c.Do(ctx, "POST", fmt.Sprintf("/api/projects/%s/boards", projectId), fields)
if err != nil {
return nil, err
}
var response struct {
Item model.Board `json:"item"`
}
if err := json.Unmarshal(data, &response); err != nil {
return nil, fmt.Errorf("failed to unmarshal board response: %w", err)
}
return &response.Item, nil
}
func (c *Client) DeleteBoard(ctx context.Context, id string) error {
_, err := c.DoNoBody(ctx, "DELETE", fmt.Sprintf("/api/boards/%s", id))
return err
}