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
4 changes: 3 additions & 1 deletion cmd/api_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,12 @@ func newAPIKeysListCommand(env environment) *cobra.Command {
key.Name,
formatMoney(key.MonthlySpend),
formatLimit(key.MonthlyLimit),
formatExpiry(key.ExpiresAt),
formatLabels(key.Labels),
})
}

return writeTable(out, []string{"ID", "NAME", "SPEND", "LIMIT", "LABELS"}, rows)
return writeTable(out, []string{"ID", "NAME", "SPEND", "LIMIT", "EXPIRES", "LABELS"}, rows)
},
}
}
Expand Down Expand Up @@ -112,6 +113,7 @@ func newAPIKeysShowCommand(env environment) *cobra.Command {
{"Name", key.Name},
{"Spend this month", formatMoney(key.MonthlySpend)},
{"Monthly limit", formatLimit(key.MonthlyLimit)},
{"Expires", formatExpiry(key.ExpiresAt)},
{"Permissions", formatPermissions(key.Permissions)},
{"Logging", strconv.FormatBool(key.Logging)},
{"Group", formatGroup(key.Group)},
Expand Down
10 changes: 10 additions & 0 deletions cmd/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"time"

"github.com/requestyai/cli/internal/client"
"github.com/requestyai/cli/internal/util"
"github.com/shopspring/decimal"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -102,6 +103,15 @@ func formatTime(moment time.Time) string {
return moment.Format(time.RFC3339)
}

// formatExpiry renders when a key stops working, or that it never does.
func formatExpiry(moment *time.Time) string {
if moment == nil {
return util.NeverExpires
}

return formatTime(*moment)
}

// formatDate renders a timestamp as a calendar date, to keep table rows narrow.
func formatDate(moment time.Time) string {
if moment.IsZero() {
Expand Down
4 changes: 3 additions & 1 deletion internal/client/api_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@ type APIKeyGroup struct {
}

// APIKey is a key as it appears in the organization listing. A MonthlyLimit of
// zero means the key is not capped.
// zero means the key is not capped, and a nil ExpiresAt means it never expires.
type APIKey struct {
ID string `json:"id"`
Name string `json:"name"`
MonthlyLimit decimal.Decimal `json:"monthly_limit"`
MonthlySpend decimal.Decimal `json:"monthly_spend"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
Permissions APIKeyPermissions `json:"permissions"`
Labels map[string]string `json:"labels,omitempty"`
CreatedBy *APIKeyUser `json:"created_by,omitempty"`
Expand All @@ -59,6 +60,7 @@ type APIKeyDetails struct {
Logging bool `json:"logging"`
MonthlyLimit decimal.Decimal `json:"monthly_limit"`
MonthlySpend decimal.Decimal `json:"monthly_spend"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
Permissions APIKeyPermissions `json:"permissions"`
Group *APIKeyGroup `json:"group,omitempty"`
}
Expand Down
13 changes: 13 additions & 0 deletions internal/client/api_keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,18 @@ func newTestClient(t *testing.T, status int, reply string) (*Client, *recorder)
return New(config.Config{APIBaseURL: server.URL, APIKey: "test-key"}), seen
}

func mustParseTime(t *testing.T, value string) *time.Time {
t.Helper()

parsed, err := time.Parse(time.RFC3339, value)
require.NoError(t, err)

return &parsed
}

func TestClientAPIKeys(t *testing.T) {
reply := `{"keys":[{"id":"key-1","name":"production","monthly_limit":"500","monthly_spend":"12.5",` +
`"expires_at":"2026-12-31T23:59:59Z",` +
`"permissions":{"manage":"read","completions":"write"},"labels":{"env":"prod"},` +
`"created_by":{"id":"user-1","email":"you@example.com"},"group":{"id":"group-1"}}]}`
client, seen := newTestClient(t, http.StatusOK, reply)
Expand All @@ -68,6 +78,7 @@ func TestClientAPIKeys(t *testing.T) {
Name: "production",
MonthlyLimit: decimal.RequireFromString("500"),
MonthlySpend: decimal.RequireFromString("12.5"),
ExpiresAt: mustParseTime(t, "2026-12-31T23:59:59Z"),
Permissions: APIKeyPermissions{Manage: APIKeyPermissionRead, Completions: APIKeyPermissionWrite},
Labels: map[string]string{"env": "prod"},
CreatedBy: &APIKeyUser{ID: "user-1", Email: "you@example.com"},
Expand All @@ -77,6 +88,7 @@ func TestClientAPIKeys(t *testing.T) {

func TestClientAPIKey(t *testing.T) {
reply := `{"id":"key-1","name":"production","logging":true,"monthly_limit":"0","monthly_spend":"3",` +
`"expires_at":"2026-12-31T23:59:59Z",` +
`"permissions":{"manage":"none","completions":"write"},"group":{"id":"group-1"}}`
client, seen := newTestClient(t, http.StatusOK, reply)

Expand All @@ -91,6 +103,7 @@ func TestClientAPIKey(t *testing.T) {
Logging: true,
MonthlyLimit: decimal.RequireFromString("0"),
MonthlySpend: decimal.RequireFromString("3"),
ExpiresAt: mustParseTime(t, "2026-12-31T23:59:59Z"),
Permissions: APIKeyPermissions{Manage: APIKeyPermissionNone, Completions: APIKeyPermissionWrite},
Group: &APIKeyGroup{ID: "group-1"},
}, key)
Expand Down
Loading