Skip to content

Commit 7d54448

Browse files
committed
Add personal access token commands
Adds `fizzy token list|create|delete` backed by AccessTokensService on the root SDK client. Covers the three SDK methods that previously had no CLI surface (CreateAccessToken, ListAccessTokens, DeleteAccessToken). Includes an e2e CRUD test mirroring the webhook pattern, and the regenerated SURFACE.txt snapshot.
1 parent bff7af2 commit 7d54448

4 files changed

Lines changed: 298 additions & 0 deletions

File tree

SURFACE.txt

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ ARG fizzy signup help 00 [command]
2424
ARG fizzy skill help 00 [command]
2525
ARG fizzy step help 00 [command]
2626
ARG fizzy tag help 00 [command]
27+
ARG fizzy token help 00 [command]
2728
ARG fizzy upload help 00 [command]
2829
ARG fizzy user help 00 [command]
2930
ARG fizzy webhook help 00 [command]
@@ -194,6 +195,13 @@ CMD fizzy tag
194195
CMD fizzy tag help
195196
CMD fizzy tag list
196197
CMD fizzy tag ls
198+
CMD fizzy token
199+
CMD fizzy token create
200+
CMD fizzy token delete
201+
CMD fizzy token help
202+
CMD fizzy token list
203+
CMD fizzy token ls
204+
CMD fizzy token rm
197205
CMD fizzy upload
198206
CMD fizzy upload file
199207
CMD fizzy upload help
@@ -2737,6 +2745,106 @@ FLAG fizzy tag ls --quiet type=bool
27372745
FLAG fizzy tag ls --styled type=bool
27382746
FLAG fizzy tag ls --token type=string
27392747
FLAG fizzy tag ls --verbose type=bool
2748+
FLAG fizzy token --agent type=bool
2749+
FLAG fizzy token --api-url type=string
2750+
FLAG fizzy token --count type=bool
2751+
FLAG fizzy token --help type=bool
2752+
FLAG fizzy token --ids-only type=bool
2753+
FLAG fizzy token --jq type=string
2754+
FLAG fizzy token --json type=bool
2755+
FLAG fizzy token --limit type=int
2756+
FLAG fizzy token --markdown type=bool
2757+
FLAG fizzy token --profile type=string
2758+
FLAG fizzy token --quiet type=bool
2759+
FLAG fizzy token --styled type=bool
2760+
FLAG fizzy token --token type=string
2761+
FLAG fizzy token --verbose type=bool
2762+
FLAG fizzy token create --agent type=bool
2763+
FLAG fizzy token create --api-url type=string
2764+
FLAG fizzy token create --count type=bool
2765+
FLAG fizzy token create --description type=string
2766+
FLAG fizzy token create --help type=bool
2767+
FLAG fizzy token create --ids-only type=bool
2768+
FLAG fizzy token create --jq type=string
2769+
FLAG fizzy token create --json type=bool
2770+
FLAG fizzy token create --limit type=int
2771+
FLAG fizzy token create --markdown type=bool
2772+
FLAG fizzy token create --permission type=string
2773+
FLAG fizzy token create --profile type=string
2774+
FLAG fizzy token create --quiet type=bool
2775+
FLAG fizzy token create --styled type=bool
2776+
FLAG fizzy token create --token type=string
2777+
FLAG fizzy token create --verbose type=bool
2778+
FLAG fizzy token delete --agent type=bool
2779+
FLAG fizzy token delete --api-url type=string
2780+
FLAG fizzy token delete --count type=bool
2781+
FLAG fizzy token delete --help type=bool
2782+
FLAG fizzy token delete --ids-only type=bool
2783+
FLAG fizzy token delete --jq type=string
2784+
FLAG fizzy token delete --json type=bool
2785+
FLAG fizzy token delete --limit type=int
2786+
FLAG fizzy token delete --markdown type=bool
2787+
FLAG fizzy token delete --profile type=string
2788+
FLAG fizzy token delete --quiet type=bool
2789+
FLAG fizzy token delete --styled type=bool
2790+
FLAG fizzy token delete --token type=string
2791+
FLAG fizzy token delete --verbose type=bool
2792+
FLAG fizzy token help --agent type=bool
2793+
FLAG fizzy token help --api-url type=string
2794+
FLAG fizzy token help --count type=bool
2795+
FLAG fizzy token help --help type=bool
2796+
FLAG fizzy token help --ids-only type=bool
2797+
FLAG fizzy token help --jq type=string
2798+
FLAG fizzy token help --json type=bool
2799+
FLAG fizzy token help --limit type=int
2800+
FLAG fizzy token help --markdown type=bool
2801+
FLAG fizzy token help --profile type=string
2802+
FLAG fizzy token help --quiet type=bool
2803+
FLAG fizzy token help --styled type=bool
2804+
FLAG fizzy token help --token type=string
2805+
FLAG fizzy token help --verbose type=bool
2806+
FLAG fizzy token list --agent type=bool
2807+
FLAG fizzy token list --api-url type=string
2808+
FLAG fizzy token list --count type=bool
2809+
FLAG fizzy token list --help type=bool
2810+
FLAG fizzy token list --ids-only type=bool
2811+
FLAG fizzy token list --jq type=string
2812+
FLAG fizzy token list --json type=bool
2813+
FLAG fizzy token list --limit type=int
2814+
FLAG fizzy token list --markdown type=bool
2815+
FLAG fizzy token list --profile type=string
2816+
FLAG fizzy token list --quiet type=bool
2817+
FLAG fizzy token list --styled type=bool
2818+
FLAG fizzy token list --token type=string
2819+
FLAG fizzy token list --verbose type=bool
2820+
FLAG fizzy token ls --agent type=bool
2821+
FLAG fizzy token ls --api-url type=string
2822+
FLAG fizzy token ls --count type=bool
2823+
FLAG fizzy token ls --help type=bool
2824+
FLAG fizzy token ls --ids-only type=bool
2825+
FLAG fizzy token ls --jq type=string
2826+
FLAG fizzy token ls --json type=bool
2827+
FLAG fizzy token ls --limit type=int
2828+
FLAG fizzy token ls --markdown type=bool
2829+
FLAG fizzy token ls --profile type=string
2830+
FLAG fizzy token ls --quiet type=bool
2831+
FLAG fizzy token ls --styled type=bool
2832+
FLAG fizzy token ls --token type=string
2833+
FLAG fizzy token ls --verbose type=bool
2834+
FLAG fizzy token rm --agent type=bool
2835+
FLAG fizzy token rm --api-url type=string
2836+
FLAG fizzy token rm --count type=bool
2837+
FLAG fizzy token rm --help type=bool
2838+
FLAG fizzy token rm --ids-only type=bool
2839+
FLAG fizzy token rm --jq type=string
2840+
FLAG fizzy token rm --json type=bool
2841+
FLAG fizzy token rm --limit type=int
2842+
FLAG fizzy token rm --markdown type=bool
2843+
FLAG fizzy token rm --profile type=string
2844+
FLAG fizzy token rm --quiet type=bool
2845+
FLAG fizzy token rm --styled type=bool
2846+
FLAG fizzy token rm --token type=string
2847+
FLAG fizzy token rm --verbose type=bool
27402848
FLAG fizzy upload --agent type=bool
27412849
FLAG fizzy upload --api-url type=string
27422850
FLAG fizzy upload --count type=bool
@@ -3385,6 +3493,13 @@ SUB fizzy tag
33853493
SUB fizzy tag help
33863494
SUB fizzy tag list
33873495
SUB fizzy tag ls
3496+
SUB fizzy token
3497+
SUB fizzy token create
3498+
SUB fizzy token delete
3499+
SUB fizzy token help
3500+
SUB fizzy token list
3501+
SUB fizzy token ls
3502+
SUB fizzy token rm
33883503
SUB fizzy upload
33893504
SUB fizzy upload file
33903505
SUB fizzy upload help

