---
name: Basecamp
slug: basecamp
category: Automation
description: Basecamp interacts with Basecamp via the CLI for projects, todos, messages, files, schedules, check-ins, chat, notifications, and more. Use it for Basecamp questions or actions across account and project workflows.
github: "https://github.com/basecamp/basecamp-cli/tree/main/skills/basecamp"
language: Go
stars: 261
forks: 21
install: "npx degit https://github.com/basecamp/basecamp-cli/tree/main/skills/basecamp ~/.claude/skills/basecamp"
installs_to: ~/.claude/skills/basecamp
source_path: skills/basecamp/SKILL.md
collection_size: 2
category_size: 1648
collection_url: "https://dirskills.com/collections/basecamp/basecamp-cli"
added: 2026-09-02T05:20:15.802Z
last_synced: 2026-09-02T05:20:15.802Z
canonical_url: "https://dirskills.com/skills/basecamp"
---

# Basecamp

Basecamp interacts with Basecamp via the CLI for projects, todos, messages, files, schedules, check-ins, chat, notifications, and more. Use it for Basecamp questions or actions across account and project workflows.

**Install:**

```bash
npx degit https://github.com/basecamp/basecamp-cli/tree/main/skills/basecamp ~/.claude/skills/basecamp
```

## README

# /basecamp - Basecamp Workflow Command

Full CLI coverage: 155 endpoints across todos, cards, messages, files, schedule, check-ins, timeline, recordings, templates, webhooks, subscriptions, lineup, chat, pings, gauges, assignments, notifications, and accounts.

## Agent Invariants

**MUST follow these rules:**

1. **Choose the right output mode** — `--jq` when you need to filter/extract data; `--json` for full JSON; `--md` when presenting results to a human (see Output Modes below). **Never pipe to external `jq` — use `--jq` instead.**
2. **Parse URLs first** with `basecamp url parse "<url>"` to extract IDs
3. **Comments are flat** - reply to parent recording, not to comments
4. **Check context** via `.basecamp/config.json` before assuming project
5. **Content fields accept Markdown and @mentions** — message body and comment content accept Markdown syntax; the CLI converts to HTML automatically. Use Markdown formatting (lists, bold, links, code blocks, tables) for rich content. Four mention syntaxes are available (prefer deterministic for agents):
   - **`[@Name](mention:SGID)`** — zero API calls, embeds SGID directly (preferred for agents)
   - **`[@Name](person:ID)`** — one API call, resolves person ID to SGID via pingable set
   - **`@sgid:VALUE`** — inline SGID embed for pipeline composability
   - **`@Name` / `@First.Last`** — fuzzy name resolution (may be ambiguous)
   For todos, documents, and cards, content is sent as-is — use plain text or HTML directly.

   **Table boundary:** GFM tables round-trip: they render in message/comment
   bodies, display converts them back to pipe tables, and the TUI in-place
   editors open simple grids for editing. Only **complex** tables — merged
   cells (colspan/rowspan), captions, extra header rows, nested tables,
   attachments/images or block content inside cells, multi-paragraph or
   multi-line cells, or a table inside a blockquote or list — refuse to open,
   since a GFM pipe table can't represent those shapes (edit them on Basecamp
   web, or replace the
   whole field via `messages update` / `comments update` / `todos update
   --description`, which take fresh content and are unaffected). Complex
   tables still **display** best-effort, flattened to a plain grid.

   **Multiline / non-ASCII content:** do not rely on bash ANSI-C quoting (`$'...\n...'`) — it is a bash/zsh extension. Under a POSIX `/bin/sh` (dash, busybox-ash, common in sandboxes) the `$` is passed through literally and posts a stray leading `$`, and `\n` stays a literal backslash-n. Pipe the content via stdin instead, using `-` as the content argument:
   ```bash
   printf '%s\n' '海报 mockup 方向稿：' '' '<bc-attachment ...>' | basecamp comments create <recording_id> - --in <project> --json
   ```
   `-` means "read from stdin" on every content input: content-kind positionals
   (`comments create/update`, `messages create [body]`, `cards create [body]`,
   `todos create`, `docs documents create [content]`, `chat post/update`, `boost create`,
   `checkins answer create/update`, `notes set`) and content flags (`--data` on
   `api post/put`, `--body`, `--content`, `--description`, `--comment` on
   `todos sweep`, `--file` on `notes set`). Each command's `--agent` help lists
   its stdin inputs. Rules:
   - A pipe is **never consumed implicitly** — without `-` it is ignored (or, where
     content is required and missing, the error teaches `-`).
   - Only one input can read stdin per invocation.
   - A literal `-` anywhere else (a title, a name, a path) **errors when stdin is
     piped**. Escape a positional after the `--` separator
     (`basecamp projects create -- -`); a flag value has no in-line escape — run
     the command without piped stdin. `basecamp help` and shell completion are
     exempt: they write nothing to Basecamp, and completion legitimately
     receives `-` as the word being completed.
   - `-` with nothing piped (interactive TTY) errors immediately instead of
     hanging; use a pipe, a heredoc (`basecamp comments create <id> - <<'EOF'`),
     or `--edit` where offered.
   - Trailing newlines are trimmed from stdin content, so `printf 'x\n' | ... -`
     posts `x` (this keeps `boost create -` inside its 16-rune limit).
