Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,30 @@ fizzy search "bug" | jq -r '.summary'

The summary adapts to pagination flags (`--page N` or `--all`) and includes contextual details like unread counts for notifications.

### Breadcrumbs

Command responses include a `breadcrumbs` array with suggested next actions. This is designed for AI agents (and humans) to discover contextual workflows without needing to know the full CLI.

```bash
fizzy card show 42 | jq '.breadcrumbs'
```

```json
[
{"action": "comment", "cmd": "fizzy comment create --card 42 --body \"text\"", "description": "Add comment"},
{"action": "triage", "cmd": "fizzy card column 42 --column <column_id>", "description": "Move to column"},
{"action": "close", "cmd": "fizzy card close 42", "description": "Close card"},
{"action": "assign", "cmd": "fizzy card assign 42 --user <user_id>", "description": "Assign user"}
]
```

Each breadcrumb contains:
- `action`: A short identifier for the action type
- `cmd`: The complete CLI command to execute
- `description`: Human-readable description

Breadcrumbs are included by default in all responses. They are contextual - after creating a card you'll see suggestions to view, triage, or comment on it; after listing cards you'll see suggestions to show a specific card, create a new one, or search.

When creating resources, the CLI automatically follows the `Location` header to fetch the complete resource data:

```json
Expand Down
29 changes: 24 additions & 5 deletions internal/commands/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package commands

import (
"github.com/robzolkos/fizzy-cli/internal/config"
"github.com/robzolkos/fizzy-cli/internal/response"
"github.com/spf13/cobra"
)

Expand All @@ -27,10 +28,17 @@ var authLoginCmd = &cobra.Command{
exitWithError(err)
}

printSuccess(map[string]interface{}{
// Build breadcrumbs
breadcrumbs := []response.Breadcrumb{
breadcrumb("status", "fizzy auth status", "Check auth status"),
breadcrumb("identity", "fizzy identity show", "View identity"),
breadcrumb("boards", "fizzy board list", "List boards"),
}

printSuccessWithBreadcrumbs(map[string]interface{}{
"authenticated": true,
"message": "Token saved to config file",
})
}, "", breadcrumbs)
},
}

Expand All @@ -43,10 +51,15 @@ var authLogoutCmd = &cobra.Command{
exitWithError(err)
}

printSuccess(map[string]interface{}{
// Build breadcrumbs
breadcrumbs := []response.Breadcrumb{
breadcrumb("login", "fizzy auth login <token>", "Log in again"),
}

printSuccessWithBreadcrumbs(map[string]interface{}{
"authenticated": false,
"message": "Logged out successfully",
})
}, "", breadcrumbs)
},
}

Expand Down Expand Up @@ -74,7 +87,13 @@ var authStatusCmd = &cobra.Command{
}
}

printSuccess(status)
// Build breadcrumbs
breadcrumbs := []response.Breadcrumb{
breadcrumb("identity", "fizzy identity show", "View identity"),
breadcrumb("logout", "fizzy auth logout", "Log out"),
}

printSuccessWithBreadcrumbs(status, "", breadcrumbs)
},
}

Expand Down
82 changes: 74 additions & 8 deletions internal/commands/board.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package commands

import (
"fmt"
"os"

"github.com/robzolkos/fizzy-cli/internal/errors"
"github.com/robzolkos/fizzy-cli/internal/response"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -49,8 +51,23 @@ var boardListCmd = &cobra.Command{
summary += fmt.Sprintf(" (page %d)", boardListPage)
}

// Build breadcrumbs
breadcrumbs := []response.Breadcrumb{
breadcrumb("show", "fizzy board show <id>", "View board details"),
breadcrumb("cards", "fizzy card list --board <id>", "List cards on board"),
breadcrumb("columns", "fizzy column list --board <id>", "List board columns"),
}

hasNext := resp.LinkNext != ""
printSuccessWithPaginationAndSummary(resp.Data, hasNext, resp.LinkNext, summary)
if hasNext {
nextPage := boardListPage + 1
if nextPage == 0 {
nextPage = 2
}
breadcrumbs = append(breadcrumbs, breadcrumb("next", fmt.Sprintf("fizzy board list --page %d", nextPage), "Next page"))
}

printSuccessWithPaginationAndBreadcrumbs(resp.Data, hasNext, resp.LinkNext, summary, breadcrumbs)
},
}

Expand All @@ -64,8 +81,10 @@ var boardShowCmd = &cobra.Command{
exitWithError(err)
}

boardID := args[0]

client := getClient()
resp, err := client.Get("/boards/" + args[0] + ".json")
resp, err := client.Get("/boards/" + boardID + ".json")
if err != nil {
exitWithError(err)
}
Expand All @@ -78,7 +97,14 @@ var boardShowCmd = &cobra.Command{
}
}

printSuccessWithSummary(resp.Data, summary)
// Build breadcrumbs
breadcrumbs := []response.Breadcrumb{
breadcrumb("cards", fmt.Sprintf("fizzy card list --board %s", boardID), "List cards"),
breadcrumb("columns", fmt.Sprintf("fizzy column list --board %s", boardID), "List columns"),
breadcrumb("create-card", fmt.Sprintf("fizzy card create --board %s --title \"title\"", boardID), "Create card"),
}

printSuccessWithBreadcrumbs(resp.Data, summary, breadcrumbs)
},
}

Expand Down Expand Up @@ -125,7 +151,33 @@ var boardCreateCmd = &cobra.Command{
if resp.Location != "" {
followResp, err := client.FollowLocation(resp.Location)
if err == nil && followResp != nil {
printSuccessWithLocation(followResp.Data, resp.Location)
// Extract board ID from response
boardID := ""
if board, ok := followResp.Data.(map[string]interface{}); ok {
if id, ok := board["id"].(string); ok {
boardID = id
}
}

// Build breadcrumbs
var breadcrumbs []response.Breadcrumb
if boardID != "" {
breadcrumbs = []response.Breadcrumb{
breadcrumb("show", fmt.Sprintf("fizzy board show %s", boardID), "View board details"),
breadcrumb("cards", fmt.Sprintf("fizzy card list --board %s", boardID), "List cards"),
breadcrumb("columns", fmt.Sprintf("fizzy column list --board %s", boardID), "List columns"),
}
}

respObj := response.SuccessWithBreadcrumbs(followResp.Data, "", breadcrumbs)
respObj.Location = resp.Location
if lastResult != nil {
lastResult.Response = respObj
lastResult.ExitCode = 0
panic(testExitSignal{})
}
respObj.Print()
os.Exit(0)
return
}
// If follow fails, just return success with location
Expand All @@ -152,6 +204,8 @@ var boardUpdateCmd = &cobra.Command{
exitWithError(err)
}

boardID := args[0]

boardParams := make(map[string]interface{})

if boardUpdateName != "" {
Expand All @@ -169,12 +223,18 @@ var boardUpdateCmd = &cobra.Command{
}

client := getClient()
resp, err := client.Patch("/boards/"+args[0]+".json", body)
resp, err := client.Patch("/boards/"+boardID+".json", body)
if err != nil {
exitWithError(err)
}

printSuccess(resp.Data)
// Build breadcrumbs
breadcrumbs := []response.Breadcrumb{
breadcrumb("show", fmt.Sprintf("fizzy board show %s", boardID), "View board"),
breadcrumb("cards", fmt.Sprintf("fizzy card list --board %s", boardID), "List cards"),
}

printSuccessWithBreadcrumbs(resp.Data, "", breadcrumbs)
},
}

Expand All @@ -194,9 +254,15 @@ var boardDeleteCmd = &cobra.Command{
exitWithError(err)
}

printSuccess(map[string]interface{}{
// Build breadcrumbs
breadcrumbs := []response.Breadcrumb{
breadcrumb("boards", "fizzy board list", "List boards"),
breadcrumb("create", "fizzy board create --name \"name\"", "Create new board"),
}

printSuccessWithBreadcrumbs(map[string]interface{}{
"deleted": true,
})
}, "", breadcrumbs)
},
}

Expand Down
Loading