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
71 changes: 71 additions & 0 deletions .surface
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ ARG basecamp documents uploads create 00 <file>
ARG basecamp documents vault create 00 <name>
ARG basecamp documents vaults create 00 <name>
ARG basecamp documents versions 00 <upload_id|url>
ARG basecamp embeds create 00 <url>
ARG basecamp embeds show 00 <sgid>
ARG basecamp events 00 [id|url]
ARG basecamp file archive 00 <id|url>
ARG basecamp file doc create 00 <title>
Expand Down Expand Up @@ -788,6 +790,9 @@ CMD basecamp documents vaults list
CMD basecamp documents versions
CMD basecamp drafts
CMD basecamp drafts list
CMD basecamp embeds
CMD basecamp embeds create
CMD basecamp embeds show
CMD basecamp events
CMD basecamp events poll
CMD basecamp events ticket
Expand Down Expand Up @@ -7532,6 +7537,69 @@ FLAG basecamp drafts list --stats type=bool
FLAG basecamp drafts list --styled type=bool
FLAG basecamp drafts list --todolist type=string
FLAG basecamp drafts list --verbose type=count
FLAG basecamp embeds --account type=string
FLAG basecamp embeds --agent type=bool
FLAG basecamp embeds --cache-dir type=string
FLAG basecamp embeds --count type=bool
FLAG basecamp embeds --help type=bool
FLAG basecamp embeds --hints type=bool
FLAG basecamp embeds --ids-only type=bool
FLAG basecamp embeds --in type=string
FLAG basecamp embeds --jq type=string
FLAG basecamp embeds --json type=bool
FLAG basecamp embeds --markdown type=bool
FLAG basecamp embeds --md type=bool
FLAG basecamp embeds --no-hints type=bool
FLAG basecamp embeds --no-stats type=bool
FLAG basecamp embeds --profile type=string
FLAG basecamp embeds --project type=string
FLAG basecamp embeds --quiet type=bool
FLAG basecamp embeds --stats type=bool
FLAG basecamp embeds --styled type=bool
FLAG basecamp embeds --todolist type=string
FLAG basecamp embeds --verbose type=count
FLAG basecamp embeds create --account type=string
FLAG basecamp embeds create --agent type=bool
FLAG basecamp embeds create --cache-dir type=string
FLAG basecamp embeds create --count type=bool
FLAG basecamp embeds create --help type=bool
FLAG basecamp embeds create --hints type=bool
FLAG basecamp embeds create --ids-only type=bool
FLAG basecamp embeds create --in type=string
FLAG basecamp embeds create --jq type=string
FLAG basecamp embeds create --json type=bool
FLAG basecamp embeds create --markdown type=bool
FLAG basecamp embeds create --md type=bool
FLAG basecamp embeds create --no-hints type=bool
FLAG basecamp embeds create --no-stats type=bool
FLAG basecamp embeds create --profile type=string
FLAG basecamp embeds create --project type=string
FLAG basecamp embeds create --quiet type=bool
FLAG basecamp embeds create --stats type=bool
FLAG basecamp embeds create --styled type=bool
FLAG basecamp embeds create --todolist type=string
FLAG basecamp embeds create --verbose type=count
FLAG basecamp embeds show --account type=string
FLAG basecamp embeds show --agent type=bool
FLAG basecamp embeds show --cache-dir type=string
FLAG basecamp embeds show --count type=bool
FLAG basecamp embeds show --help type=bool
FLAG basecamp embeds show --hints type=bool
FLAG basecamp embeds show --ids-only type=bool
FLAG basecamp embeds show --in type=string
FLAG basecamp embeds show --jq type=string
FLAG basecamp embeds show --json type=bool
FLAG basecamp embeds show --markdown type=bool
FLAG basecamp embeds show --md type=bool
FLAG basecamp embeds show --no-hints type=bool
FLAG basecamp embeds show --no-stats type=bool
FLAG basecamp embeds show --profile type=string
FLAG basecamp embeds show --project type=string
FLAG basecamp embeds show --quiet type=bool
FLAG basecamp embeds show --stats type=bool
FLAG basecamp embeds show --styled type=bool
FLAG basecamp embeds show --todolist type=string
FLAG basecamp embeds show --verbose type=count
FLAG basecamp events --account type=string
FLAG basecamp events --agent type=bool
FLAG basecamp events --all type=bool
Expand Down Expand Up @@ -19520,6 +19588,9 @@ SUB basecamp documents vaults list
SUB basecamp documents versions
SUB basecamp drafts
SUB basecamp drafts list
SUB basecamp embeds
SUB basecamp embeds create
SUB basecamp embeds show
SUB basecamp events
SUB basecamp events poll
SUB basecamp events ticket
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 | 53 | 207 |
| ✅ Implemented | 54 | 209 |
| ⚠️ Blocked | 0 | 0 |
| ⏭️ Out of scope | 4 | 12 |
| **Total tracked** | **57** | **219** |
| **Total tracked** | **58** | **221** |