6. **Project scope is mandatory for most commands** — via `--in <project>` or `.basecamp/config.json`. Cross-project exceptions: `basecamp reports assigned` for assigned work, `basecamp assignments` for structured assignment views, `basecamp reports overdue` for overdue todos, `basecamp reports schedule` for upcoming schedule across all projects, `basecamp recordings <type>` for browsing by type, `basecamp notifications` for notifications, `basecamp gauges list` for account-wide gauges, and the seven list commands covered in item 7.
7. **Account-wide listing.** `basecamp todos list --all-projects --json` lists across every project; the same flag does the same on `cards list`, `messages list`, `comments list`, `files list`, `forwards list`, and `checkins answers`. It overrides a configured project, and with no project in scope those commands already list account-wide rather than prompting. Flags that name something inside a single project are rejected there rather than silently ignored.
   Account-wide listings return **the first 100 items by default** — account-wide "all" is the whole account, not one project's worth. Use `--limit N` to raise the cap (it walks pages until N are collected) or `--all` for everything. `--page N` fetches exactly one page, but only on the paginated listings.
   The two overdue variants — `basecamp todos list --all-projects --overdue` and `basecamp cards list --all-projects --overdue` — come from unpaginated endpoints. They accept `--limit` and `--all` but **reject `--page`**, so do not generate `--page` against them.

### Output Modes

**Choosing a mode:**

| Goal | Flag | Format |
|------|------|--------|
| Filter/extract JSON data | `--jq '<expr>'` | Built-in jq filter (no external jq needed). Implies `--json`; filter runs on the envelope. |
| Filter in agent mode | `--agent --jq '<expr>'` | Filter runs on data-only payload (no envelope), matching `--agent` contract. |
| Full JSON output | `--json` | JSON envelope: `{ok, data, summary, breadcrumbs, meta}`; errors: `{ok:false, error, code, retryable, hint, meta}` |
| Show results to a user | `--md` / `-m` | GFM tables, task lists, structured Markdown |
| Automation / scripting | `--agent` | Success: raw JSON data (no envelope); errors: `{ok:false,...}` object; no interactive prompts |

Always pass `--json` or `--md` explicitly — auto-detection depends on config and may not produce the format you expect. Use `--md` when composing reports, summarizing data, or displaying results inline. `--agent` is for headless integration scripts.

**Avoiding interactive prompts.** The flags `--agent`/`--json`/`--quiet`/`--ids-only`/`--count` and the environment variable `BASECAMP_NONINTERACTIVE=1` suppress interactive selection prompts. `--md` does **not** — if a required target is ambiguous (e.g. a project with multiple todosets and no `--todoset`), and the CLI is attached to a terminal, it will show a blocking picker. When you need Markdown output *and* no prompts, either pass the flag that names whatever is ambiguous (`--todoset <id>` for the todoset case above, or `--in <project>` / `--list <id>` when the project or list is ambiguous) or set `BASECAMP_NONINTERACTIVE=1` in the environment. `BASECAMP_NONINTERACTIVE` disables all prompts (they become actionable errors instead) without changing the output format — an escape hatch for agents running under a PTY.

