Skip to content
This repository was archived by the owner on Mar 12, 2026. It is now read-only.

Commit cff00d5

Browse files
authored
Merge pull request #143 from nnemirovsky/feat/card-move-on-hold
feat: add --on-hold flag to card move command
2 parents 5b34a45 + 581b643 commit cff00d5

2 files changed

Lines changed: 61 additions & 4 deletions

File tree

cmd/card/move.go

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ func newMoveCmd(f *factory.Factory) *cobra.Command {
1515
var columnName string
1616
var accountID string
1717
var projectID string
18+
var onHold bool
1819

1920
cmd := &cobra.Command{
2021
Use: "move [ID or URL]",
@@ -25,9 +26,14 @@ You can specify the card using either:
2526
- A numeric ID (e.g., "12345")
2627
- A Basecamp URL (e.g., "https://3.basecamp.com/1234567/buckets/89012345/card_tables/cards/12345")
2728
29+
Use --on-hold to move a card to the on-hold section of its current column
30+
(or target column if --column is also specified).
31+
2832
Examples:
2933
bc4 card move 123 --column "In Progress"
3034
bc4 card move 123 --column 456
35+
bc4 card move 123 --on-hold
36+
bc4 card move 123 --column "Developing" --on-hold
3137
bc4 card move https://3.basecamp.com/1234567/buckets/89012345/card_tables/cards/12345 --column "Done"`,
3238
Args: cobra.ExactArgs(1),
3339
RunE: func(cmd *cobra.Command, args []string) error {
@@ -37,8 +43,8 @@ Examples:
3743
return fmt.Errorf("invalid card ID or URL: %s", args[0])
3844
}
3945

40-
if columnName == "" {
41-
return fmt.Errorf("--column flag is required")
46+
if columnName == "" && !onHold {
47+
return fmt.Errorf("--column flag is required (or use --on-hold)")
4248
}
4349

4450
// Apply overrides if specified
@@ -112,6 +118,22 @@ Examples:
112118
}
113119
}
114120

121+
// Handle --on-hold: move to the on-hold section
122+
if onHold {
123+
targetColumn, err := findColumn(currentCardTable, columnName, card)
124+
if err != nil {
125+
return err
126+
}
127+
if targetColumn.OnHold.ID == 0 {
128+
return fmt.Errorf("column '%s' does not have an on-hold section", targetColumn.Title)
129+
}
130+
if err := cardOps.MoveCard(f.Context(), resolvedProjectID, cardID, targetColumn.OnHold.ID); err != nil {
131+
return fmt.Errorf("failed to move card to on-hold: %w", err)
132+
}
133+
fmt.Printf("✓ Moved card #%d to on-hold in column '%s'\n", cardID, targetColumn.Title)
134+
return nil
135+
}
136+
115137
// Find the target column by name or ID within the same card table
116138
var targetColumnID int64
117139

@@ -164,10 +186,44 @@ Examples:
164186
},
165187
}
166188

167-
cmd.Flags().StringVar(&columnName, "column", "", "Target column name or ID (required)")
189+
cmd.Flags().StringVar(&columnName, "column", "", "Target column name or ID")
168190
cmd.Flags().StringVarP(&accountID, "account", "a", "", "Specify account ID")
169191
cmd.Flags().StringVarP(&projectID, "project", "p", "", "Specify project ID")
170-
_ = cmd.MarkFlagRequired("column")
192+
cmd.Flags().BoolVar(&onHold, "on-hold", false, "Move card to the on-hold section of its current (or target) column")
171193

172194
return cmd
173195
}
196+
197+
// findColumn resolves the target column from --column flag or falls back to the card's current column.
198+
func findColumn(cardTable *api.CardTable, columnName string, card *api.Card) (*api.Column, error) {
199+
if columnName != "" {
200+
// Try as ID first
201+
if id, err := strconv.ParseInt(columnName, 10, 64); err == nil {
202+
for i := range cardTable.Lists {
203+
if cardTable.Lists[i].ID == id {
204+
return &cardTable.Lists[i], nil
205+
}
206+
}
207+
return nil, fmt.Errorf("column ID %d not found in card table '%s'", id, cardTable.Title)
208+
}
209+
// Search by name
210+
columnNameLower := strings.ToLower(columnName)
211+
for i := range cardTable.Lists {
212+
if strings.ToLower(cardTable.Lists[i].Title) == columnNameLower {
213+
return &cardTable.Lists[i], nil
214+
}
215+
}
216+
return nil, fmt.Errorf("column '%s' not found in card table '%s'", columnName, cardTable.Title)
217+
}
218+
219+
// No --column specified, use card's current column
220+
if card.Parent == nil {
221+
return nil, fmt.Errorf("card has no parent column")
222+
}
223+
for i := range cardTable.Lists {
224+
if cardTable.Lists[i].ID == card.Parent.ID {
225+
return &cardTable.Lists[i], nil
226+
}
227+
}
228+
return nil, fmt.Errorf("could not find card's current column in card table")
229+
}

internal/api/card.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type Column struct {
3838

3939
// OnHoldStatus represents the on_hold status of a column
4040
type OnHoldStatus struct {
41+
ID int64 `json:"id,omitempty"`
4142
Enabled bool `json:"enabled"`
4243
CardsCount int `json:"cards_count,omitempty"`
4344
CardsURL string `json:"cards_url,omitempty"`

0 commit comments

Comments
 (0)