Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
6a1b207
Migrate CLI to flat SDK routes (basecamp/basecamp-sdk#74)
jeremy Feb 5, 2026
6f954cf
Use extractID() instead of extractWithProject() where project is disc…
jeremy Feb 5, 2026
69a33d5
Update e2e tests for commands that no longer require project
jeremy Feb 5, 2026
e97a9ca
Upgrade SDK to cdd1aa7 (flat branch, boosts + timesheet flattening)
jeremy Feb 8, 2026
07ce76d
Flatten boost commands for flat SDK routes (remove bucketID params)
jeremy Feb 8, 2026
50cd988
Upgrade SDK to c75edff (flat branch, required-fields + derefInt64 fix)
jeremy Feb 22, 2026
8e7eccc
Upgrade SDK to f857e99 (flat branch, derefInt64 + Smithy regen)
jeremy Feb 25, 2026
ea11eb8
Migrate TUI data layer to flat SDK routes (remove bucketID params)
jeremy Feb 25, 2026
771089a
Upgrade SDK to 96abdc8 (flat branch, C1/C2/C5/C6 fixes)
jeremy Feb 26, 2026
a2eb4e9
Wire projectID/bucketID through CLI commands for flat SDK routes
jeremy Feb 26, 2026
7c969bb
Pass projectID to TUI ProjectTimeline pool fetch
jeremy Feb 26, 2026
e1ee5c0
Remove dead project resolution from webhook show/update/delete and bo…
jeremy Feb 26, 2026
3ff547c
Rename bucketID vars to projectID in TUI resolve layer
jeremy Feb 26, 2026
78c7bce
Rename ParsedURL.BucketID to ProjectID (bucket_id → project_id in JSON)
jeremy Feb 26, 2026
b9564a5
Skip project resolution in campfire post when campfire ID is provided
jeremy Feb 26, 2026
3a82344
Fix comment interactive fallback to resolve project before picker
jeremy Feb 26, 2026
ddf36cf
Upgrade SDK to main branch (d284e87, timeline/timesheet path fixes)
jeremy Feb 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions e2e/events.bats
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,6 @@ load test_helper
assert_output_contains "ID required"
}

@test "events without project shows error" {
create_credentials
create_global_config '{"account_id": 99999}'

run basecamp events 12345
assert_failure
assert_output_contains "project"
}


# Flag parsing

@test "events --project without value shows error" {
create_credentials
create_global_config '{"account_id": 99999}'

run basecamp events 12345 --project
assert_failure
assert_output_contains "--project requires a value"
}


# Help

@test "events --help shows help" {
Expand Down
21 changes: 0 additions & 21 deletions e2e/messagetypes.bats
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,6 @@ load test_helper

# Missing context errors

@test "messagetypes without project shows error" {
create_credentials
create_global_config '{"account_id": 99999}'

run basecamp messagetypes
assert_failure
assert_output_contains "project"
}

@test "messagetypes show without id shows error" {
create_credentials
create_global_config '{"account_id": 99999, "project_id": 123}'
Expand Down Expand Up @@ -97,18 +88,6 @@ load test_helper
}


# Flag parsing

@test "messagetypes --project without value shows error" {
create_credentials
create_global_config '{"account_id": 99999}'

run basecamp messagetypes --project
assert_failure
assert_output_contains "--project requires a value"
}


# Help

@test "messagetypes --help shows help" {
Expand Down
10 changes: 0 additions & 10 deletions e2e/recordings.bats
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,6 @@ load test_helper
assert_output_contains "--visible or --hidden"
}

@test "recordings visibility without project shows error" {
create_credentials
create_global_config '{"account_id": 99999}'

run basecamp recordings visibility 456 --visible
assert_failure
assert_output_contains "project"
}


# Trash/Archive/Restore - missing context

@test "recordings trash without recording id shows error" {
Expand Down
30 changes: 1 addition & 29 deletions e2e/timesheet.bats
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,6 @@ load test_helper
assert_output_contains "--person requires a value"
}

@test "timesheet --project without value shows error" {
create_credentials
create_global_config '{"account_id": 99999}'

run basecamp timesheet --project
assert_failure
assert_output_contains "--project requires a value"
}


# Date range validation

@test "timesheet with start but no end shows error" {
Expand All @@ -66,33 +56,15 @@ load test_helper

# Missing context errors

@test "timesheet project without id shows error" {
create_credentials
create_global_config '{"account_id": 99999}'

run basecamp timesheet project
assert_failure
assert_output_contains "Project ID"
}

@test "timesheet recording without id shows error" {
create_credentials
create_global_config '{"account_id": 99999, "project_id": 123}'
create_global_config '{"account_id": 99999}'

run basecamp timesheet recording
assert_failure
assert_output_contains "ID required"
}

@test "timesheet recording without project shows error" {
create_credentials
create_global_config '{"account_id": 99999}'

run basecamp timesheet recording 456
assert_failure
assert_output_contains "project"
}


# Help flag

Expand Down
10 changes: 5 additions & 5 deletions e2e/url.bats
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ load test_helper
assert_success
is_valid_json
assert_json_value ".data.account_id" "2914079"
assert_json_value ".data.bucket_id" "41746046"
assert_json_value ".data.project_id" "41746046"
assert_json_value ".data.type" "messages"
assert_json_value ".data.recording_id" "9478142982"
}
Expand All @@ -36,7 +36,7 @@ load test_helper
assert_success
is_valid_json
assert_json_value ".data.account_id" "2914079"
assert_json_value ".data.bucket_id" "41746046"
assert_json_value ".data.project_id" "41746046"
assert_json_value ".data.type" "messages"
assert_json_value ".data.recording_id" "9478142982"
assert_json_value ".data.comment_id" "9488783598"
Expand Down Expand Up @@ -90,7 +90,7 @@ load test_helper
assert_success
is_valid_json
assert_json_value ".data.account_id" "2914079"
assert_json_value ".data.bucket_id" "27"
assert_json_value ".data.project_id" "27"
assert_json_value ".data.type" "cards"
assert_json_value ".data.type_singular" "card"
assert_json_value ".data.recording_id" "9486682178"
Expand All @@ -105,7 +105,7 @@ load test_helper
assert_success
is_valid_json
assert_json_value ".data.account_id" "2914079"
assert_json_value ".data.bucket_id" "41746046"
assert_json_value ".data.project_id" "41746046"
assert_json_value ".data.type" "project"
}

