From 576ccc886ee4cae089404ec6e8b57e3a7065ec15 Mon Sep 17 00:00:00 2001 From: Greg Wolff Date: Thu, 26 Feb 2026 13:33:00 -0500 Subject: [PATCH 1/2] Adds ability to list Notehub projects to notehub CLI --- notehub/app.go | 24 ++++++++++++++++++++++++ notehub/main.go | 24 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/notehub/app.go b/notehub/app.go index 8bdd76d..c01874c 100644 --- a/notehub/app.go +++ b/notehub/app.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/blues/note-cli/lib" + "github.com/blues/note-go/note" notegoapi "github.com/blues/note-go/notehub/api" ) @@ -300,6 +301,29 @@ func addScope(scope string, appMetadata *AppMetadata, scopeDevices *[]string, sc } +// List all projects accessible to the authenticated user +func appListProjects(flagVerbose bool) (projects []Metadata, err error) { + rsp, err := reqHubV1JSON(flagVerbose, lib.ConfigAPIHub(), "GET", "/v1/projects", nil) + if err != nil { + return + } + var parsed map[string]interface{} + err = note.JSONUnmarshal(rsp, &parsed) + if err != nil { + return + } + items, _ := parsed["projects"].([]interface{}) + for _, v := range items { + p, ok := v.(map[string]interface{}) + if ok { + uid, _ := p["uid"].(string) + label, _ := p["label"].(string) + projects = append(projects, Metadata{Name: label, UID: uid}) + } + } + return +} + // Sort and remove duplicates in a string slice func sortAndRemoveDuplicates(strings []string) []string { diff --git a/notehub/main.go b/notehub/main.go index 9ef6101..886d44e 100644 --- a/notehub/main.go +++ b/notehub/main.go @@ -43,6 +43,7 @@ func getFlagGroups() []lib.FlagGroup { Name: "scope", Description: "Project & Device Scope", Flags: []*flag.Flag{ + lib.GetFlagByName("projects"), lib.GetFlagByName("project"), lib.GetFlagByName("provision"), lib.GetFlagByName("product"), @@ -168,6 +169,8 @@ func main() { flag.StringVar(&flagSn, "sn", "", "serial number") var flagProvision bool flag.BoolVar(&flagProvision, "provision", false, "provision devices") + var flagProjects bool + flag.BoolVar(&flagProjects, "projects", false, "list all projects") // Parse these flags and also the note tool config flags err := lib.FlagParse(false, true) @@ -271,6 +274,27 @@ func main() { fmt.Printf("Notehub CLI Version: %s\n", version) } + if err == nil && flagProjects { + didSomething = true + err = withCreds(credentials, func() (err error) { + projects, err := appListProjects(flagVerbose) + if err != nil { + return err + } + var projectsJSON []byte + if flagPretty { + projectsJSON, err = note.JSONMarshalIndent(projects, "", " ") + } else { + projectsJSON, err = note.JSONMarshal(projects) + } + if err != nil { + return err + } + fmt.Printf("%s\n", projectsJSON) + return nil + }) + } + if flagReq != "" || flagUpload != "" { didSomething = true err = withCreds(credentials, func() (err error) { From c881b3227aebd3a60b2de4e96a74f63d3a55be4c Mon Sep 17 00:00:00 2001 From: Greg Wolff Date: Thu, 26 Feb 2026 13:35:18 -0500 Subject: [PATCH 2/2] Adds Product UIDs to list of returned projects --- notehub/app.go | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/notehub/app.go b/notehub/app.go index c01874c..554d95b 100644 --- a/notehub/app.go +++ b/notehub/app.go @@ -301,8 +301,15 @@ func addScope(scope string, appMetadata *AppMetadata, scopeDevices *[]string, sc } +// ProjectInfo represents a project with its products +type ProjectInfo struct { + Name string `json:"name,omitempty"` + UID string `json:"uid,omitempty"` + Products []Metadata `json:"products,omitempty"` +} + // List all projects accessible to the authenticated user -func appListProjects(flagVerbose bool) (projects []Metadata, err error) { +func appListProjects(flagVerbose bool) (projects []ProjectInfo, err error) { rsp, err := reqHubV1JSON(flagVerbose, lib.ConfigAPIHub(), "GET", "/v1/projects", nil) if err != nil { return @@ -315,11 +322,33 @@ func appListProjects(flagVerbose bool) (projects []Metadata, err error) { items, _ := parsed["projects"].([]interface{}) for _, v := range items { p, ok := v.(map[string]interface{}) - if ok { - uid, _ := p["uid"].(string) - label, _ := p["label"].(string) - projects = append(projects, Metadata{Name: label, UID: uid}) + if !ok { + continue + } + uid, _ := p["uid"].(string) + label, _ := p["label"].(string) + info := ProjectInfo{Name: label, UID: uid} + + // Fetch products for this project + productsRsp := map[string]interface{}{} + err = reqHubV1(flagVerbose, lib.ConfigAPIHub(), "GET", "/v1/projects/"+uid+"/products", nil, &productsRsp) + if err == nil { + pi, exists := productsRsp["products"].([]interface{}) + if exists { + for _, pv := range pi { + pp, ok := pv.(map[string]interface{}) + if ok { + info.Products = append(info.Products, Metadata{ + Name: pp["label"].(string), + UID: pp["uid"].(string), + }) + } + } + } } + err = nil // don't fail the whole listing if one project's products can't be fetched + + projects = append(projects, info) } return }