Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
48 changes: 48 additions & 0 deletions .surface
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ ARG basecamp schedule update 00 <id|url>
ARG basecamp search 00 <query>
ARG basecamp show 00 [type]
ARG basecamp show 01 <id|url>
ARG basecamp spotlight 00 <id|url>
ARG basecamp subscriptions add 00 <id|url>
ARG basecamp subscriptions add 01 [person_ids]
ARG basecamp subscriptions remove 00 <id|url>
Expand Down Expand Up @@ -463,6 +464,7 @@ ARG basecamp tools update 00 <id>
ARG basecamp tools update 01 <title>
ARG basecamp tui 00 [url]
ARG basecamp unassign 00 <id|url>...
ARG basecamp unspotlight 00 <id|url>
ARG basecamp upload 00 <file>
ARG basecamp uploads create 00 <file>
ARG basecamp uploads show 00 <id|url>
Expand Down Expand Up @@ -1066,6 +1068,7 @@ CMD basecamp setup grok
CMD basecamp show
CMD basecamp skill
CMD basecamp skill install
CMD basecamp spotlight
CMD basecamp subscriptions
CMD basecamp subscriptions add
CMD basecamp subscriptions remove
Expand Down Expand Up @@ -1183,6 +1186,7 @@ CMD basecamp tools trash
CMD basecamp tools update
CMD basecamp tui
CMD basecamp unassign
CMD basecamp unspotlight
CMD basecamp upgrade
CMD basecamp upload
CMD basecamp uploads
Expand Down Expand Up @@ -14171,6 +14175,27 @@ FLAG basecamp skill install --stats type=bool
FLAG basecamp skill install --styled type=bool
FLAG basecamp skill install --todolist type=string
FLAG basecamp skill install --verbose type=count
FLAG basecamp spotlight --account type=string
FLAG basecamp spotlight --agent type=bool
FLAG basecamp spotlight --cache-dir type=string
FLAG basecamp spotlight --count type=bool
FLAG basecamp spotlight --help type=bool
FLAG basecamp spotlight --hints type=bool
FLAG basecamp spotlight --ids-only type=bool
FLAG basecamp spotlight --in type=string
FLAG basecamp spotlight --jq type=string
FLAG basecamp spotlight --json type=bool
FLAG basecamp spotlight --markdown type=bool
FLAG basecamp spotlight --md type=bool
FLAG basecamp spotlight --no-hints type=bool
FLAG basecamp spotlight --no-stats type=bool
FLAG basecamp spotlight --profile type=string
FLAG basecamp spotlight --project type=string
FLAG basecamp spotlight --quiet type=bool
FLAG basecamp spotlight --stats type=bool
FLAG basecamp spotlight --styled type=bool
FLAG basecamp spotlight --todolist type=string
FLAG basecamp spotlight --verbose type=count
FLAG basecamp subscriptions --account type=string
FLAG basecamp subscriptions --agent type=bool
FLAG basecamp subscriptions --cache-dir type=string
Expand Down Expand Up @@ -16845,6 +16870,27 @@ FLAG basecamp unassign --step type=bool
FLAG basecamp unassign --styled type=bool
FLAG basecamp unassign --todolist type=string
FLAG basecamp unassign --verbose type=count
FLAG basecamp unspotlight --account type=string
FLAG basecamp unspotlight --agent type=bool
FLAG basecamp unspotlight --cache-dir type=string
FLAG basecamp unspotlight --count type=bool
FLAG basecamp unspotlight --help type=bool
FLAG basecamp unspotlight --hints type=bool
FLAG basecamp unspotlight --ids-only type=bool
FLAG basecamp unspotlight --in type=string
FLAG basecamp unspotlight --jq type=string
FLAG basecamp unspotlight --json type=bool
FLAG basecamp unspotlight --markdown type=bool
FLAG basecamp unspotlight --md type=bool
FLAG basecamp unspotlight --no-hints type=bool
FLAG basecamp unspotlight --no-stats type=bool
FLAG basecamp unspotlight --profile type=string
FLAG basecamp unspotlight --project type=string
FLAG basecamp unspotlight --quiet type=bool
FLAG basecamp unspotlight --stats type=bool
FLAG basecamp unspotlight --styled type=bool
FLAG basecamp unspotlight --todolist type=string
FLAG basecamp unspotlight --verbose type=count
FLAG basecamp upgrade --account type=string
FLAG basecamp upgrade --agent type=bool
FLAG basecamp upgrade --cache-dir type=string
Expand Down Expand Up @@ -19865,6 +19911,7 @@ SUB basecamp setup grok
SUB basecamp show
SUB basecamp skill
SUB basecamp skill install
SUB basecamp spotlight
SUB basecamp subscriptions
SUB basecamp subscriptions add
SUB basecamp subscriptions remove
Expand Down Expand Up @@ -19982,6 +20029,7 @@ SUB basecamp tools trash
SUB basecamp tools update
SUB basecamp tui
SUB basecamp unassign
SUB basecamp unspotlight
SUB basecamp upgrade
SUB basecamp upload
SUB basecamp uploads
Expand Down
9 changes: 6 additions & 3 deletions API-COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ Coverage of Basecamp 3 API endpoints. Source: [bc3-api/sections](https://github.

| Status | Sections | Endpoints |
|--------|----------|-----------|
| ✅ Implemented | 54 | 209 |
| ✅ Implemented | 55 | 211 |
| ⚠️ Blocked | 0 | 0 |
| ⏭️ Out of scope | 4 | 12 |
| **Total tracked** | **58** | **221** |
| **Total tracked** | **59** | **223** |

**209 of 209 tracked in-scope endpoints.** The two embed endpoints bc3 documents
**211 of 211 tracked in-scope endpoints.** Spotlighting a recording on its
project or template home page (`POST`/`DELETE /recordings/:id/spotlight.json`,
SDK v0.27.0) lands as `spotlight` and `unspotlight`. The two embed endpoints bc3 documents
in `embeds.md` — `POST /embeds.json` and `GET /embeds/:sgid.json` — land as
`embeds`. The three Pings endpoints bc3#13943
documented — `GET /circles.json`, `GET /circles/:id.json` and `POST
Expand Down Expand Up @@ -218,6 +220,7 @@ cannot faithfully cover at least one endpoint for a reason outside the CLI. A
| boosts | 7 | `boost`, `react` | ✅ | BC4 | - | list (recording + event), show, create (recording + event), update (`PUT /boosts/{id}`, the creator only; same 16-character cap as create), delete. No account-wide listing — BC5 withdrew `/boosts.json` (basecamp/bc3#12464); temporary, returns via basecamp/bc3#12463 |
| notifications | 2 | `notifications` | ✅ | BC4 | - | list, mark as read (BC5: `bubble_ups`/`scheduled_bubble_ups` sections; `memories` is BC4-only) |
| bubble_ups | 3 | `bubble-up`, `notifications bubbleups` | ✅ | BC5 | - | `bubble-up add`/`remove` create and delete a per-recording bubble-up (`POST`/`DELETE /recordings/{id}/bubble_up.json`); `add --at` schedules. Dedicated list is `notifications bubbleups` (`GET /my/readings/bubble_ups.json`, paginated) plus the `limit_bubble_ups` variant behind `notifications list --limit-bubble-ups`. Per-recording GET is an unrenderable API gap, so there is no `check`. |
| spotlights | 2 | `spotlight`, `unspotlight` | ✅ | BC5 | - | `POST`/`DELETE /recordings/{id}/spotlight.json` put a recording's card on, or take it off, its project or template home page; both idempotent. No endpoint reads spotlight state or lists spotlights, so there is no `check` or list. Reordering spotlights (`PUT /recordings/:id/spotlight/position`) has no SDK operation, so it is not covered |
| **Cards (Kanban)** |
| card_tables | 3 | `cards` | ✅ | BC4 | - | Accessed via project dock |
| card_table_cards | 9 | `cards` | ✅ | BC4 | - | list, show, create, update, move |
Expand Down
29 changes: 28 additions & 1 deletion e2e/smoke/smoke_misc_write.bats
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env bats
# smoke_misc_write.bats - Level 1: Schedule settings, recordings trash/restore
# smoke_misc_write.bats - Level 1: Schedule settings, recordings trash/restore, spotlight

load smoke_helper

Expand Down Expand Up @@ -82,3 +82,30 @@ setup_file() {
assert_success
assert_json_value '.ok' 'true'
}

@test "spotlight and unspotlight a recording" {
ensure_todolist || mark_unverifiable "Cannot discover todolist for spotlight"

# A throwaway todo: unspotlight would otherwise remove a spotlight someone
# set, and there is no read API to put it back.
local todo_out
todo_out=$(basecamp todos create "Spotlight target $(date +%s)" --list "$QA_TODOLIST" -p "$QA_PROJECT" --json 2>/dev/null) || {
mark_unverifiable "Cannot create todo for spotlight test"
return
}
local todo_id
todo_id=$(echo "$todo_out" | jq -r '.data.id // empty')
[[ -n "$todo_id" ]] || mark_unverifiable "No todo ID returned"

run_smoke basecamp spotlight "$todo_id" --json
assert_success
assert_json_value '.ok' 'true'
assert_json_value '.data.id' "$todo_id"

run_smoke basecamp unspotlight "$todo_id" --json
assert_success
assert_json_value '.ok' 'true'
assert_json_value '.data.spotlighted' 'false'

basecamp recordings trash "$todo_id" -p "$QA_PROJECT" --json >/dev/null 2>&1 || true
}
23 changes: 23 additions & 0 deletions e2e/spotlight.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bats
# spotlight.bats - Test spotlight and unspotlight error handling

load test_helper


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

run basecamp spotlight
assert_failure
assert_output_contains "ID required"
}

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

run basecamp unspotlight
assert_failure
assert_output_contains "ID required"
}
2 changes: 2 additions & 0 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,8 @@ func Execute() {
cmd.AddCommand(commands.NewCommentsCmd())
cmd.AddCommand(commands.NewAssignCmd())
cmd.AddCommand(commands.NewUnassignCmd())
cmd.AddCommand(commands.NewSpotlightCmd())
cmd.AddCommand(commands.NewUnspotlightCmd())
cmd.AddCommand(commands.NewMessagesCmd())
cmd.AddCommand(commands.NewCardsCmd())
cmd.AddCommand(commands.NewURLCmd())
Expand Down
2 changes: 2 additions & 0 deletions internal/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ func CommandCategories() []CommandCategory {
Commands: []CommandInfo{
{Name: "assign", Category: "shortcut", Description: "Assign someone to a to-do, card, or step"},
{Name: "unassign", Category: "shortcut", Description: "Remove assignment from a to-do, card, or step"},
{Name: "spotlight", Category: "shortcut", Description: "Spotlight a recording on its project page"},
{Name: "unspotlight", Category: "shortcut", Description: "Remove a recording's spotlight"},
{Name: "attach", Category: "shortcut", Description: "Upload and stage an attachment"},
{Name: "upload", Category: "shortcut", Description: "Upload a file to Docs & Files"},
},
Expand Down
2 changes: 2 additions & 0 deletions internal/commands/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ func buildRootWithAllCommands() *cobra.Command {
root.AddCommand(commands.NewCommentsCmd())
root.AddCommand(commands.NewAssignCmd())
root.AddCommand(commands.NewUnassignCmd())
root.AddCommand(commands.NewSpotlightCmd())
root.AddCommand(commands.NewUnspotlightCmd())
root.AddCommand(commands.NewMessagesCmd())
root.AddCommand(commands.NewCardsCmd())
root.AddCommand(commands.NewURLCmd())
Expand Down
9 changes: 7 additions & 2 deletions internal/commands/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,9 @@ You can pass either a message ID or a Basecamp URL:
return cmd
}

const messagesPinAgentNotes = "Pinning is not spotlighting: a pin keeps a message at the top of its message board; " +
"basecamp spotlight puts any recording on its project's home page, and basecamp unspotlight takes it off"

func newMessagesPinCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "pin <id|url>",
Expand All @@ -823,7 +826,8 @@ func newMessagesPinCmd() *cobra.Command {
You can pass either a message ID or a Basecamp URL:
basecamp messages pin 789
basecamp messages pin https://3.basecamp.com/123/buckets/456/messages/789`,
Args: cobra.ExactArgs(1),
Annotations: map[string]string{"agent_notes": messagesPinAgentNotes},
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
app := appctx.FromContext(cmd.Context())

Expand Down Expand Up @@ -876,7 +880,8 @@ func newMessagesUnpinCmd() *cobra.Command {
You can pass either a message ID or a Basecamp URL:
basecamp messages unpin 789
basecamp messages unpin https://3.basecamp.com/123/buckets/456/messages/789`,
Args: cobra.ExactArgs(1),
Annotations: map[string]string{"agent_notes": messagesPinAgentNotes},
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
app := appctx.FromContext(cmd.Context())

Expand Down
Loading
Loading