e2e/cli_tests/token_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package clitests
2+
3+
import (
4+
"strconv"
5+
"testing"
6+
"time"
7+
)
8+
9+
func TestAccessTokenCRUD(t *testing.T) {
10+
h := newHarness(t)
11+
description := "CLI Test Token " + strconv.FormatInt(time.Now().UnixNano(), 10)
12+
13+
create := h.Run("token", "create", "--description", description, "--permission", "read")
14+
assertOK(t, create)
15+
tokenID := create.GetDataString("id")
16+
if tokenID == "" {
17+
t.Fatal("no token ID in create response")
18+
}
19+
if create.GetDataString("token") == "" {
20+
t.Fatal("expected raw token value in create response")
21+
}
22+
deleted := false
23+
t.Cleanup(func() {
24+
if !deleted {
25+
newHarness(t).Run("token", "delete", tokenID)
26+
}
27+
})
28+
29+
list := h.Run("token", "list")
30+
assertOK(t, list)
31+
found := false
32+
for _, item := range list.GetDataArray() {
33+
if mapValueString(asMap(item), "id") == tokenID {
34+
found = true
35+
break
36+
}
37+
}
38+
if !found {
39+
t.Fatalf("expected token list to include %q", tokenID)
40+
}
41+
42+
deleteResult := h.Run("token", "delete", tokenID)
43+
assertOK(t, deleteResult)
44+
deleted = true
45+
if !deleteResult.GetDataBool("deleted") {
46+
t.Fatal("expected deleted=true")
47+
}
48+
}