**207 of 207 tracked in-scope endpoints.** The three Pings endpoints bc3#13943
**209 of 209 tracked in-scope endpoints.** 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
/circles.json` — land as `pings`. Moving a document, upload or folder
between folders (`POST /recordings/:id/filing.json`, documented by bc3#13644 and
Expand Down Expand Up @@ -238,6 +240,7 @@ cannot faithfully cover at least one endpoint for a reason outside the CLI. A
| vaults (move) | 1 | `files move` | ✅ | BC5 | - | `POST /recordings/:id/filing.json`: moves a document, upload or folder into another folder in the same project, in place (same id and comments). `--to` takes a folder ID or URL; `--position` is 1-indexed. Flat route, so no `--in` |
| documents | 8 | `files`, `docs` | ✅ | BC4 | - | list, show, create, update. Create supports `--subscribe`/`--no-subscribe`, `--visible-to-clients` (root vault only) |
| attachments | 1 | `uploads`, `attachments` | ✅ | BC4 | - | Upload via `attach`; list embedded attachments via `attachments list` (parses `<bc-attachment>` from content) |
| embeds | 2 | `embeds` | ✅ | BC5 | - | create, show. Rich-text commands also embed a URL that stands alone on its line in Markdown content, through the same create |
| **Schedule** |
| calendars | 2 | `calendars` | ✅ | BC5 | - | show, update (color only). No index endpoint, so there is no `calendars list` — address one by id or pasted URL. The eleven colors are validated client-side for an immediate answer that names the alternatives; since v0.13.0 the SDK also carries the server's field-keyed 422 message |
| schedules | 2 | `schedule` | ✅ | BC4 | - | Schedule container + settings |
Expand Down
1 change: 1 addition & 0 deletions e2e/smoke/run_smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ level1=(
"$SMOKE_DIR"/smoke_assign.bats
"$SMOKE_DIR"/smoke_lineup.bats
"$SMOKE_DIR"/smoke_communication_write.bats
"$SMOKE_DIR"/smoke_embeds.bats
"$SMOKE_DIR"/smoke_misc_write.bats
"$SMOKE_DIR"/smoke_tools.bats
"$SMOKE_DIR"/smoke_cards_column_write.bats
Expand Down
32 changes: 32 additions & 0 deletions e2e/smoke/smoke_embeds.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env bats
# smoke_embeds.bats - Level 1: Embeds for rich text

load smoke_helper

setup_file() {
ensure_token || return 1
}

@test "embeds without subcommand shows help" {
run basecamp embeds
assert_success
assert_output_contains "create"
}

@test "embeds create returns an embed with its sgid" {
run_smoke basecamp embeds create "https://www.youtube.com/watch?v=dQw4w9WgXcQ" --json
if [[ "$status" -ne 0 ]] && [[ "$output" == *"not found"* || "$output" == *"404"* ]]; then
mark_unverifiable "This Basecamp does not serve POST /embeds.json to API clients yet"
return
fi
assert_success
assert_json_not_null '.data.sgid'
echo "$output" | jq -r '.data.sgid' > "$BATS_FILE_TMPDIR/embed_sgid"
}

@test "embeds show returns the embed" {
[[ -s "$BATS_FILE_TMPDIR/embed_sgid" ]] || mark_unverifiable "No embed was created to show"
run_smoke basecamp embeds show "$(cat "$BATS_FILE_TMPDIR/embed_sgid")" --json
assert_success
assert_json_value '.data.url' 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
charm.land/bubbles/v2 v2.2.1
charm.land/bubbletea/v2 v2.1.0
charm.land/lipgloss/v2 v2.0.6
github.com/basecamp/basecamp-sdk/go v0.26.0
github.com/basecamp/basecamp-sdk/go v0.27.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 @@ -89,8 +89,8 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/basecamp/actioncable-go v1.2.0 h1:t0MxlfMTcmaGXZ5ztgKRs7ZiY1MwXtBRwSSSAh1ofVg=
github.com/basecamp/actioncable-go v1.2.0/go.mod h1:ezaV5z1GXQAsqyejqTs6wCFl2D8Wj+COLQkHc/kwoRs=
github.com/basecamp/basecamp-sdk/go v0.26.0 h1:ENIK+0Q9WWRCzcJjz0RLLpvAMSfKoQOl8lHRumaEeOg=
github.com/basecamp/basecamp-sdk/go v0.26.0/go.mod h1:PAp3gBnEPH7LtAyWXwgQ1tfmCAJiMkRHgRJAKT/Xefc=
github.com/basecamp/basecamp-sdk/go v0.27.0 h1:Shq2lyAMBDvvBtmVLZZes42UyKkFMIvpR1Pa8xyWOVo=
github.com/basecamp/basecamp-sdk/go v0.27.0/go.mod h1:mUm/MQGMyg1E1cddyxdG7tQHiq0NsYee2zUeAUT5VjU=
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
1 change: 1 addition & 0 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ func Execute() {
cmd.AddCommand(commands.NewProfileCmd())
cmd.AddCommand(commands.NewSkillCmd())
cmd.AddCommand(commands.NewAttachmentsCmd())
cmd.AddCommand(commands.NewEmbedsCmd())
cmd.AddCommand(commands.NewAttachCmd())
cmd.AddCommand(commands.NewUploadCmd())
cmd.AddCommand(commands.NewGaugesCmd())
Expand Down
1 change: 1 addition & 0 deletions internal/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func CommandCategories() []CommandCategory {
{Name: "forwards", Category: "communication", Description: "Manage email forwards (inbox)", Actions: []string{"list", "show", "inbox", "replies", "reply"}},
{Name: "subscriptions", Category: "communication", Description: "Manage notification subscriptions", Actions: []string{"show", "subscribe", "unsubscribe", "add", "remove"}},
{Name: "attachments", Category: "communication", Description: "List and download attachments", Actions: []string{"list", "download"}},
{Name: "embeds", Category: "communication", Description: "Embed videos, charts and other links in rich text", Actions: []string{"create", "show"}},
{Name: "comments", Category: "communication", Description: "Manage comments", Actions: []string{"create", "list", "show", "thread", "update", "trash", "archive", "restore"}},
{Name: "boost", Category: "communication", Description: "Manage boosts (reactions)", Actions: []string{"list", "show", "create", "update", "delete"}},
{Name: "notifications", Category: "communication", Description: "View and manage notifications", Actions: []string{"list", "read", "bubbleups"}},
Expand Down
1 change: 1 addition & 0 deletions internal/commands/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ func buildRootWithAllCommands() *cobra.Command {
root.AddCommand(commands.NewUpgradeCmd())
root.AddCommand(commands.NewMigrateCmd())
root.AddCommand(commands.NewAttachmentsCmd())
root.AddCommand(commands.NewEmbedsCmd())
root.AddCommand(commands.NewAttachCmd())
root.AddCommand(commands.NewUploadCmd())
root.AddCommand(commands.NewSkillCmd())
Expand Down
Loading
Loading