Skip to content

Commit c6eb810

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 72e6994 commit c6eb810

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
@@ -23,6 +23,7 @@ ARG fizzy signup help 00 [command]
2323
ARG fizzy skill help 00 [command]
2424
ARG fizzy step help 00 [command]
2525
ARG fizzy tag help 00 [command]
26+
ARG fizzy token help 00 [command]
2627
ARG fizzy upload help 00 [command]
2728
ARG fizzy user help 00 [command]
2829
ARG fizzy webhook help 00 [command]
@@ -188,6 +189,13 @@ CMD fizzy tag
188189
CMD fizzy tag help
189190
CMD fizzy tag list
190191
CMD fizzy tag ls
192+
CMD fizzy token
193+
CMD fizzy token create
194+
CMD fizzy token delete
195+
CMD fizzy token help
196+
CMD fizzy token list
197+
CMD fizzy token ls
198+
CMD fizzy token rm
191199
CMD fizzy upload
192200
CMD fizzy upload file
193201
CMD fizzy upload help
@@ -2646,6 +2654,106 @@ FLAG fizzy tag ls --quiet type=bool
26462654
FLAG fizzy tag ls --styled type=bool
26472655
FLAG fizzy tag ls --token type=string
26482656
FLAG fizzy tag ls --verbose type=bool
2657+
FLAG fizzy token --agent type=bool
2658+
FLAG fizzy token --api-url type=string
2659+
FLAG fizzy token --count type=bool
2660+
FLAG fizzy token --help type=bool
2661+
FLAG fizzy token --ids-only type=bool
2662+
FLAG fizzy token --jq type=string
2663+
FLAG fizzy token --json type=bool
2664+
FLAG fizzy token --limit type=int
2665+
FLAG fizzy token --markdown type=bool
2666+
FLAG fizzy token --profile type=string
2667+
FLAG fizzy token --quiet type=bool
2668+
FLAG fizzy token --styled type=bool
2669+
FLAG fizzy token --token type=string
2670+
FLAG fizzy token --verbose type=bool
2671+
FLAG fizzy token create --agent type=bool
2672+
FLAG fizzy token create --api-url type=string
2673+
FLAG fizzy token create --count type=bool
2674+
FLAG fizzy token create --description type=string
2675+
FLAG fizzy token create --help type=bool
2676+
FLAG fizzy token create --ids-only type=bool
2677+
FLAG fizzy token create --jq type=string
2678+
FLAG fizzy token create --json type=bool
2679+
FLAG fizzy token create --limit type=int
2680+
FLAG fizzy token create --markdown type=bool
2681+
FLAG fizzy token create --permission type=string
2682+
FLAG fizzy token create --profile type=string
2683+
FLAG fizzy token create --quiet type=bool
2684+
FLAG fizzy token create --styled type=bool
2685+
FLAG fizzy token create --token type=string
2686+
FLAG fizzy token create --verbose type=bool
2687+
FLAG fizzy token delete --agent type=bool
2688+
FLAG fizzy token delete --api-url type=string
2689+
FLAG fizzy token delete --count type=bool
2690+
FLAG fizzy token delete --help type=bool
2691+
FLAG fizzy token delete --ids-only type=bool
2692+
FLAG fizzy token delete --jq type=string
2693+
FLAG fizzy token delete --json type=bool
2694+
FLAG fizzy token delete --limit type=int
2695+
FLAG fizzy token delete --markdown type=bool
2696+
FLAG fizzy token delete --profile type=string
2697+
FLAG fizzy token delete --quiet type=bool
2698+
FLAG fizzy token delete --styled type=bool
2699+
FLAG fizzy token delete --token type=string
2700+
FLAG fizzy token delete --verbose type=bool
2701+
FLAG fizzy token help --agent type=bool
2702+
FLAG fizzy token help --api-url type=string
2703+
FLAG fizzy token help --count type=bool
2704+
FLAG fizzy token help --help type=bool
2705+
FLAG fizzy token help --ids-only type=bool
2706+
FLAG fizzy token help --jq type=string
2707+
FLAG fizzy token help --json type=bool
2708+
FLAG fizzy token help --limit type=int
2709+
FLAG fizzy token help --markdown type=bool
2710+
FLAG fizzy token help --profile type=string
2711+
FLAG fizzy token help --quiet type=bool
2712+
FLAG fizzy token help --styled type=bool
2713+
FLAG fizzy token help --token type=string
2714+
FLAG fizzy token help --verbose type=bool
2715+
FLAG fizzy token list --agent type=bool
2716+
FLAG fizzy token list --api-url type=string
2717+
FLAG fizzy token list --count type=bool
2718+
FLAG fizzy token list --help type=bool
2719+
FLAG fizzy token list --ids-only type=bool
2720+
FLAG fizzy token list --jq type=string
2721+
FLAG fizzy token list --json type=bool
2722+
FLAG fizzy token list --limit type=int
2723+
FLAG fizzy token list --markdown type=bool
2724+
FLAG fizzy token list --profile type=string
2725+
FLAG fizzy token list --quiet type=bool
2726+
FLAG fizzy token list --styled type=bool
2727+
FLAG fizzy token list --token type=string
2728+
FLAG fizzy token list --verbose type=bool
2729+
FLAG fizzy token ls --agent type=bool
2730+
FLAG fizzy token ls --api-url type=string
2731+
FLAG fizzy token ls --count type=bool
2732+
FLAG fizzy token ls --help type=bool
2733+
FLAG fizzy token ls --ids-only type=bool
2734+
FLAG fizzy token ls --jq type=string
2735+
FLAG fizzy token ls --json type=bool
2736+
FLAG fizzy token ls --limit type=int
2737+
FLAG fizzy token ls --markdown type=bool
2738+
FLAG fizzy token ls --profile type=string
2739+
FLAG fizzy token ls --quiet type=bool
2740+
FLAG fizzy token ls --styled type=bool
2741+
FLAG fizzy token ls --token type=string
2742+
FLAG fizzy token ls --verbose type=bool
2743+
FLAG fizzy token rm --agent type=bool
2744+
FLAG fizzy token rm --api-url type=string
2745+
FLAG fizzy token rm --count type=bool
2746+
FLAG fizzy token rm --help type=bool
2747+
FLAG fizzy token rm --ids-only type=bool
2748+
FLAG fizzy token rm --jq type=string
2749+
FLAG fizzy token rm --json type=bool
2750+
FLAG fizzy token rm --limit type=int
2751+
FLAG fizzy token rm --markdown type=bool
2752+
FLAG fizzy token rm --profile type=string
2753+
FLAG fizzy token rm --quiet type=bool
2754+
FLAG fizzy token rm --styled type=bool
2755+
FLAG fizzy token rm --token type=string
2756+
FLAG fizzy token rm --verbose type=bool
26492757
FLAG fizzy upload --agent type=bool
26502758
FLAG fizzy upload --api-url type=string
26512759
FLAG fizzy upload --count type=bool
@@ -3215,6 +3323,13 @@ SUB fizzy tag
32153323
SUB fizzy tag help
32163324
SUB fizzy tag list
32173325
SUB fizzy tag ls
3326+
SUB fizzy token
3327+
SUB fizzy token create
3328+
SUB fizzy token delete
3329+
SUB fizzy token help
3330+
SUB fizzy token list
3331+
SUB fizzy token ls
3332+
SUB fizzy token rm
32183333
SUB fizzy upload
32193334
SUB fizzy upload file
32203335
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
@@ -77,4 +77,11 @@ var (
7777
{Header: "URL", Field: "payload_url"},
7878
{Header: "Active", Field: "active"},
7979
}
80+
81+
tokenColumns = render.Columns{
82+
{Header: "ID", Field: "id"},
83+
{Header: "Description", Field: "description"},
84+
{Header: "Permission", Field: "permission"},
85+
{Header: "Created", Field: "created_at"},
86+
}
8087
)

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)