internal/commands/columns.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,11 @@ var (
9191
{Header: "Created", Field: "created_at"},
9292
{Header: "Updated", Field: "updated_at"},
9393
}
94+
95+
tokenColumns = render.Columns{
96+
{Header: "ID", Field: "id"},
97+
{Header: "Description", Field: "description"},
98+
{Header: "Permission", Field: "permission"},
99+
{Header: "Created", Field: "created_at"},
100+
}
94101
)

internal/commands/token.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package commands
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/basecamp/fizzy-sdk/go/pkg/generated"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
var tokenCmd = &cobra.Command{
11+
Use: "token",
12+
Short: "Manage personal access tokens",
13+
Long: "Commands for managing your personal access tokens.",
14+
}
15+
16+
var tokenListCmd = &cobra.Command{
17+
Use: "list",
18+
Short: "List personal access tokens",
19+
Long: "Lists your personal access tokens.",
20+
RunE: func(cmd *cobra.Command, args []string) error {
21+
if err := requireAuthAndAccount(); err != nil {
22+
return err
23+
}
24+
25+
ac := getSDKClient()
26+
data, _, err := ac.AccessTokens().List(cmd.Context())
27+
if err != nil {
28+
return convertSDKError(err)
29+
}
30+
31+
items := normalizeAny(data)
32+
33+
count := dataCount(items)
34+
summary := fmt.Sprintf("%d access tokens", count)
35+
36+
breadcrumbs := []Breadcrumb{
37+
breadcrumb("create", "fizzy token create --description <desc> --permission <perm>", "Create a token"),
38+
breadcrumb("delete", "fizzy token delete <id>", "Delete a token"),
39+
}
40+
41+
printList(items, tokenColumns, summary, breadcrumbs)
42+
return nil
43+
},
44+
}
45+
46+
var (
47+
tokenCreateDescription string
48+
tokenCreatePermission string
49+
)
50+
51+
var tokenCreateCmd = &cobra.Command{
52+
Use: "create",
53+
Short: "Create a personal access token",
54+
Long: "Creates a new personal access token. The token value is shown once at creation and cannot be retrieved later.",
55+
RunE: func(cmd *cobra.Command, args []string) error {
56+
if err := requireAuthAndAccount(); err != nil {
57+
return err
58+
}
59+
60+
if tokenCreateDescription == "" {
61+
return newRequiredFlagError("description")
62+
}
63+
if tokenCreatePermission == "" {
64+
return newRequiredFlagError("permission")
65+
}
66+
67+
ac := getSDKClient()
68+
req := &generated.CreateAccessTokenRequest{
69+
Description: tokenCreateDescription,
70+
Permission: tokenCreatePermission,
71+
}
72+
raw, _, err := ac.AccessTokens().Create(cmd.Context(), req)
73+
if err != nil {
74+
return convertSDKError(err)
75+
}
76+
77+
result := normalizeAny(raw)
78+
id := ""
79+
if m, ok := result.(map[string]any); ok {
80+
id = getStringField(m, "id")
81+
}
82+
83+
breadcrumbs := []Breadcrumb{
84+
breadcrumb("list", "fizzy token list", "List tokens"),
85+
breadcrumb("delete", fmt.Sprintf("fizzy token delete %s", id), "Delete this token"),
86+
}
87+
88+
notice := "Save the token now — it will not be shown again."
89+
printMutation(result, notice, breadcrumbs)
90+
return nil
91+
},
92+
}
93+
94+
var tokenDeleteCmd = &cobra.Command{
95+
Use: "delete TOKEN_ID",
96+
Short: "Delete a personal access token",
97+
Long: "Deletes a personal access token by ID.",
98+
Args: cobra.ExactArgs(1),
99+
RunE: func(cmd *cobra.Command, args []string) error {
100+
if err := requireAuthAndAccount(); err != nil {
101+
return err
102+
}
103+
104+
ac := getSDKClient()
105+
if _, err := ac.AccessTokens().Delete(cmd.Context(), args[0]); err != nil {
106+
return convertSDKError(err)
107+
}
108+
109+
breadcrumbs := []Breadcrumb{
110+
breadcrumb("list", "fizzy token list", "List remaining tokens"),
111+
}
112+
113+
printMutation(map[string]any{"deleted": true, "id": args[0]}, "", breadcrumbs)
114+
return nil
115+
},
116+
}
117+
118+
func init() {
119+
rootCmd.AddCommand(tokenCmd)
120+
121+
tokenCmd.AddCommand(tokenListCmd)
122+
123+
tokenCreateCmd.Flags().StringVar(&tokenCreateDescription, "description", "", "Token description (required)")
124+
tokenCreateCmd.Flags().StringVar(&tokenCreatePermission, "permission", "", "Token permission (required)")
125+
tokenCmd.AddCommand(tokenCreateCmd)
126+
127+
tokenCmd.AddCommand(tokenDeleteCmd)
128+
}

0 commit comments

Comments
 (0)