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
166 changes: 166 additions & 0 deletions .surface
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@ ARG basecamp msgs update 00 <id|url>
ARG basecamp notes set 00 [content]
ARG basecamp notifications read 00 <id>...
ARG basecamp people add 00 <person-id>...
ARG basecamp people clients add 00 <id|email|name>...
ARG basecamp people clients invite 00 <invitee>...
ARG basecamp people clients remove 00 <id|email|name>...
ARG basecamp people ooo 00 [me]
ARG basecamp people out-of-office 00 [me]
ARG basecamp people remove 00 <person-id>...
Expand Down Expand Up @@ -925,6 +928,13 @@ CMD basecamp notifications list
CMD basecamp notifications read
CMD basecamp people
CMD basecamp people add
CMD basecamp people clients
CMD basecamp people clients add
CMD basecamp people clients disable
CMD basecamp people clients enable
CMD basecamp people clients invite
CMD basecamp people clients list
CMD basecamp people clients remove
CMD basecamp people list
CMD basecamp people ooo
CMD basecamp people out-of-office
Expand Down Expand Up @@ -11742,6 +11752,155 @@ FLAG basecamp people add --stats type=bool
FLAG basecamp people add --styled type=bool
FLAG basecamp people add --todolist type=string
FLAG basecamp people add --verbose type=count
FLAG basecamp people clients --account type=string
FLAG basecamp people clients --agent type=bool
FLAG basecamp people clients --cache-dir type=string
FLAG basecamp people clients --count type=bool
FLAG basecamp people clients --help type=bool
FLAG basecamp people clients --hints type=bool
FLAG basecamp people clients --ids-only type=bool
FLAG basecamp people clients --in type=string
FLAG basecamp people clients --jq type=string
FLAG basecamp people clients --json type=bool
FLAG basecamp people clients --markdown type=bool
FLAG basecamp people clients --md type=bool
FLAG basecamp people clients --no-hints type=bool
FLAG basecamp people clients --no-stats type=bool
FLAG basecamp people clients --profile type=string
FLAG basecamp people clients --project type=string
FLAG basecamp people clients --quiet type=bool
FLAG basecamp people clients --stats type=bool
FLAG basecamp people clients --styled type=bool
FLAG basecamp people clients --todolist type=string
FLAG basecamp people clients --verbose type=count
FLAG basecamp people clients add --account type=string
FLAG basecamp people clients add --agent type=bool
FLAG basecamp people clients add --cache-dir type=string
FLAG basecamp people clients add --count type=bool
FLAG basecamp people clients add --help type=bool
FLAG basecamp people clients add --hints type=bool
FLAG basecamp people clients add --ids-only type=bool
FLAG basecamp people clients add --in type=string
FLAG basecamp people clients add --jq type=string
FLAG basecamp people clients add --json type=bool
FLAG basecamp people clients add --markdown type=bool
FLAG basecamp people clients add --md type=bool
FLAG basecamp people clients add --no-hints type=bool
FLAG basecamp people clients add --no-stats type=bool
FLAG basecamp people clients add --profile type=string
FLAG basecamp people clients add --project type=string
FLAG basecamp people clients add --quiet type=bool
FLAG basecamp people clients add --stats type=bool
FLAG basecamp people clients add --styled type=bool
FLAG basecamp people clients add --todolist type=string
FLAG basecamp people clients add --verbose type=count
FLAG basecamp people clients disable --account type=string
FLAG basecamp people clients disable --agent type=bool
FLAG basecamp people clients disable --cache-dir type=string
FLAG basecamp people clients disable --count type=bool
FLAG basecamp people clients disable --help type=bool
FLAG basecamp people clients disable --hints type=bool
FLAG basecamp people clients disable --ids-only type=bool
FLAG basecamp people clients disable --in type=string
FLAG basecamp people clients disable --jq type=string
FLAG basecamp people clients disable --json type=bool
FLAG basecamp people clients disable --markdown type=bool
FLAG basecamp people clients disable --md type=bool
FLAG basecamp people clients disable --no-hints type=bool
FLAG basecamp people clients disable --no-stats type=bool
FLAG basecamp people clients disable --profile type=string
FLAG basecamp people clients disable --project type=string
FLAG basecamp people clients disable --quiet type=bool
FLAG basecamp people clients disable --stats type=bool
FLAG basecamp people clients disable --styled type=bool
FLAG basecamp people clients disable --todolist type=string
FLAG basecamp people clients disable --verbose type=count
FLAG basecamp people clients enable --account type=string
FLAG basecamp people clients enable --agent type=bool
FLAG basecamp people clients enable --cache-dir type=string
FLAG basecamp people clients enable --count type=bool
FLAG basecamp people clients enable --help type=bool
FLAG basecamp people clients enable --hints type=bool
FLAG basecamp people clients enable --ids-only type=bool
FLAG basecamp people clients enable --in type=string
FLAG basecamp people clients enable --jq type=string
FLAG basecamp people clients enable --json type=bool
FLAG basecamp people clients enable --markdown type=bool
FLAG basecamp people clients enable --md type=bool
FLAG basecamp people clients enable --no-hints type=bool
FLAG basecamp people clients enable --no-stats type=bool
FLAG basecamp people clients enable --profile type=string
FLAG basecamp people clients enable --project type=string
FLAG basecamp people clients enable --quiet type=bool
FLAG basecamp people clients enable --stats type=bool
FLAG basecamp people clients enable --styled type=bool
FLAG basecamp people clients enable --todolist type=string
FLAG basecamp people clients enable --verbose type=count
FLAG basecamp people clients invite --account type=string
FLAG basecamp people clients invite --agent type=bool
FLAG basecamp people clients invite --cache-dir type=string
FLAG basecamp people clients invite --company type=string
FLAG basecamp people clients invite --count type=bool
FLAG basecamp people clients invite --help type=bool
FLAG basecamp people clients invite --hints type=bool
FLAG basecamp people clients invite --ids-only type=bool
FLAG basecamp people clients invite --in type=string
FLAG basecamp people clients invite --jq type=string
FLAG basecamp people clients invite --json type=bool
FLAG basecamp people clients invite --markdown type=bool
FLAG basecamp people clients invite --md type=bool
FLAG basecamp people clients invite --no-hints type=bool
FLAG basecamp people clients invite --no-stats type=bool
FLAG basecamp people clients invite --profile type=string
FLAG basecamp people clients invite --project type=string
FLAG basecamp people clients invite --quiet type=bool
FLAG basecamp people clients invite --stats type=bool
FLAG basecamp people clients invite --styled type=bool
FLAG basecamp people clients invite --title type=string
FLAG basecamp people clients invite --todolist type=string
FLAG basecamp people clients invite --verbose type=count
FLAG basecamp people clients list --account type=string
FLAG basecamp people clients list --agent type=bool
FLAG basecamp people clients list --cache-dir type=string
FLAG basecamp people clients list --count type=bool
FLAG basecamp people clients list --help type=bool
FLAG basecamp people clients list --hints type=bool
FLAG basecamp people clients list --ids-only type=bool
FLAG basecamp people clients list --in type=string
FLAG basecamp people clients list --jq type=string
FLAG basecamp people clients list --json type=bool
FLAG basecamp people clients list --markdown type=bool
FLAG basecamp people clients list --md type=bool
FLAG basecamp people clients list --no-hints type=bool
FLAG basecamp people clients list --no-stats type=bool
FLAG basecamp people clients list --profile type=string
FLAG basecamp people clients list --project type=string
FLAG basecamp people clients list --quiet type=bool
FLAG basecamp people clients list --stats type=bool
FLAG basecamp people clients list --styled type=bool
FLAG basecamp people clients list --todolist type=string
FLAG basecamp people clients list --verbose type=count
FLAG basecamp people clients remove --account type=string
FLAG basecamp people clients remove --agent type=bool
FLAG basecamp people clients remove --cache-dir type=string
FLAG basecamp people clients remove --count type=bool
FLAG basecamp people clients remove --help type=bool
FLAG basecamp people clients remove --hints type=bool
FLAG basecamp people clients remove --ids-only type=bool
FLAG basecamp people clients remove --in type=string
FLAG basecamp people clients remove --jq type=string
FLAG basecamp people clients remove --json type=bool
FLAG basecamp people clients remove --markdown type=bool
FLAG basecamp people clients remove --md type=bool
FLAG basecamp people clients remove --no-hints type=bool
FLAG basecamp people clients remove --no-stats type=bool
FLAG basecamp people clients remove --profile type=string
FLAG basecamp people clients remove --project type=string
FLAG basecamp people clients remove --quiet type=bool
FLAG basecamp people clients remove --stats type=bool
FLAG basecamp people clients remove --styled type=bool
FLAG basecamp people clients remove --todolist type=string
FLAG basecamp people clients remove --verbose type=count
FLAG basecamp people list --account type=string
FLAG basecamp people list --agent type=bool
FLAG basecamp people list --all type=bool
Expand Down Expand Up @@ -18435,6 +18594,13 @@ SUB basecamp notifications list
SUB basecamp notifications read
SUB basecamp people
SUB basecamp people add
SUB basecamp people clients
SUB basecamp people clients add
SUB basecamp people clients disable
SUB basecamp people clients enable
SUB basecamp people clients invite
SUB basecamp people clients list
SUB basecamp people clients remove
SUB basecamp people list
SUB basecamp people ooo
SUB basecamp people out-of-office
Expand Down
11 changes: 7 additions & 4 deletions API-COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ Coverage of Basecamp 3 API endpoints. Source: [bc3-api/sections](https://github.

| Status | Sections | Endpoints |
|--------|----------|-----------|
| ✅ Implemented | 50 | 189 |
| ✅ Implemented | 50 | 192 |
| ⚠️ Blocked | 0 | 0 |
| ⏭️ Out of scope | 4 | 12 |
| **Total tracked** | **54** | **201** |
| **Total tracked** | **54** | **204** |

**189 of 189 tracked in-scope endpoints.** SDK v0.16.0 adds the three to-do
**192 of 192 tracked in-scope endpoints.** The client-admission endpoints
basecamp/bc3#13098 added — `PUT /projects/:id/people/client_users.json` and
`POST`/`DELETE /projects/:id/client_enablement.json` — land as `people clients`.
SDK v0.16.0 adds the three to-do
list template-library operations, available through `templates library`,
`templates copy`, and `templates copy-status`. The previous last gap — `GET
/uploads/:id/versions.json` — closed with the v0.14.0 SDK bump. The command
Expand Down Expand Up @@ -211,7 +214,7 @@ cannot faithfully cover at least one endpoint for a reason outside the CLI. A
| drafts | 1 | `drafts` | ✅ | BC5 | - | list unpublished drafts across projects (server caps at 250). Bounded like the account-wide listings; publishing happens through the command for the draft's type |
| my_notes | 2 | `notes` | ✅ | BC5 | - | show, set. A singleton per person, so no id and no listing. Pre-first-write the record does not exist yet and renders as empty rather than 404. `set` writes Markdown as HTML; attachments are out of scope |
| **People** |
| people | 12 | `people`, `me` | ✅ | BC4 | - | list, show, update (edit your own profile via `PUT /my/profile.json`), out-of-office show/set/clear (`GetOutOfOffice`/`EnableOutOfOffice`/`DisableOutOfOffice`), pingable, add, remove (BC5: `tagline` alias of `bio` on person output) |
| people | 15 | `people`, `me` | ✅ | BC4 | - | list, show, update (edit your own profile via `PUT /my/profile.json`), out-of-office show/set/clear (`GetOutOfOffice`/`EnableOutOfOffice`/`DisableOutOfOffice`), pingable, add, remove (BC5: `tagline` alias of `bio` on person output). `people clients` covers the client side: `add`/`remove`/`invite` through `PUT /projects/:id/people/client_users.json` (basecamp/bc3#13098), `enable`/`disable` through `POST`/`DELETE /projects/:id/client_enablement.json`; `list` is the project roster filtered to `client: true` |
| **Search & Recordings** |
| my_assignments | 6 | `assignments` | ✅ | BC4 | - | list (priorities/non-priorities), completed, due (with scope filter), prioritize, deprioritize, reorder. `list` surfaces `priority_recording_id`, which is the only way to address a prioritized card-table step — it appears in no URL |
| search | 2 | `search` | ✅ | BC4 | - | Full-text search + metadata. Filters: `--project`/`--in`, `--type`, `--creator`, `--since` (BC5-only), `--file-type`, `--exclude-chat`. Metadata lists recording/file search types |
Expand Down
80 changes: 80 additions & 0 deletions e2e/people.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env bats
# people.bats - people command error handling (offline: every case resolves
# locally, before any request)

load test_helper


# Help

@test "people clients without subcommand shows help" {
run basecamp people clients
assert_success
assert_output_contains "COMMANDS"
}


# Missing context errors

@test "people clients list without project shows error" {
create_credentials
create_global_config '{"account_id": 99999}'

run basecamp people clients list --json
assert_failure
assert_json_value '.code' 'usage'
assert_json_value '.error' '--project (or --in) is required'
}

@test "people clients add without ids shows error" {
create_credentials
create_global_config '{"account_id": 99999, "project_id": 123}'

run basecamp people clients add --json
assert_failure
assert_json_value '.code' 'usage'
assert_json_value '.error' '<id|email|name>... required'
}


# Invitee parsing

@test "people clients invite rejects a token that is not an address" {
create_credentials
create_global_config '{"account_id": 99999, "project_id": 123}'

run basecamp people clients invite "Annie Bryan" --json
assert_failure
assert_json_value '.code' 'usage'
assert_json_value '.error | contains("Name <email>")' 'true'
}

@test "people clients invite - with empty pipe is a usage error" {
create_credentials
create_global_config '{"account_id": 99999, "project_id": 123}'

run bash -c "printf '' | basecamp people clients invite - --json"
assert_failure
assert_json_value '.code' 'usage'
assert_output_contains "empty"
}

@test "people clients invite - mixed with other invitees is rejected" {
create_credentials
create_global_config '{"account_id": 99999, "project_id": 123}'

run bash -c "printf 'a@example.com' | basecamp people clients invite - b@example.com --json"
assert_failure
assert_json_value '.code' 'usage'
assert_output_contains "cannot be combined"
}

@test "people clients invite --title with several invitees is rejected" {
create_credentials
create_global_config '{"account_id": 99999, "project_id": 123}'

run basecamp people clients invite a@example.com b@example.com --title Owner --json
assert_failure
assert_json_value '.code' 'usage'
assert_output_contains "--title"
}
20 changes: 20 additions & 0 deletions e2e/smoke/smoke_lifecycle.bats
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,26 @@ load smoke_helper
mark_out_of_scope "Modifies project membership"
}

@test "people clients add is out of scope" {
mark_out_of_scope "Modifies project membership"
}

@test "people clients remove is out of scope" {
mark_out_of_scope "Modifies project membership"
}

@test "people clients invite is out of scope" {
mark_out_of_scope "Invites a new client by email — consumes an account seat"
}

@test "people clients enable is out of scope" {
mark_out_of_scope "Reconfigures project-wide client visibility"
}

@test "people clients disable is out of scope" {
mark_out_of_scope "Reconfigures project-wide client visibility"
}

@test "todos sweep is out of scope" {
mark_out_of_scope "Bulk completion — destructive, no undo"
}
Expand Down
12 changes: 12 additions & 0 deletions e2e/smoke/smoke_projects.bats
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ setup_file() {
echo "$output" | jq -r '.data[0].id' > "$BATS_FILE_TMPDIR/project_id"
}

@test "people clients list returns the project's clients" {
local proj_file="$BATS_FILE_TMPDIR/project_id"
[[ -f "$proj_file" ]] || mark_unverifiable "projects list did not produce a project ID"
local proj_id
proj_id=$(<"$proj_file")

run_smoke basecamp people clients list --in "$proj_id" --json
assert_success
assert_json_value '.ok' 'true'
assert_json_value '.data | type' 'array'
}

@test "projects show returns project detail" {
local proj_file="$BATS_FILE_TMPDIR/project_id"
[[ -f "$proj_file" ]] || mark_unverifiable "projects list did not produce a project ID"
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
charm.land/bubbles/v2 v2.2.1
charm.land/bubbletea/v2 v2.0.9
charm.land/lipgloss/v2 v2.0.6
github.com/basecamp/basecamp-sdk/go v0.16.1-0.20260903193203-47e7ca381a49
github.com/basecamp/basecamp-sdk/go v0.17.0
github.com/basecamp/cli v0.2.2-0.20260828230226-767413fc712d
github.com/basecamp/mcp v0.0.0-20260828100356-2d6f44b51e9d
github.com/basecamp/surfguard/go v0.1.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,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.16.1-0.20260903193203-47e7ca381a49 h1:acgFif/siLT3R1/XWHdXLGGsE4x7HqKCvSaXw6j7xHo=
github.com/basecamp/basecamp-sdk/go v0.16.1-0.20260903193203-47e7ca381a49/go.mod h1:Cs9DV8iRJaVT4+IQXGZTeyG2nQAV2kQda7GHuBOeonY=
github.com/basecamp/basecamp-sdk/go v0.17.0 h1:u2kIDtr7bmHXLSkF61HXkLDymQdwoI+pzbT0F6OwHYM=
github.com/basecamp/basecamp-sdk/go v0.17.0/go.mod h1:Cs9DV8iRJaVT4+IQXGZTeyG2nQAV2kQda7GHuBOeonY=
github.com/basecamp/cli v0.2.2-0.20260828230226-767413fc712d h1:jAzDrCCzDpIwhbFT1xVVs0z2xpXoDEkomHfKB2bUUp8=
github.com/basecamp/cli v0.2.2-0.20260828230226-767413fc712d/go.mod h1:iTBTaWvsPEFIcZfkxQHEfISyJ6sZ7036K6bNx0RY3EE=
github.com/basecamp/mcp v0.0.0-20260828100356-2d6f44b51e9d h1:zEQVGq1x1nhKMZ2TudFAcSJ32CHT8richI1vQakIKz4=
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func CommandCategories() []CommandCategory {
{
Name: "Organization",
Commands: []CommandInfo{
{Name: "people", Category: "organization", Description: "Manage people and access", Actions: []string{"list", "show", "update", "out-of-office", "pingable", "add", "remove"}},
{Name: "people", Category: "organization", Description: "Manage people and access", Actions: []string{"list", "show", "update", "out-of-office", "pingable", "add", "remove", "clients"}},
{Name: "templates", Category: "organization", Description: "Manage project and to-do list templates", Actions: []string{"list", "show", "create", "update", "delete", "construct", "construction", "library", "copy", "copy-status"}},
{Name: "webhooks", Category: "organization", Description: "Manage webhooks", Actions: []string{"list", "show", "create", "update", "delete"}},
{Name: "lineup", Category: "organization", Description: "Manage lineup markers", Actions: []string{"list", "create", "update", "delete"}},
Expand Down
Loading
Loading