**Other modes:** `--quiet` (success: raw JSON, no envelope; errors: `{ok:false,...}`), `--ids-only`, `--count`, `--stats` (session statistics), `--styled` (force ANSI), `-v` / `-vv` (verbose/trace), `--jq '<expr>'` (built-in jq filter — see below).

### CLI Introspection

Navigate unfamiliar commands with `--agent --help` — returns structured JSON describing any command:

```bash
basecamp todos --agent --help
```

```json
{"command":"todos","path":"basecamp todos","short":"...","long":"...","usage":"...","notes":["..."],
 "subcommands":[{"name":"sweep","short":"...","path":"basecamp todos sweep"}],
 "flags":[{"name":"assignee","type":"string","default":"","usage":"..."}],
 "inherited_flags":[{"name":"json","shorthand":"j","type":"bool","default":"false","usage":"..."}]}
```

Walk the tree: start at `basecamp --agent --help` for top-level commands, then drill into any subcommand. Commands carry domain-specific agent hints (e.g., "`--assignee` filters the account-wide listing only; within a project, fetch all and filter client-side").

### Pagination

```bash
basecamp <cmd> --limit 50   # Cap results (default varies by resource)
basecamp <cmd> --all        # Fetch all (may be slow for large datasets)
basecamp <cmd> --page 1     # First page only, no auto-pagination
```

`--all` and `--limit` are mutually exclusive. `--page` cannot combine with either.

### Smart Defaults

- `--assignee me` resolves to current user
- `--due tomorrow` / `--due +3` / `--due "next week"` — natural date parsing, **when setting a due date** (`todos create`, `todos update`, `cards create`, and so on)
- `--due` on a **listing** is a different flag and does not take dates: it accepts only `with`, `without`, or `overdue`, and only account-wide. `basecamp todos list --due tomorrow` is rejected. For date-based listing use `--overdue`, `--no-due-date`, or `basecamp assignments due <scope>`
- Project from `.basecamp/config.json` if `--in` not specified
- Multiple identities use named profiles: `basecamp profile create <name>`, then select one with global `--profile <name>` or `BASECAMP_PROFILE=<name>`.

## Quick Reference

> **Note:** Most queries require project scope (via `--in <project>` or `.basecamp/config.json`). Cross-project exceptions: `basecamp reports assigned`, `basecamp assignments`, `basecamp reports overdue`, `basecamp reports schedule`, `basecamp recordings <type>`, `basecamp notifications`, `basecamp gauges list`.
>
> Seven list commands also list account-wide: `basecamp todos list --all-projects --json`, and likewise `cards list`, `messages list`, `comments list`, `files list`, `forwards list`, and `checkins answers`.

