diff --git a/cli/cli.go b/cli/cli.go
index 3f2e4169..e0bb3f75 100644
--- a/cli/cli.go
+++ b/cli/cli.go
@@ -28,6 +28,7 @@ import (
"github.com/arduino/arduino-cloud-cli/cli/credentials"
"github.com/arduino/arduino-cloud-cli/cli/dashboard"
"github.com/arduino/arduino-cloud-cli/cli/device"
+ "github.com/arduino/arduino-cloud-cli/cli/folders"
"github.com/arduino/arduino-cloud-cli/cli/ota"
"github.com/arduino/arduino-cloud-cli/cli/template"
"github.com/arduino/arduino-cloud-cli/cli/thing"
@@ -67,6 +68,7 @@ func Execute() {
cli.AddCommand(dashboard.NewCommand())
cli.AddCommand(ota.NewCommand())
cli.AddCommand(template.NewCommand())
+ cli.AddCommand(folders.NewCommand())
if err := cli.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
diff --git a/cli/folders/folder.go b/cli/folders/folder.go
new file mode 100644
index 00000000..06a91bec
--- /dev/null
+++ b/cli/folders/folder.go
@@ -0,0 +1,34 @@
+// This file is part of arduino-cloud-cli.
+//
+// Copyright (C) 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package folders
+
+import (
+ "github.com/spf13/cobra"
+)
+
+func NewCommand() *cobra.Command {
+ foldersCommand := &cobra.Command{
+ Use: "folders",
+ Short: "Custom folders.",
+ Long: "Commands to manage folders.",
+ }
+
+ foldersCommand.AddCommand(initFoldersListCommand())
+
+ return foldersCommand
+}
diff --git a/cli/folders/list.go b/cli/folders/list.go
new file mode 100644
index 00000000..4e4c4ac9
--- /dev/null
+++ b/cli/folders/list.go
@@ -0,0 +1,55 @@
+// This file is part of arduino-cloud-cli.
+//
+// Copyright (C) 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package folders
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ "github.com/arduino/arduino-cli/cli/errorcodes"
+ "github.com/arduino/arduino-cli/cli/feedback"
+ "github.com/arduino/arduino-cloud-cli/command/folder"
+ "github.com/arduino/arduino-cloud-cli/config"
+ "github.com/spf13/cobra"
+)
+
+func initFoldersListCommand() *cobra.Command {
+ listCommand := &cobra.Command{
+ Use: "list",
+ Short: "List folders",
+ Long: "List available folders",
+ Run: func(cmd *cobra.Command, args []string) {
+ if err := runFoldersListCommand(); err != nil {
+ feedback.Errorf("Error during folders list: %v", err)
+ os.Exit(errorcodes.ErrGeneric)
+ }
+ },
+ }
+
+ return listCommand
+}
+
+func runFoldersListCommand() error {
+ cred, err := config.RetrieveCredentials()
+ if err != nil {
+ return fmt.Errorf("retrieving credentials: %w", err)
+ }
+ ctx := context.Background()
+ return folder.ListFolders(ctx, cred)
+}
diff --git a/command/folder/dto.go b/command/folder/dto.go
new file mode 100644
index 00000000..077934ae
--- /dev/null
+++ b/command/folder/dto.go
@@ -0,0 +1,57 @@
+package folder
+
+import (
+ "time"
+
+ "github.com/arduino/arduino-cli/table"
+)
+
+type PathNode struct {
+ // ID of the folder that makes up this path node
+ FolderId string `json:"folder_id"`
+ // Name of the folder that makes up this path node
+ FolderName string `json:"folder_name"`
+}
+
+type Folder struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+ Parent *PathNode `json:"parent,omitempty"`
+ Path []PathNode `json:"path,omitempty"`
+}
+
+type Folders struct {
+ Folders []Folder `json:"folders"`
+}
+
+func (r *Folders) Data() interface{} {
+ return r
+}
+
+func (r *Folders) String() string {
+ if len(r.Folders) == 0 {
+ return ""
+ }
+ t := table.New()
+ t.SetHeader("Folder ID", "Name", "Created At", "Updated At", "Parent", "Path")
+
+ // Now print the table
+ for _, fol := range r.Folders {
+ parent := ""
+ if fol.Parent != nil {
+ parent = fol.Parent.FolderName
+ }
+ if fol.Path != nil {
+ for _, p := range fol.Path {
+ parent += "> " + p.FolderName
+ }
+ }
+ path := ""
+ line := []any{fol.ID, fol.Name, fol.CreatedAt.Format(time.RFC3339), fol.UpdatedAt.Format(time.RFC3339), parent, path}
+ t.AddRow(line...)
+ }
+
+ return t.Render()
+}
diff --git a/command/folder/list.go b/command/folder/list.go
new file mode 100644
index 00000000..e7e8f0e6
--- /dev/null
+++ b/command/folder/list.go
@@ -0,0 +1,71 @@
+// This file is part of arduino-cloud-cli.
+//
+// Copyright (C) 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package folder
+
+import (
+ "context"
+
+ "github.com/arduino/arduino-cli/cli/feedback"
+ "github.com/arduino/arduino-cloud-cli/config"
+ "github.com/arduino/arduino-cloud-cli/internal/iot"
+)
+
+func ListFolders(ctx context.Context, cred *config.Credentials) error {
+ iotClient, err := iot.NewClient(cred)
+ if err != nil {
+ return err
+ }
+
+ fold, err := iotClient.FoldersList(ctx)
+ if err != nil {
+ return err
+ }
+
+ if fold == nil {
+ feedback.Print("No folders found")
+ } else {
+ folders := &Folders{}
+ for _, f := range fold {
+ trFolder := Folder{
+ ID: f.Id,
+ Name: f.Name,
+ CreatedAt: f.CreatedAt,
+ UpdatedAt: f.UpdatedAt,
+ }
+ if f.Parent != nil {
+ trFolder.Parent = &PathNode{
+ FolderId: f.Parent.FolderId,
+ FolderName: f.Parent.FolderName,
+ }
+ }
+ if f.Path != nil {
+ trFolder.Path = make([]PathNode, 0, len(f.Path))
+ for _, p := range f.Path {
+ trFolder.Path = append(trFolder.Path, PathNode{
+ FolderId: p.FolderId,
+ FolderName: p.FolderName,
+ })
+ }
+ }
+ folders.Folders = append(folders.Folders, trFolder)
+ }
+ feedback.PrintResult(folders)
+ }
+
+ return nil
+}
diff --git a/go.mod b/go.mod
index f29d6e6b..5acfacb4 100644
--- a/go.mod
+++ b/go.mod
@@ -2,6 +2,8 @@ module github.com/arduino/arduino-cloud-cli
go 1.23.0
+replace github.com/arduino/iot-client-go/v2 => /home/macolomb/CODE/clients-iot-api/generated/iot-client-go
+
require (
github.com/arduino/arduino-cli v0.0.0-20240927141754-d9dd4ba1ed71
github.com/arduino/go-paths-helper v1.12.1
diff --git a/internal/iot/client.go b/internal/iot/client.go
index 788a4e05..cb521ed2 100644
--- a/internal/iot/client.go
+++ b/internal/iot/client.go
@@ -560,6 +560,22 @@ func (cl *Client) TemplateApply(ctx context.Context, id, thingId, prefix, device
return dev, nil
}
+// List available folders
+func (cl *Client) FoldersList(ctx context.Context) ([]iotclient.Folder, error) {
+ ctx, err := ctxWithToken(ctx, cl.token)
+ if err != nil {
+ return nil, err
+ }
+
+ req := cl.api.FoldersApi.ListFolders(ctx)
+ folders, _, err := cl.api.FoldersApi.ListFoldersExecute(req)
+ if err != nil {
+ err = fmt.Errorf("listing folders: %w", errorDetail(err))
+ return nil, err
+ }
+ return folders, nil
+}
+
func (cl *Client) setup(client, secret, organizationId string) error {
baseURL := GetArduinoAPIBaseURL()
@@ -572,7 +588,7 @@ func (cl *Client) setup(client, secret, organizationId string) error {
}
config.Servers = iotclient.ServerConfigurations{
{
- URL: fmt.Sprintf("%s/iot", baseURL),
+ URL: baseURL,
Description: "IoT API endpoint",
},
}