Skip to content

Commit 9a714fe

Browse files
committed
feat: add forgebit products command group
1 parent b0c611a commit 9a714fe

4 files changed

Lines changed: 569 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Pre-release — nothing has shipped yet.
1515
- `forgebit vendor list` / `forgebit vendor switch <id|name>`
1616
- `--vendor <id|name>` on any command to target one vendor for a single call without changing the active default
1717
- `forgebit licenses issue|list|show|verify|revoke|renew` against the Forgebit license API
18+
- `forgebit products list|show|create|update|archive|restore` against the Forgebit products API
1819
- `forgebit licenses public-key` — fetch a vendor's Ed25519 public key
1920
- Fully offline license verification (`forgebit licenses verify --offline`) for `jwt` and `forgebit`-type keys, checked locally with no network call
2021
- `forgebit status` — reports whether the CLI is running against the API or offline data

cmd/products.go

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/boone-studios/forgebit-cli/internal/forgebit"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
var productsCmd = &cobra.Command{
11+
Use: "products",
12+
Short: "Create, inspect, and manage Forgebit products",
13+
}
14+
15+
func init() {
16+
rootCmd.AddCommand(productsCmd)
17+
}
18+
19+
func formatDescription(description *string) string {
20+
if description == nil || *description == "" {
21+
return "-"
22+
}
23+
return *description
24+
}
25+
26+
var productsListCmd = &cobra.Command{
27+
Use: "list",
28+
Short: "List products",
29+
RunE: func(c *cobra.Command, args []string) error {
30+
client, err := requireAuth()
31+
if err != nil {
32+
return err
33+
}
34+
35+
flags := c.Flags()
36+
search, _ := flags.GetString("search")
37+
perPage, _ := flags.GetInt("per-page")
38+
asJSON, _ := flags.GetBool("json")
39+
40+
result, err := client.ListProducts(c.Context(), forgebit.ListProductsParams{
41+
Search: search,
42+
PerPage: perPage,
43+
})
44+
if err != nil {
45+
return err
46+
}
47+
48+
if asJSON {
49+
return printJSON(result)
50+
}
51+
52+
if len(result.Data) == 0 {
53+
fmt.Println("No products found.")
54+
return nil
55+
}
56+
for _, product := range result.Data {
57+
fmt.Printf("%s %s slug:%s\n", product.ID, product.Name, product.Slug)
58+
}
59+
if result.Meta.Pagination.NextCursor != nil {
60+
fmt.Printf("(more results — next cursor: %s)\n", *result.Meta.Pagination.NextCursor)
61+
}
62+
return nil
63+
},
64+
}
65+
66+
var productsShowCmd = &cobra.Command{
67+
Use: "show <product-id>",
68+
Short: "Show a single product",
69+
Args: cobra.ExactArgs(1),
70+
RunE: func(c *cobra.Command, args []string) error {
71+
client, err := requireAuth()
72+
if err != nil {
73+
return err
74+
}
75+
76+
asJSON, _ := c.Flags().GetBool("json")
77+
78+
result, err := client.ShowProduct(c.Context(), args[0])
79+
if err != nil {
80+
return err
81+
}
82+
83+
if asJSON {
84+
return printJSON(result)
85+
}
86+
87+
product := result.Data
88+
fmt.Printf("%s %s slug:%s description:%s\n", product.ID, product.Name, product.Slug, formatDescription(product.Description))
89+
return nil
90+
},
91+
}
92+
93+
var productsCreateCmd = &cobra.Command{
94+
Use: "create",
95+
Short: "Create a new product",
96+
RunE: func(c *cobra.Command, args []string) error {
97+
client, err := requireAuth()
98+
if err != nil {
99+
return err
100+
}
101+
102+
flags := c.Flags()
103+
name, _ := flags.GetString("name")
104+
slug, _ := flags.GetString("slug")
105+
description, _ := flags.GetString("description")
106+
defaultExpiry, _ := flags.GetString("default-expiry")
107+
allowedLicenseTypes, _ := flags.GetStringArray("allowed-license-type")
108+
customerPortalManaged, _ := flags.GetBool("customer-portal-managed")
109+
requireDeviceFingerprint, _ := flags.GetBool("require-device-fingerprint")
110+
staleDetection, _ := flags.GetBool("stale-detection")
111+
staleThresholdDays, _ := flags.GetInt("stale-threshold-days")
112+
asJSON, _ := flags.GetBool("json")
113+
114+
if staleDetection && staleThresholdDays <= 0 {
115+
return fmt.Errorf("--stale-threshold-days is required when --stale-detection is set")
116+
}
117+
118+
result, err := client.CreateProduct(c.Context(), forgebit.CreateProductParams{
119+
Name: name,
120+
Slug: slug,
121+
Description: description,
122+
DefaultExpiry: defaultExpiry,
123+
AllowedLicenseTypes: allowedLicenseTypes,
124+
CustomerPortalManaged: customerPortalManaged,
125+
RequireDeviceFingerprint: requireDeviceFingerprint,
126+
IsStaleDetectionEnabled: staleDetection,
127+
StaleThresholdDays: staleThresholdDays,
128+
})
129+
if err != nil {
130+
return err
131+
}
132+
133+
if asJSON {
134+
return printJSON(result)
135+
}
136+
137+
fmt.Printf("Created %s %s slug:%s\n", result.Data.ID, result.Data.Name, result.Data.Slug)
138+
return nil
139+
},
140+
}
141+
142+
var productsUpdateCmd = &cobra.Command{
143+
Use: "update <product-id>",
144+
Short: "Update a product",
145+
Args: cobra.ExactArgs(1),
146+
RunE: func(c *cobra.Command, args []string) error {
147+
client, err := requireAuth()
148+
if err != nil {
149+
return err
150+
}
151+
152+
flags := c.Flags()
153+
name, _ := flags.GetString("name")
154+
description, _ := flags.GetString("description")
155+
defaultExpiry, _ := flags.GetString("default-expiry")
156+
allowedLicenseTypes, _ := flags.GetStringArray("allowed-license-type")
157+
customerPortalManaged, _ := flags.GetBool("customer-portal-managed")
158+
requireDeviceFingerprint, _ := flags.GetBool("require-device-fingerprint")
159+
staleDetection, _ := flags.GetBool("stale-detection")
160+
staleThresholdDays, _ := flags.GetInt("stale-threshold-days")
161+
asJSON, _ := flags.GetBool("json")
162+
163+
if staleDetection && staleThresholdDays <= 0 {
164+
return fmt.Errorf("--stale-threshold-days is required when --stale-detection is set")
165+
}
166+
167+
result, err := client.UpdateProduct(c.Context(), args[0], forgebit.UpdateProductParams{
168+
Name: name,
169+
Description: description,
170+
DefaultExpiry: defaultExpiry,
171+
AllowedLicenseTypes: allowedLicenseTypes,
172+
CustomerPortalManaged: customerPortalManaged,
173+
RequireDeviceFingerprint: requireDeviceFingerprint,
174+
IsStaleDetectionEnabled: staleDetection,
175+
StaleThresholdDays: staleThresholdDays,
176+
})
177+
if err != nil {
178+
return err
179+
}
180+
181+
if asJSON {
182+
return printJSON(result)
183+
}
184+
185+
fmt.Printf("Updated %s %s slug:%s\n", result.Data.ID, result.Data.Name, result.Data.Slug)
186+
return nil
187+
},
188+
}
189+
190+
var productsArchiveCmd = &cobra.Command{
191+
Use: "archive <product-id>",
192+
Short: "Archive a product",
193+
Args: cobra.ExactArgs(1),
194+
RunE: func(c *cobra.Command, args []string) error {
195+
client, err := requireAuth()
196+
if err != nil {
197+
return err
198+
}
199+
200+
asJSON, _ := c.Flags().GetBool("json")
201+
202+
result, err := client.ArchiveProduct(c.Context(), args[0])
203+
if err != nil {
204+
return err
205+
}
206+
207+
if asJSON {
208+
return printJSON(result)
209+
}
210+
211+
fmt.Printf("Archived %s %s\n", result.Data.ID, result.Data.Name)
212+
return nil
213+
},
214+
}
215+
216+
var productsRestoreCmd = &cobra.Command{
217+
Use: "restore <product-id>",
218+
Short: "Restore an archived product",
219+
Args: cobra.ExactArgs(1),
220+
RunE: func(c *cobra.Command, args []string) error {
221+
client, err := requireAuth()
222+
if err != nil {
223+
return err
224+
}
225+
226+
asJSON, _ := c.Flags().GetBool("json")
227+
228+
result, err := client.RestoreProduct(c.Context(), args[0])
229+
if err != nil {
230+
return err
231+
}
232+
233+
if asJSON {
234+
return printJSON(result)
235+
}
236+
237+
fmt.Printf("Restored %s %s\n", result.Data.ID, result.Data.Name)
238+
return nil
239+
},
240+
}
241+
242+
func init() {
243+
productsListCmd.Flags().String("search", "", "filter by product name (substring match)")
244+
productsListCmd.Flags().Int("per-page", 15, "results per page (max 100)")
245+
productsListCmd.Flags().Bool("json", false, "print the raw API response as JSON")
246+
247+
productsShowCmd.Flags().Bool("json", false, "print the raw API response as JSON")
248+
249+
productsCreateCmd.Flags().String("name", "", "product name (required)")
250+
productsCreateCmd.Flags().String("slug", "", "product slug, auto-generated from name if omitted")
251+
productsCreateCmd.Flags().String("description", "", "product description")
252+
productsCreateCmd.Flags().String("default-expiry", "", "30, 90, 365, 730, or perpetual")
253+
productsCreateCmd.Flags().StringArray("allowed-license-type", nil, "allowed license type slug, repeatable")
254+
productsCreateCmd.Flags().Bool("customer-portal-managed", false, "let customers manage licenses for this product in the portal")
255+
productsCreateCmd.Flags().Bool("require-device-fingerprint", false, "require a device fingerprint on activation")
256+
productsCreateCmd.Flags().Bool("stale-detection", false, "enable stale license detection")
257+
productsCreateCmd.Flags().Int("stale-threshold-days", 0, "days of inactivity before a license is stale; required with --stale-detection")
258+
productsCreateCmd.Flags().Bool("json", false, "print the raw API response as JSON")
259+
_ = productsCreateCmd.MarkFlagRequired("name")
260+
261+
productsUpdateCmd.Flags().String("name", "", "product name (required)")
262+
productsUpdateCmd.Flags().String("description", "", "product description")
263+
productsUpdateCmd.Flags().String("default-expiry", "", "30, 90, 365, 730, or perpetual")
264+
productsUpdateCmd.Flags().StringArray("allowed-license-type", nil, "allowed license type slug, repeatable")
265+
productsUpdateCmd.Flags().Bool("customer-portal-managed", false, "let customers manage licenses for this product in the portal")
266+
productsUpdateCmd.Flags().Bool("require-device-fingerprint", false, "require a device fingerprint on activation")
267+
productsUpdateCmd.Flags().Bool("stale-detection", false, "enable stale license detection")
268+
productsUpdateCmd.Flags().Int("stale-threshold-days", 0, "days of inactivity before a license is stale; required with --stale-detection")
269+
productsUpdateCmd.Flags().Bool("json", false, "print the raw API response as JSON")
270+
_ = productsUpdateCmd.MarkFlagRequired("name")
271+
272+
productsArchiveCmd.Flags().Bool("json", false, "print the raw API response as JSON")
273+
productsRestoreCmd.Flags().Bool("json", false, "print the raw API response as JSON")
274+
275+
productsCmd.AddCommand(productsListCmd, productsShowCmd, productsCreateCmd, productsUpdateCmd, productsArchiveCmd, productsRestoreCmd)
276+
}

0 commit comments

Comments
 (0)