| Task | Command |
|------|---------|
| List projects | `basecamp projects list --json` |
| My todos (in project) | `basecamp todos list --assignee me --in <project> --json` |
| My todos (cross-project) | `basecamp reports assigned --json` (defaults to "me") |
| My schedule (cross-project) | `basecamp reports schedule --json` (upcoming events across all projects) |
| All todos (cross-project) | `basecamp todos list --all-projects --json` (grouped by project) |
| Overdue todos (in project) | `basecamp todos list --overdue --in <project> --json` |
| Overdue todos (cross-project) | `basecamp todos list --all-projects --overdue --json` (flat, oldest first) or `basecamp reports overdue --json` (bucketed by lateness) |
| All cards (cross-project) | `basecamp cards list --all-projects --json` (grouped by project) |
| Someone's todos (cross-project) | `basecamp todos list --all-projects --assignee "Ann" --json` (server-side filter) |
| Two people's todos (cross-project) | `basecamp todos list --all-projects --assignee ann --assignee bob --json` (matches either) |
| Someone's cards (cross-project) | `basecamp cards list --all-projects --assignee "Ann" --json` |
| Todos with no due date set (cross-project) | `basecamp todos list --all-projects --due without --json` |
| My bookmarks | `basecamp bookmarks list --json` |
| Bookmark something | `basecamp bookmarks add <id-or-url> --json` |
| Is it bookmarked? | `basecamp bookmarks check <id-or-url> --json` (always exits 0) |
| My unpublished drafts | `basecamp drafts list --json` |
| Read my personal note | `basecamp notes show --json` |
| Replace my personal note | `basecamp notes set "<content>" --json` |
| Check-ins I owe answers to | `basecamp checkins reminders --json` |
| Add to Up Next | `basecamp assignments prioritize <id> --json` |
| Recolor a calendar | `basecamp calendars update <id-or-url> --color blue --json` |
| Todo outside any list | `basecamp todos create "<content>" --loose --in <project> --json` |
| Assign todo | `basecamp assign <id> [id...] --to <person> --in <project> --json` |
| Assign card | `basecamp assign <id> [id...] --card --to <person> --in <project> --json` |
| Assign card step | `basecamp assign <id> [id...] --step --to <person> --in <project> --json` |
| Create todo | `basecamp todos create "Task" --in <project> --list <list> --json` |
| Create todolist | `basecamp todolists create "Name" --in <project> --json` |
| Complete todo | `basecamp todos complete <id> --json` |
| List cards | `basecamp cards list --in <project> --json` |
| Create card | `basecamp cards create "Title" --in <project> --json` |
| Complete card | `basecamp cards done <id|url> --in <project> --json` |
| Move card | `basecamp cards move <id> --to <column> [--position N] --in <project> --json` |
| Move card to on-hold | `basecamp cards move <id> --on-hold --in <project> --json` |
| Move card to another project | `basecamp cards move <id> --to-wormhole <wormhole_id> --in <project> --json` (async teleport) |
| Post message | `basecamp messages create "Title" "Body" --in <project> --json` |
| Post with @mention | `basecamp messages create "Title" "Hey @First.Last, ..." --in <project> --json` |
| Post silently | `basecamp messages create "Title" "Body" --no-subscribe --in <project> --json` |
| Post to chat | `basecamp chat post "Message" --in <project> --json` |
| List pings | `basecamp notifications --json --jq '.data.reads[]? | select(.section == "pings")'` |
| Read ping thread | `basecamp api get "/buckets/<circle_id>/chats/<chat_id>/lines.json" --agent` |
| Post to ping thread | `basecamp api post "/buckets/<circle_id>/chats/<chat_id>/lines.json" --data '{"content":"<p>message</p>"}' --json` |
| Add comment | `basecamp comments create <recording_id> "Text" --in <project> --json` |
| Inspect comment / reply atoms | `basecamp comments show <url> --json` → `reply_target` + `mention` in `.data` |
| List attachments | `basecamp attachments list <id\|url> --json` |
| Download attachments | `basecamp attachments download <id> --out /tmp/` |
| Show + download | `basecamp todos show <id> --download-attachments --json` |
| Stream attachment to stdout | `basecamp attachments download <id> --file <name> --out -` |
| Change history for an item | `basecamp events <id\|url> --json` (when a card moved columns, when a todo was completed) |
| Search | `basecamp search "query" --json` |
| Parse URL | `basecamp url parse "<url>" --json` |
| Upload file | `basecamp files uploads create <file> [--vault <folder_id>] --in <project> --json` |
| Download file | `basecamp files download <id> --in <project>` |
| Stream file to stdout | `basecamp files download <id> --out - --in <project>` |
| Download storage URL | `basecamp files download "https://storage.3.basecamp.com/.../download/report.pdf"` |
| My assignments | `basecamp assignments --json` (priorities + non-priorities) |
| Overdue assignments | `basecamp assignments due overdue --json` |
| Completed assignments | `basecamp assignments completed --json` |
| Notifications | `basecamp notifications --json` |
| Mark notification read | `basecamp notifications read <id> --json` |
| All bubble-ups (BC5) | `basecamp notifications bubbleups --json` |
| Gauges (account-wide) | `basecamp gauges list --json` |
| Gauge needles | `basecamp gauges needles --in <project> --json` |
| Create needle | `basecamp gauges create --position 75 --color green --in <project> --json` |
| Account details | `basecamp accounts show --json` |