Expand All @@ -116,7 +116,7 @@ load test_helper
run basecamp url parse "https://3.basecamp.com/123/buckets/456/todos" --json
assert_success
is_valid_json
assert_json_value ".data.bucket_id" "456"
assert_json_value ".data.project_id" "456"
assert_json_value ".data.type" "todos"
assert_json_value ".data.recording_id" "null"
}
Expand Down
20 changes: 1 addition & 19 deletions e2e/webhooks.bats
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,6 @@ load test_helper

# Flag parsing errors

@test "webhooks --project without value shows error" {
create_credentials
create_global_config '{"account_id": 99999}'

run basecamp webhooks --project
assert_failure
assert_output_contains "--project requires a value"
}

@test "webhooks create --url without value shows error" {
create_credentials
create_global_config '{"account_id": 99999, "project_id": 123}'
Expand All @@ -36,15 +27,6 @@ load test_helper

# Missing context errors

@test "webhooks without project shows error" {
create_credentials
create_global_config '{"account_id": 99999}'

run basecamp webhooks
assert_failure
assert_output_contains "project"
}

@test "webhooks show without id shows error" {
create_credentials
create_global_config '{"account_id": 99999, "project_id": 123}'
Expand Down Expand Up @@ -127,7 +109,7 @@ load test_helper
create_credentials
create_global_config '{"account_id": 99999}'

run basecamp webhooks
run basecamp webhooks create
assert_failure
assert_json_value '.ok' 'false'
assert_json_value '.code' 'usage'
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/basecamp/basecamp-cli
go 1.26

require (
github.com/basecamp/basecamp-sdk/go v0.0.0-20260224005845-4095cc34c3f9
github.com/basecamp/basecamp-sdk/go v0.0.0-20260227230418-6b4c632286aa
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/glamour v0.10.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3v
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/basecamp/basecamp-sdk/go v0.0.0-20260224005845-4095cc34c3f9 h1:99VXGJSnUIO37oOqRbvZD8VXVYCnjp5Zli8M6WKIwFI=
github.com/basecamp/basecamp-sdk/go v0.0.0-20260224005845-4095cc34c3f9/go.mod h1:rd2pK2uDq7yEzwXj9UOp99XwOF9MFPzbsz9jBqpGUCw=
github.com/basecamp/basecamp-sdk/go v0.0.0-20260227230418-6b4c632286aa h1:UMkyWqFoJiWeruGijGOc/Qrtdvp1uEH4sJuualj8Exc=
github.com/basecamp/basecamp-sdk/go v0.0.0-20260227230418-6b4c632286aa/go.mod h1:rd2pK2uDq7yEzwXj9UOp99XwOF9MFPzbsz9jBqpGUCw=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
Expand Down
1 change: 0 additions & 1 deletion internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ func Execute() {
cmd.AddCommand(commands.NewTemplatesCmd())
cmd.AddCommand(commands.NewLineupCmd())
cmd.AddCommand(commands.NewTimesheetCmd())
cmd.AddCommand(commands.NewClockCmd())
cmd.AddCommand(commands.NewBoostsCmd())
cmd.AddCommand(commands.NewBoostShortcutCmd())
cmd.AddCommand(commands.NewTodosetsCmd())
Expand Down
46 changes: 10 additions & 36 deletions internal/commands/boost.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Use 'basecamp boost delete <boost-id>' to remove a boost.`,
newBoostListCmd(&project),
newBoostShowCmd(&project),
newBoostCreateCmd(&project),
newBoostDeleteCmd(&project),
newBoostDeleteCmd(),
)

return cmd
Expand Down Expand Up @@ -96,7 +96,6 @@ func runBoostList(cmd *cobra.Command, app *appctx.App, recording, project, event
return err
}

bucketID, _ := strconv.ParseInt(resolvedProjectID, 10, 64)
recordingIDInt, err := strconv.ParseInt(recordingID, 10, 64)
if err != nil {
return output.ErrUsage("Invalid recording ID")
Expand All @@ -108,7 +107,7 @@ func runBoostList(cmd *cobra.Command, app *appctx.App, recording, project, event
return output.ErrUsage("Invalid event ID")
}

result, err := app.Account().Boosts().ListEvent(cmd.Context(), bucketID, recordingIDInt, eventIDInt)
result, err := app.Account().Boosts().ListEvent(cmd.Context(), recordingIDInt, eventIDInt)
if err != nil {
return convertSDKError(err)
}
Expand All @@ -127,7 +126,7 @@ func runBoostList(cmd *cobra.Command, app *appctx.App, recording, project, event
)
}

result, err := app.Account().Boosts().ListRecording(cmd.Context(), bucketID, recordingIDInt)
result, err := app.Account().Boosts().ListRecording(cmd.Context(), recordingIDInt)
if err != nil {
return convertSDKError(err)
}
Expand Down Expand Up @@ -186,13 +185,12 @@ You can pass either a boost ID or a Basecamp URL:
return err
}

bucketID, _ := strconv.ParseInt(resolvedProjectID, 10, 64)
boostIDInt, err := strconv.ParseInt(boostID, 10, 64)
if err != nil {
return output.ErrUsage("Invalid boost ID")
}

boost, err := app.Account().Boosts().Get(cmd.Context(), bucketID, boostIDInt)
boost, err := app.Account().Boosts().Get(cmd.Context(), boostIDInt)
if err != nil {
return convertSDKError(err)
}
Expand Down Expand Up @@ -274,7 +272,6 @@ func runBoostCreate(cmd *cobra.Command, app *appctx.App, recording, project, con
return err
}

bucketID, _ := strconv.ParseInt(resolvedProjectID, 10, 64)
recordingIDInt, err := strconv.ParseInt(recordingID, 10, 64)
if err != nil {
return output.ErrUsage("Invalid recording ID")
Expand All @@ -286,7 +283,7 @@ func runBoostCreate(cmd *cobra.Command, app *appctx.App, recording, project, con
return output.ErrUsage("Invalid event ID")
}

boost, err := app.Account().Boosts().CreateEvent(cmd.Context(), bucketID, recordingIDInt, eventIDInt, content)
boost, err := app.Account().Boosts().CreateEvent(cmd.Context(), recordingIDInt, eventIDInt, content)
if err != nil {
return convertSDKError(err)
}
Expand All @@ -305,7 +302,7 @@ func runBoostCreate(cmd *cobra.Command, app *appctx.App, recording, project, con
)
}

boost, err := app.Account().Boosts().CreateRecording(cmd.Context(), bucketID, recordingIDInt, content)
boost, err := app.Account().Boosts().CreateRecording(cmd.Context(), recordingIDInt, content)
if err != nil {
return convertSDKError(err)
}
Expand All @@ -329,14 +326,14 @@ func runBoostCreate(cmd *cobra.Command, app *appctx.App, recording, project, con
)
}

func newBoostDeleteCmd(project *string) *cobra.Command {
func newBoostDeleteCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "delete <boost-id|url>",
Short: "Delete a boost",
Long: `Delete a boost from a recording.

You can pass either a boost ID or a Basecamp URL:
basecamp boost delete 789 --project my-project
basecamp boost delete 789
basecamp boost delete https://3.basecamp.com/123/buckets/456/boosts/789`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
Expand All @@ -345,37 +342,14 @@ You can pass either a boost ID or a Basecamp URL:
return err
}

boostID, urlProjectID := extractWithProject(args[0])

projectID := *project
if projectID == "" && urlProjectID != "" {
projectID = urlProjectID
}
if projectID == "" {
projectID = app.Flags.Project
}
if projectID == "" {
projectID = app.Config.ProjectID
}
if projectID == "" {
if err := ensureProject(cmd, app); err != nil {
return err
}
projectID = app.Config.ProjectID
}

resolvedProjectID, _, err := app.Names.ResolveProject(cmd.Context(), projectID)
if err != nil {
return err
}
boostID := extractID(args[0])

bucketID, _ := strconv.ParseInt(resolvedProjectID, 10, 64)
boostIDInt, err := strconv.ParseInt(boostID, 10, 64)
if err != nil {
return output.ErrUsage("Invalid boost ID")
}

err = app.Account().Boosts().Delete(cmd.Context(), bucketID, boostIDInt)
err = app.Account().Boosts().Delete(cmd.Context(), boostIDInt)
if err != nil {
return convertSDKError(err)
}
Expand Down
Loading
Loading