diff --git a/.goreleaser.yaml b/.goreleaser.yaml index d47df70..c2e267d 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -62,36 +62,6 @@ changelog: - Merge pull request - Merge branch -homebrew_casks: - - name: hevycli - repository: - owner: obay - name: homebrew-tap - token: "{{ .Env.HOMEBREW_TAP_TOKEN }}" - directory: Casks - homepage: "https://github.com/obay/hevycli" - description: "CLI for the Hevy fitness tracking platform" - binaries: - - hevycli - commit_author: - name: goreleaserbot - email: bot@goreleaser.com - commit_msg_template: "Brew cask update for {{ .ProjectName }} version {{ .Tag }}" - -scoops: - - name: hevycli - repository: - owner: obay - name: scoop-bucket - token: "{{ .Env.SCOOP_BUCKET_TOKEN }}" - homepage: "https://github.com/obay/hevycli" - description: "CLI for the Hevy fitness tracking platform" - license: MIT - commit_author: - name: goreleaserbot - email: bot@goreleaser.com - commit_msg_template: "Scoop manifest update for {{ .ProjectName }} version {{ .Tag }}" - release: github: owner: obay diff --git a/cmd/folder/create.go b/cmd/folder/create.go index 044eb82..96652d0 100644 --- a/cmd/folder/create.go +++ b/cmd/folder/create.go @@ -94,7 +94,7 @@ func runFolderCreate(cmd *cobra.Command, args []string) error { fmt.Println(out) } else { fmt.Println("Folder created successfully!") - fmt.Printf("ID: %s\n", folder.ID) + fmt.Printf("ID: %d\n", folder.ID) fmt.Printf("Title: %s\n", folder.Title) } diff --git a/cmd/folder/delete.go b/cmd/folder/delete.go index a864e7e..4f8e4ce 100644 --- a/cmd/folder/delete.go +++ b/cmd/folder/delete.go @@ -4,6 +4,7 @@ import ( "bufio" "fmt" "os" + "strconv" "strings" "github.com/spf13/cobra" @@ -54,9 +55,13 @@ func runFolderDelete(cmd *cobra.Command, args []string) error { client := api.NewClient(apiKey) - var folderID string + var folderID int if len(args) > 0 { - folderID = args[0] + var err error + folderID, err = strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("invalid folder ID: %s", args[0]) + } } else { // Interactive mode - let user select from folders selected, err := prompt.SearchSelect(prompt.SearchSelectConfig{ @@ -71,7 +76,7 @@ func runFolderDelete(cmd *cobra.Command, args []string) error { options := make([]prompt.SelectOption, len(folders.RoutineFolders)) for i, f := range folders.RoutineFolders { options[i] = prompt.SelectOption{ - ID: f.ID, + ID: fmt.Sprintf("%d", f.ID), Title: f.Title, Description: fmt.Sprintf("Index: %d", f.Index), } @@ -82,7 +87,10 @@ func runFolderDelete(cmd *cobra.Command, args []string) error { if err != nil { return err } - folderID = selected.ID + folderID, err = strconv.Atoi(selected.ID) + if err != nil { + return fmt.Errorf("invalid folder ID selected: %s", selected.ID) + } } // Get folder details first to show what we're deleting @@ -93,7 +101,7 @@ func runFolderDelete(cmd *cobra.Command, args []string) error { // Confirm deletion unless --force is used if !deleteForce { - fmt.Printf("Are you sure you want to delete folder '%s' (%s)?\n", folder.Title, folder.ID) + fmt.Printf("Are you sure you want to delete folder '%s' (%d)?\n", folder.Title, folder.ID) fmt.Printf("This action cannot be undone. Routines inside will become unorganized.\n") fmt.Print("Type 'yes' to confirm: ") diff --git a/cmd/folder/get.go b/cmd/folder/get.go index 84f67c3..0d7d251 100644 --- a/cmd/folder/get.go +++ b/cmd/folder/get.go @@ -3,6 +3,7 @@ package folder import ( "fmt" "os" + "strconv" "github.com/spf13/cobra" @@ -19,8 +20,8 @@ var getCmd = &cobra.Command{ Long: `Get detailed information about a specific routine folder. Examples: - hevycli folder get abc123-def456 # Get folder by ID - hevycli folder get abc123 -o json # Output as JSON`, + hevycli folder get 123 # Get folder by ID + hevycli folder get 123 -o json # Output as JSON`, Args: cmdutil.RequireArgs(1, ""), RunE: runGet, } @@ -38,9 +39,13 @@ func runGet(cmd *cobra.Command, args []string) error { client := api.NewClient(apiKey) - var folderID string + var folderID int if len(args) > 0 { - folderID = args[0] + var err error + folderID, err = strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("invalid folder ID: %s", args[0]) + } } else { // Interactive mode - let user select from folders selected, err := prompt.SearchSelect(prompt.SearchSelectConfig{ @@ -55,7 +60,7 @@ func runGet(cmd *cobra.Command, args []string) error { options := make([]prompt.SelectOption, len(folders.RoutineFolders)) for i, f := range folders.RoutineFolders { options[i] = prompt.SelectOption{ - ID: f.ID, + ID: fmt.Sprintf("%d", f.ID), Title: f.Title, Description: fmt.Sprintf("Index: %d", f.Index), } @@ -66,7 +71,10 @@ func runGet(cmd *cobra.Command, args []string) error { if err != nil { return err } - folderID = selected.ID + folderID, err = strconv.Atoi(selected.ID) + if err != nil { + return fmt.Errorf("invalid folder ID selected: %s", selected.ID) + } } // Search for the folder in the list @@ -93,7 +101,7 @@ func runGet(cmd *cobra.Command, args []string) error { } if folder == nil { - return fmt.Errorf("folder not found: %s", folderID) + return fmt.Errorf("folder not found: %d", folderID) } // Determine output format @@ -123,7 +131,7 @@ func runGet(cmd *cobra.Command, args []string) error { func printFolderDetails(f *api.RoutineFolder, cfg *config.Config) { fmt.Printf("Folder: %s\n", f.Title) - fmt.Printf("ID: %s\n", f.ID) + fmt.Printf("ID: %d\n", f.ID) fmt.Printf("Index: %d\n", f.Index) fmt.Printf("Created: %s\n", f.CreatedAt.Format(cfg.Display.DateFormat)) fmt.Printf("Updated: %s\n", f.UpdatedAt.Format(cfg.Display.DateFormat)) diff --git a/cmd/folder/list.go b/cmd/folder/list.go index b4d7784..f838c47 100644 --- a/cmd/folder/list.go +++ b/cmd/folder/list.go @@ -110,7 +110,7 @@ func runList(cmd *cobra.Command, args []string) error { for _, f := range allFolders { updated := f.UpdatedAt.Format(cfg.Display.DateFormat) table.AddRow( - f.ID, + fmt.Sprintf("%d", f.ID), f.Title, fmt.Sprintf("%d", f.Index), updated, diff --git a/cmd/folder/update.go b/cmd/folder/update.go index 124811d..d663b98 100644 --- a/cmd/folder/update.go +++ b/cmd/folder/update.go @@ -3,6 +3,7 @@ package folder import ( "fmt" "os" + "strconv" "github.com/spf13/cobra" @@ -48,9 +49,13 @@ func runFolderUpdate(cmd *cobra.Command, args []string) error { client := api.NewClient(apiKey) - var folderID string + var folderID int if len(args) > 0 { - folderID = args[0] + var err error + folderID, err = strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("invalid folder ID: %s", args[0]) + } } else { // Interactive mode - let user select from folders selected, err := prompt.SearchSelect(prompt.SearchSelectConfig{ @@ -65,7 +70,7 @@ func runFolderUpdate(cmd *cobra.Command, args []string) error { options := make([]prompt.SelectOption, len(folders.RoutineFolders)) for i, f := range folders.RoutineFolders { options[i] = prompt.SelectOption{ - ID: f.ID, + ID: fmt.Sprintf("%d", f.ID), Title: f.Title, Description: fmt.Sprintf("Index: %d", f.Index), } @@ -76,7 +81,10 @@ func runFolderUpdate(cmd *cobra.Command, args []string) error { if err != nil { return err } - folderID = selected.ID + folderID, err = strconv.Atoi(selected.ID) + if err != nil { + return fmt.Errorf("invalid folder ID selected: %s", selected.ID) + } } // Determine output format @@ -112,7 +120,7 @@ func runFolderUpdate(cmd *cobra.Command, args []string) error { fmt.Println(out) } else { fmt.Println("Folder updated successfully!") - fmt.Printf("ID: %s\n", folder.ID) + fmt.Printf("ID: %d\n", folder.ID) fmt.Printf("Title: %s\n", folder.Title) } diff --git a/cmd/routine/get.go b/cmd/routine/get.go index 2fb283c..4931513 100644 --- a/cmd/routine/get.go +++ b/cmd/routine/get.go @@ -105,7 +105,7 @@ func printRoutineDetails(r *api.Routine, cfg *config.Config, formatter output.Fo fmt.Printf("ID: %s\n", r.ID) if r.FolderID != nil { - fmt.Printf("Folder: %s\n", *r.FolderID) + fmt.Printf("Folder: %d\n", *r.FolderID) } fmt.Printf("Created: %s\n", r.CreatedAt.Format(cfg.Display.DateFormat)) diff --git a/cmd/routine/list.go b/cmd/routine/list.go index ea2fd5f..727a7cb 100644 --- a/cmd/routine/list.go +++ b/cmd/routine/list.go @@ -3,6 +3,7 @@ package routine import ( "fmt" "os" + "strconv" "github.com/spf13/cobra" @@ -94,9 +95,13 @@ func runList(cmd *cobra.Command, args []string) error { // Filter by folder if specified if listFolder != "" { + folderID, err := strconv.Atoi(listFolder) + if err != nil { + return fmt.Errorf("invalid folder ID: %s", listFolder) + } var filtered []api.Routine for _, r := range allRoutines { - if r.FolderID != nil && *r.FolderID == listFolder { + if r.FolderID != nil && *r.FolderID == folderID { filtered = append(filtered, r) } } @@ -120,7 +125,7 @@ func runList(cmd *cobra.Command, args []string) error { for _, r := range allRoutines { folder := "-" if r.FolderID != nil { - folder = *r.FolderID + folder = fmt.Sprintf("%d", *r.FolderID) } updated := r.UpdatedAt.Format(cfg.Display.DateFormat) diff --git a/internal/api/client.go b/internal/api/client.go index 425dd4c..e654965 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -355,7 +355,14 @@ func (c *Client) CreateRoutine(req *CreateRoutineRequest) (*Routine, error) { return nil, err } - return &result.Routine, nil + if len(result.Routines) == 0 { + return nil, &APIError{ + ErrorCode: "EMPTY_RESPONSE", + ErrorMessage: "API returned no routines in response", + } + } + + return &result.Routines[0], nil } // UpdateRoutine updates an existing routine @@ -377,7 +384,14 @@ func (c *Client) UpdateRoutine(id string, req *UpdateRoutineRequest) (*Routine, return nil, err } - return &result.Routine, nil + if len(result.Routines) == 0 { + return nil, &APIError{ + ErrorCode: "EMPTY_RESPONSE", + ErrorMessage: "API returned no routines in response", + } + } + + return &result.Routines[0], nil } // CreateRoutineFolder creates a new routine folder @@ -403,11 +417,11 @@ func (c *Client) CreateRoutineFolder(req *CreateRoutineFolderRequest) (*RoutineF } // GetRoutineFolder fetches a single routine folder by ID -func (c *Client) GetRoutineFolder(id string) (*RoutineFolder, error) { +func (c *Client) GetRoutineFolder(id int) (*RoutineFolder, error) { var result RoutineFolderResponse resp, err := c.httpClient.R(). SetResult(&result). - Get("/routine_folders/" + id) + Get(fmt.Sprintf("/routine_folders/%d", id)) if err != nil { return nil, &APIError{ @@ -532,12 +546,12 @@ func (c *Client) DeleteRoutine(id string) error { } // UpdateRoutineFolder updates an existing routine folder -func (c *Client) UpdateRoutineFolder(id string, req *UpdateRoutineFolderRequest) (*RoutineFolder, error) { +func (c *Client) UpdateRoutineFolder(id int, req *UpdateRoutineFolderRequest) (*RoutineFolder, error) { var result RoutineFolderResponse resp, err := c.httpClient.R(). SetBody(req). SetResult(&result). - Put("/routine_folders/" + id) + Put(fmt.Sprintf("/routine_folders/%d", id)) if err != nil { return nil, &APIError{ @@ -554,9 +568,9 @@ func (c *Client) UpdateRoutineFolder(id string, req *UpdateRoutineFolderRequest) } // DeleteRoutineFolder deletes a routine folder by ID -func (c *Client) DeleteRoutineFolder(id string) error { +func (c *Client) DeleteRoutineFolder(id int) error { resp, err := c.httpClient.R(). - Delete("/routine_folders/" + id) + Delete(fmt.Sprintf("/routine_folders/%d", id)) if err != nil { return &APIError{ diff --git a/internal/api/types.go b/internal/api/types.go index 463102f..25f77cc 100644 --- a/internal/api/types.go +++ b/internal/api/types.go @@ -68,7 +68,7 @@ const ( type Routine struct { ID string `json:"id"` Title string `json:"title"` - FolderID *string `json:"folder_id,omitempty"` + FolderID *int `json:"folder_id,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` Exercises []Exercise `json:"exercises"` @@ -76,7 +76,7 @@ type Routine struct { // RoutineFolder represents a folder for organizing routines type RoutineFolder struct { - ID string `json:"id"` + ID int `json:"id"` Title string `json:"title"` Index int `json:"index"` CreatedAt time.Time `json:"created_at"` @@ -280,7 +280,7 @@ type WorkoutResponse struct { // RoutineResponse represents the response from POST/PUT /routines type RoutineResponse struct { - Routine Routine `json:"routine"` + Routines []Routine `json:"routine"` } // RoutineFolderResponse represents the response from POST /routine_folders