## URL Parsing

**Parse URLs before acting on them — unless you're handing the URL to a command
that accepts a URL directly** (`show`, `comments show`, `comments thread`,
`attachments list`/`attachments download`), which extract the IDs for you. Only `comments show` and
`comments thread` verify the URL's host and account before any fetch. For other
URL-accepting commands, only pass URLs from a trusted Basecamp host:
`basecamp url parse` extracts IDs but does **not** validate the URL's origin, so
parsing an attacker-controlled path yields trusted-looking IDs.

```bash
basecamp url parse "https://3.basecamp.com/2914079/buckets/41746046/messages/9478142982#__recording_9488783598" --json
```

Returns: `account_id`, `project_id`, `type`, `recording_id`, `comment_id` (from fragment).

**URL patterns:**
- `/buckets/27/messages/123` - Message 123 in project 27
- `/buckets/27/messages/123#__recording_456` - Comment 456 on message 123
- `/buckets/27/card_tables/cards/789` - Card 789
- `/buckets/27/card_tables/columns/456` - Column 456 (for creating cards)
- `/buckets/27/todos/101` - Todo 101
- `/buckets/27/uploads/202` - Upload/file 202
- `/buckets/27/documents/303` - Document 303
- `/buckets/27/schedule_entries/404` - Schedule entry 404

**Replying to comments:**
```bash
# Comments are flat - reply to the parent recording_id, not the comment_id
basecamp url parse "https://...messages/123#__recording_456" --json
# Returns recording_id: 123 (parent), comment_id: 456 (fragment) - comment on 123, not 456
basecamp comments create 123 "Reply" --in <project>

# Or get the whole reply-ready context deterministically in one call:
basecamp comments thread "https://...messages/123#__recording_456" --json
# .data.reply_target.recording_id  → where to post the reply
# .data.reply_target.account_id    → the account that reply belongs to (build a fully-qualified command)
# .data.focus.author.mention.syntax → paste-ready [@Name](mention:SGID)
# .data.comments                   → surrounding discussion (default window of 41)
# --all returns every fetched comment; --window N sets the window size
# When the account came from the URL (none configured), the reply breadcrumb carries --account
```

## Decision Trees

### Finding Content

```
Need to find something?
├── Know the type + project? → basecamp <type> list --in <project> --json
│   (some groups have default list behavior; use --agent --help if unsure)
├── My assigned work? → basecamp assignments --json (priorities + non-priorities)
│   Or: basecamp reports assigned --json (traditional view, defaults to "me")
├── My overdue assignments? → basecamp assignments due overdue --json
├── My notifications? → basecamp notifications --json
├── Upcoming schedule? → basecamp reports schedule --json (cross-project)
├── Overdue across projects? → basecamp reports overdue --json
├── Browse by type cross-project? → basecamp recordings <type> --json
│   (types: todos, messages, documents, comments, cards, uploads)
│   Note: Defaults to active status; use --status archived for archived items
│   ⚠ No assignee data — cannot filter by person; use reports assigned instead
├── Full-text search? → basecamp search "query" --json
├── Have a comment URL, or a notification link targeting a comment? → basecamp comments thread <url> --json
└── Have a URL? → basecamp url parse "<url>" --json
```

### Modifying Content

```
Want to change something?
├── Have URL? → basecamp url parse "<url>" → use extracted IDs
├── Have ID? → basecamp <resource> update <id> --field value
├── Change status? → basecamp recordings trash|archive|restore <id>
├── Complete todo? → basecamp todos complete <id>
├── Complete card? → basecamp cards done <id|url> --in <project>
└── Reply to a comment? → basecamp comments show <url> --jq '.data | {reply_target, mention}'
    (one call, cheap atoms — the mention is machine-only, so use --jq/--json, not plain show)
    or basecamp comments thread <url> when you need the surrounding discussion;
    then basecamp comments create <reply_target.recording_id> <text>
```

## Common Workflows

### Link Code to Basecamp Todo

```bash
# Get commit info and comment on todo (use printf %q for safe quoting)
COMMIT=$(git rev-parse --short HEAD)
MSG=$(git log -1 --f
