diff --git a/.surface b/.surface index 570ff2db6..6d55c905b 100644 --- a/.surface +++ b/.surface @@ -149,6 +149,7 @@ CMD basecamp help CMD basecamp lineup CMD basecamp lineup create CMD basecamp lineup delete +CMD basecamp lineup list CMD basecamp lineup update CMD basecamp login CMD basecamp logout @@ -3606,6 +3607,26 @@ FLAG basecamp lineup delete --stats type=bool FLAG basecamp lineup delete --styled type=bool FLAG basecamp lineup delete --todolist type=string FLAG basecamp lineup delete --verbose type=count +FLAG basecamp lineup list --account type=string +FLAG basecamp lineup list --agent type=bool +FLAG basecamp lineup list --cache-dir type=string +FLAG basecamp lineup list --count type=bool +FLAG basecamp lineup list --hints type=bool +FLAG basecamp lineup list --ids-only type=bool +FLAG basecamp lineup list --in type=string +FLAG basecamp lineup list --jq type=string +FLAG basecamp lineup list --json type=bool +FLAG basecamp lineup list --markdown type=bool +FLAG basecamp lineup list --md type=bool +FLAG basecamp lineup list --no-hints type=bool +FLAG basecamp lineup list --no-stats type=bool +FLAG basecamp lineup list --profile type=string +FLAG basecamp lineup list --project type=string +FLAG basecamp lineup list --quiet type=bool +FLAG basecamp lineup list --stats type=bool +FLAG basecamp lineup list --styled type=bool +FLAG basecamp lineup list --todolist type=string +FLAG basecamp lineup list --verbose type=count FLAG basecamp lineup update --account type=string FLAG basecamp lineup update --agent type=bool FLAG basecamp lineup update --cache-dir type=string @@ -7326,6 +7347,7 @@ SUB basecamp help SUB basecamp lineup SUB basecamp lineup create SUB basecamp lineup delete +SUB basecamp lineup list SUB basecamp lineup update SUB basecamp login SUB basecamp logout diff --git a/API-COVERAGE.md b/API-COVERAGE.md index 880b945a9..5e292b112 100644 --- a/API-COVERAGE.md +++ b/API-COVERAGE.md @@ -6,15 +6,15 @@ Coverage of Basecamp 3 API endpoints. Source: [bc3-api/sections](https://github. | Status | Sections | Endpoints | |--------|----------|-----------| -| ✅ Implemented | 37 | 136 | +| ✅ Implemented | 37 | 137 | | ⏭️ Out of scope | 4 | 12 | -| **Total (docs)** | **41** | **148** | +| **Total (docs)** | **41** | **149** | -**100% coverage of in-scope API** (136/136 endpoints) +**100% coverage of in-scope API** (137/137 endpoints) Out-of-scope sections are excluded from parity totals and scripts: chatbots (different auth), legacy Clientside (deprecated) -**SDK version:** v0.4.0 — uniform pagination (Limit/Page) on all List methods; `types.FlexibleTime` and `types.FlexInt` for wire format handling. +**SDK version:** v0.6.0 — uniform pagination (Limit/Page) on all List methods; `types.FlexibleTime` and `types.FlexInt` for wire format handling. ## Coverage by Section @@ -77,7 +77,7 @@ Out-of-scope sections are excluded from parity totals and scripts: chatbots (dif | **Chatbots** | | chatbots | 10 | - | ⏭️ | skip | Requires chatbot key, not OAuth (see notes) | | **Lineup** | -| lineup_markers | 3 | `lineup` | ✅ | - | create, update, delete markers | +| lineup_markers | 4 | `lineup` | ✅ | - | list, create, update, delete markers | | **Reference Only** | | basecamps | 0 | - | - | - | Documentation reference, no endpoints | | rich_text | 0 | - | - | - | Documentation reference, no endpoints | diff --git a/e2e/smoke/.qa-allowlist b/e2e/smoke/.qa-allowlist index ee955b114..337f7f3ac 100644 --- a/e2e/smoke/.qa-allowlist +++ b/e2e/smoke/.qa-allowlist @@ -22,6 +22,9 @@ test_timesheet_item_returns_timesheet_item_detail # timesheets not enabled — s # Lineup API may not exist on all environments test_lineup_create_creates_a_lineup_marker # API not available on some servers — smoke +test_lineup_list_returns_markers # API not available on some servers — smoke +test_lineup_update_updates_a_lineup_marker # depends on lineup create — smoke +test_lineup_delete_removes_a_lineup_marker # depends on lineup create — smoke # SDK bug: TodolistOrGroup0 union type expects wrapped JSON, API returns flat test_todolists_show_returns_todolist_detail # SDK deserialization mismatch — smoke diff --git a/e2e/smoke/smoke_lifecycle.bats b/e2e/smoke/smoke_lifecycle.bats index b7823366a..0cf2f7620 100644 --- a/e2e/smoke/smoke_lifecycle.bats +++ b/e2e/smoke/smoke_lifecycle.bats @@ -128,16 +128,6 @@ load smoke_helper mark_out_of_scope "Bulk completion — destructive, no undo" } -# --- Structurally untestable (API returns 204 No Content, no ID to chain) --- - -@test "lineup update is out of scope" { - mark_out_of_scope "API returns 204 No Content — no ID to chain" -} - -@test "lineup delete is out of scope" { - mark_out_of_scope "API returns 204 No Content — no ID to chain" -} - # --- Code-path equivalence: docs group shares implementation with files --- @test "docs archive is out of scope" { diff --git a/e2e/smoke/smoke_lineup.bats b/e2e/smoke/smoke_lineup.bats index 18ae5544e..634ef23e4 100644 --- a/e2e/smoke/smoke_lineup.bats +++ b/e2e/smoke/smoke_lineup.bats @@ -1,9 +1,5 @@ #!/usr/bin/env bats -# smoke_lineup.bats - Level 1: Lineup create -# -# Note: lineup update/delete are OOS — the API returns 204 No Content -# (no ID in response), making them structurally untestable without a -# fragile list-after-create workaround. See smoke_lifecycle.bats. +# smoke_lineup.bats - Level 1: Lineup CRUD lifecycle load smoke_helper @@ -15,8 +11,50 @@ setup_file() { @test "lineup create creates a lineup marker" { local future_date future_date=$(date -v+7d +%Y-%m-%d 2>/dev/null || date -d "+7 days" +%Y-%m-%d) - run_smoke basecamp lineup create "Smoke lineup $(date +%s)" "$future_date" --json + local marker_name="Smoke lineup $(date +%s)" + run_smoke basecamp lineup create "$marker_name" "$future_date" --json # Lineup API may not exist on all environments (404 → validation error) [[ "$status" -ne 0 ]] && mark_unverifiable "Lineup API not available" assert_json_value '.ok' 'true' + echo "$marker_name" > "$BATS_FILE_TMPDIR/marker_name" +} + +@test "lineup list returns markers" { + run_smoke basecamp lineup list --json + [[ "$status" -ne 0 ]] && mark_unverifiable "Lineup API not available" + assert_json_value '.ok' 'true' +} + +@test "lineup update updates a lineup marker" { + local name_file="$BATS_FILE_TMPDIR/marker_name" + [[ ! -f "$name_file" ]] && mark_unverifiable "No marker created by earlier test" + local marker_name + marker_name=$(cat "$name_file") + run_smoke basecamp lineup list --json + [[ "$status" -ne 0 ]] && mark_unverifiable "Lineup API not available" + assert_json_value '.ok' 'true' + local marker_id + marker_id=$(echo "$output" | jq -r --arg name "$marker_name" '[.data[] | select(.name == $name)][0].id // empty') + [[ -z "$marker_id" ]] && mark_unverifiable "No markers found to update" + local updated_name="Updated $marker_name" + run_smoke basecamp lineup update "$marker_id" "$updated_name" --json + assert_success + assert_json_value '.ok' 'true' + echo "$updated_name" > "$BATS_FILE_TMPDIR/marker_name" +} + +@test "lineup delete removes a lineup marker" { + local name_file="$BATS_FILE_TMPDIR/marker_name" + [[ ! -f "$name_file" ]] && mark_unverifiable "No marker created by earlier test" + local marker_name + marker_name=$(cat "$name_file") + run_smoke basecamp lineup list --json + [[ "$status" -ne 0 ]] && mark_unverifiable "Lineup API not available" + assert_json_value '.ok' 'true' + local marker_id + marker_id=$(echo "$output" | jq -r --arg name "$marker_name" '[.data[] | select(.name == $name)][0].id // empty') + [[ -z "$marker_id" ]] && mark_unverifiable "No markers found to delete" + run_smoke basecamp lineup delete "$marker_id" --json + assert_success + assert_json_value '.ok' 'true' } diff --git a/go.mod b/go.mod index 84553ebd3..51f57371a 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( charm.land/bubbles/v2 v2.0.0 charm.land/bubbletea/v2 v2.0.2 charm.land/lipgloss/v2 v2.0.2 - github.com/basecamp/basecamp-sdk/go v0.6.1-0.20260318221909-49475983b9c8 + github.com/basecamp/basecamp-sdk/go v0.6.1-0.20260319050216-1c33ce43c305 github.com/basecamp/cli v0.1.1 github.com/charmbracelet/bubbles v1.0.0 github.com/charmbracelet/glamour v1.0.0 diff --git a/go.sum b/go.sum index c110e52b7..b679dedcf 100644 --- a/go.sum +++ b/go.sum @@ -25,8 +25,8 @@ github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w= 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.6.1-0.20260318221909-49475983b9c8 h1:WsLan1O+1GWBSQEn+dTd63roBunjojffjaoKnQ4g33U= -github.com/basecamp/basecamp-sdk/go v0.6.1-0.20260318221909-49475983b9c8/go.mod h1:g05DM58QkUm4/mvBAvRiugPw+F4trliuGkRGg8y+Th4= +github.com/basecamp/basecamp-sdk/go v0.6.1-0.20260319050216-1c33ce43c305 h1:NuMRr9mXUhSZAxxxLLfOAhiCTeYDlt/h7HaoarRLGjk= +github.com/basecamp/basecamp-sdk/go v0.6.1-0.20260319050216-1c33ce43c305/go.mod h1:g05DM58QkUm4/mvBAvRiugPw+F4trliuGkRGg8y+Th4= github.com/basecamp/cli v0.1.1 h1:FAF3M09xo1m7gJJXf38glCkT50ZUuvz+31f+c3R3zcc= github.com/basecamp/cli v0.1.1/go.mod h1:NTHe+keCTGI2qM5sMXdkUN0QgU3zGbwnBxcmg8vD5QU= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= diff --git a/internal/commands/commands.go b/internal/commands/commands.go index 3edea001b..9c22f6b56 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -85,7 +85,7 @@ func CommandCategories() []CommandCategory { {Name: "people", Category: "organization", Description: "Manage people and access", Actions: []string{"list", "show", "pingable", "add", "remove"}}, {Name: "templates", Category: "organization", Description: "Manage project templates", Actions: []string{"list", "show", "create", "update", "delete", "construct"}}, {Name: "webhooks", Category: "organization", Description: "Manage webhooks", Actions: []string{"list", "show", "create", "update", "delete"}}, - {Name: "lineup", Category: "organization", Description: "Manage lineup markers", Actions: []string{"create", "update", "delete"}}, + {Name: "lineup", Category: "organization", Description: "Manage lineup markers", Actions: []string{"list", "create", "update", "delete"}}, }, }, { diff --git a/internal/commands/lineup.go b/internal/commands/lineup.go index 22e1ff725..ec6713c5c 100644 --- a/internal/commands/lineup.go +++ b/internal/commands/lineup.go @@ -29,6 +29,7 @@ They apply to the entire Basecamp account.`, } cmd.AddCommand( + newLineupListCmd(), newLineupCreateCmd(), newLineupUpdateCmd(), newLineupDeleteCmd(), @@ -37,6 +38,36 @@ They apply to the entire Basecamp account.`, return cmd } +func newLineupListCmd() *cobra.Command { + return &cobra.Command{ + Use: "list", + Short: "List lineup markers", + RunE: func(cmd *cobra.Command, args []string) error { + app := appctx.FromContext(cmd.Context()) + + if err := ensureAccount(cmd, app); err != nil { + return err + } + + result, err := app.Account().Lineup().ListMarkers(cmd.Context()) + if err != nil { + return convertSDKError(err) + } + + return app.OK(result.Markers, + output.WithSummary(fmt.Sprintf("%d lineup markers", len(result.Markers))), + output.WithBreadcrumbs( + output.Breadcrumb{ + Action: "create", + Cmd: "basecamp lineup create ", + Description: "Create new marker", + }, + ), + ) + }, + } +} + func newLineupCreateCmd() *cobra.Command { cmd := &cobra.Command{ Use: "create ", diff --git a/internal/version/sdk-provenance.json b/internal/version/sdk-provenance.json index b497b26c6..3b9233a76 100644 --- a/internal/version/sdk-provenance.json +++ b/internal/version/sdk-provenance.json @@ -1,9 +1,9 @@ { "sdk": { "module": "github.com/basecamp/basecamp-sdk/go", - "version": "v0.6.1-0.20260318221909-49475983b9c8", - "revision": "49475983b9c8", - "updated_at": "2026-03-18T22:19:09Z" + "version": "v0.6.1-0.20260319050216-1c33ce43c305", + "revision": "1c33ce43c305", + "updated_at": "2026-03-19T05:02:16Z" }, "api": { "repo": "basecamp/bc3", diff --git a/skills/basecamp/SKILL.md b/skills/basecamp/SKILL.md index cbbbb1787..c8d76e186 100644 --- a/skills/basecamp/SKILL.md +++ b/skills/basecamp/SKILL.md @@ -573,6 +573,7 @@ basecamp subscriptions remove --people 1,2,3 # Remove people ### Lineup (Account-wide Markers) ```bash +basecamp lineup list # List all markers basecamp lineup create "Milestone" "2024-03-15" # Create marker basecamp lineup create "Launch" tomorrow # Natural date parsing basecamp lineup update "New Name" "+7"