diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 488152f..2426006 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -8,7 +8,16 @@ "plugins": [ { "name": "basecamp", - "description": "Basecamp integration for Claude Code and Codex. Create todos, track work, link code to projects.", + "description": "Deprecated alias for basecamp-cli, kept so existing installs keep working. Install basecamp-cli instead, or run `basecamp setup` to switch.", + "source": { + "source": "url", + "url": "https://github.com/basecamp/basecamp-cli.git" + }, + "category": "productivity" + }, + { + "name": "basecamp-cli", + "description": "CLI-based Basecamp for Claude Code and Codex, driven by the basecamp command. Create todos, track work, link code to projects.", "source": { "source": "url", "url": "https://github.com/basecamp/basecamp-cli.git" diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..4ab7259 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,18 @@ +name: Check + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + marketplace: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - run: bin/check-marketplace diff --git a/README.md b/README.md index b331003..036072c 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,43 @@ claude plugin install | Plugin | Description | |--------|-------------| -| [basecamp](https://github.com/basecamp/basecamp-cli/tree/main/.claude-plugin) | Todos, cards, messages, files, schedule, check-ins, timeline, recordings, templates, webhooks, subscriptions, lineup, and campfire. | +| [basecamp-cli](https://github.com/basecamp/basecamp-cli/tree/main/.claude-plugin) | Basecamp through the `basecamp` command, for Claude Code and Codex. Todos, cards, messages, files, schedule, check-ins, timeline, recordings, templates, webhooks, subscriptions, lineup, and campfire; links code to projects. | +| [hey](https://github.com/basecamp/hey-cli) | Read email, manage todos, track time. | +| [fizzy](https://github.com/basecamp/fizzy-cli) | Manage boards and cards, track work, link code to projects. | -HEY and Fizzy plugins coming soon. +### `basecamp` is now `basecamp-cli` + +The CLI plugin used to be `basecamp@37signals`. It's now +`basecamp-cli@37signals`, with the same source, skills and hooks. The +`basecamp` command itself keeps its name. + +For a deprecation window (7 days from the CLI release that ships the rename), `basecamp` stays in this +marketplace as an alias with the same source as `basecamp-cli`. Existing +installs keep loading and updating. From the basecamp-cli release with the +rename on, a plugin installed under the old name says once that it has a +new name. `basecamp setup` moves you to `basecamp-cli`. To +do it by hand: + +``` +claude plugin marketplace update 37signals +claude plugin install basecamp-cli@37signals +claude plugin uninstall basecamp@37signals +``` + +The alias goes away in its own later PR, after the window. + +### The hosted Basecamp plugin + +`plugins/basecamp` is a second Basecamp plugin. It uses Basecamp's hosted +connector at `https://mcp.basecamp.com/mcp` rather than the CLI, so it also +works in claude.ai chat and Cowork. This is the folder Anthropic's plugin +directory publishes as `basecamp`. + +This marketplace doesn't list it. `basecamp` here is the CLI alias until +the window ends, and only after that can the name point at the hosted +plugin. `bin/check-marketplace`, which CI runs, holds that rule: a +`basecamp` entry passes only with exactly the same source as +`basecamp-cli`. The script says when the rule can change. ## Development plugins @@ -29,6 +63,15 @@ HEY and Fizzy plugins coming soon. ## About -This repo is a thin marketplace index. Plugin source code (skills, hooks, -commands) lives in each product's CLI repo. To contribute, open issues or PRs -there. +This repo is mostly a thin marketplace index. Plugin source code (skills, +hooks, commands) lives in each product's own repo. To contribute, open issues +or PRs there. + +The one plugin with files here, `plugins/basecamp` (the hosted plugin, not +listed in the marketplace), is a mirror. Its source +of truth is `plugins/claude/basecamp` in the (private) Basecamp connector +repository, which tests it against the tools the connector actually serves. +Don't edit the mirror; change it upstream, then run +`bin/sync-basecamp-plugin ` to copy and validate it, and commit +with the source commit it prints. `bin/sync-basecamp-plugin --check +` reports drift. diff --git a/bin/check-marketplace b/bin/check-marketplace new file mode 100755 index 0000000..2c53564 --- /dev/null +++ b/bin/check-marketplace @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Checks .claude-plugin/marketplace.json. Run by CI on every push and pull request. +# +# 1. It parses, and every plugin name is unique. +# 2. A plugin named "basecamp", if listed, has exactly the same source as +# "basecamp-cli": a deprecated alias for it. +# +# "basecamp@37signals" used to be the CLI plugin, now "basecamp-cli". During the +# deprecation window the alias keeps existing installs loading, and +# basecamp-cli's `basecamp setup` moves them to basecamp-cli by key: it treats an +# installed basecamp@37signals as the CLI plugin (basecamp/basecamp-cli#840). +# Pointing "basecamp" anywhere else, such as the hosted plugin in +# plugins/basecamp, would swap a different plugin in under existing installs, +# and setup would uninstall it. +# +# Rule 2 changes only in its own PR, once both hold: +# - the deprecation window has passed (7 days from the CLI release that ships the rename), and +# - a basecamp-cli release has shipped that no longer treats basecamp@37signals +# as the old CLI plugin. +# That PR first removes the alias. Pointing "basecamp" at the hosted plugin is a +# later decision still. +set -euo pipefail + +root="$(cd "$(dirname "$0")/.." && pwd)" +marketplace="$root/.claude-plugin/marketplace.json" + +python3 - "$marketplace" <<'PY' +import collections, json, sys + +with open(sys.argv[1]) as f: + plugins = json.load(f)["plugins"] +names = [p["name"] for p in plugins] + +failed = False +for name, count in collections.Counter(names).items(): + if count > 1: + print(f"error: plugin name {name!r} is listed {count} times", file=sys.stderr) + failed = True + +by_name = {p["name"]: p for p in plugins} +alias = by_name.get("basecamp") +if alias is not None: + target = by_name.get("basecamp-cli") + if target is None or alias.get("source") != target.get("source"): + print( + "error: the 'basecamp' entry must be a deprecated alias with exactly the same source as\n" + "'basecamp-cli' until the deprecation window ends and a basecamp-cli release stops\n" + "treating basecamp@37signals as the old CLI plugin. See bin/check-marketplace.", + file=sys.stderr, + ) + failed = True + +sys.exit(1 if failed else 0) +PY +echo "marketplace.json OK" diff --git a/bin/sync-basecamp-plugin b/bin/sync-basecamp-plugin new file mode 100755 index 0000000..9e00d4d --- /dev/null +++ b/bin/sync-basecamp-plugin @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# Mirror the hosted Basecamp plugin from basecamp-mcp-server, its source of truth. +# +# bin/sync-basecamp-plugin copy, validate, print the source commit +# bin/sync-basecamp-plugin --check exit 1 if plugins/basecamp differs +# +# The source is plugins/claude/basecamp in that checkout. Sync from a clean +# checkout of its main branch, and put the printed commit in the commit message. +# Never edit plugins/basecamp here: change it upstream and sync. +set -euo pipefail + +check=0 +if [[ "${1:-}" == "--check" ]]; then + check=1 + shift +fi + +if [[ $# -ne 1 ]]; then + sed -n '4,5p' "$0" | sed 's/^# *//' >&2 + exit 2 +fi + +root="$(cd "$(dirname "$0")/.." && pwd)" +upstream="$(cd "$1" && pwd)" +src="$upstream/plugins/claude/basecamp" +dest="$root/plugins/basecamp" + +if [[ ! -f "$src/.claude-plugin/plugin.json" ]]; then + echo "error: $src is not the Basecamp plugin (no .claude-plugin/plugin.json)" >&2 + exit 2 +fi + +if [[ $check -eq 1 ]]; then + if diff -r "$src" "$dest"; then + echo "plugins/basecamp matches $(git -C "$upstream" rev-parse --short HEAD)" + else + echo "plugins/basecamp has drifted from basecamp-mcp-server; run bin/sync-basecamp-plugin" >&2 + exit 1 + fi + exit 0 +fi + +if [[ -n "$(git -C "$upstream" status --porcelain -- plugins/claude/basecamp)" ]]; then + echo "error: uncommitted changes under $src; sync from a committed source" >&2 + exit 2 +fi + +rm -rf "$dest" +mkdir -p "$dest" +cp -R "$src/." "$dest/" +find "$dest" -name .DS_Store -delete + +if command -v claude >/dev/null 2>&1; then + claude plugin validate "$dest" +else + echo "claude not installed; skipping claude plugin validate" >&2 +fi + +echo +echo "Mirrored from basecamp/basecamp-mcp-server@$(git -C "$upstream" rev-parse HEAD)" diff --git a/plugins/basecamp/.claude-plugin/plugin.json b/plugins/basecamp/.claude-plugin/plugin.json new file mode 100644 index 0000000..0468464 --- /dev/null +++ b/plugins/basecamp/.claude-plugin/plugin.json @@ -0,0 +1,21 @@ +{ + "name": "basecamp", + "displayName": "Basecamp", + "version": "0.1.0", + "description": "Work in your Basecamp projects from Claude: catch up on a project, recap your week, triage your Hey! menu, plan work into to-dos and cards, draft a check-in answer, and prep for a meeting, through Basecamp's own hosted connector.", + "author": { + "name": "37signals", + "email": "support@37signals.com", + "url": "https://basecamp.com" + }, + "homepage": "https://basecamp.com", + "repository": "https://github.com/basecamp/claude-plugins", + "license": "MIT", + "keywords": [ + "basecamp", + "projects", + "to-dos", + "project management", + "check-ins" + ] +} diff --git a/plugins/basecamp/.mcp.json b/plugins/basecamp/.mcp.json new file mode 100644 index 0000000..3a30f34 --- /dev/null +++ b/plugins/basecamp/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "basecamp": { + "type": "http", + "url": "https://mcp.basecamp.com/mcp" + } + } +} diff --git a/plugins/basecamp/README.md b/plugins/basecamp/README.md new file mode 100644 index 0000000..2790cdf --- /dev/null +++ b/plugins/basecamp/README.md @@ -0,0 +1,58 @@ +# Basecamp + +Bring your Basecamp projects into Claude. Ask what happened on a project +while you were out, what's on your plate this week, or what a long thread +actually decided. Then have Claude do the follow-through: add and assign +to-dos, move cards, post a comment, draft a check-in answer, write up an +agenda. + +This plugin pairs Basecamp's own hosted connector with a skill for working +in Basecamp and six ready-made workflows. + +## Get started + +1. Add the plugin, then open its **Connectors** tab and connect Basecamp. +2. Sign in to Basecamp and choose **Full access**, or **Read-only** if you + only want Claude to read. +3. Ask away: "What's assigned to me in Basecamp this week?" + +You need a Basecamp account. There's no extra charge. + +## Workflows + +In Cowork and Claude Code, run them as commands. In chat, just ask, and +Claude picks the one that fits. + +| Command | What it does | +| --- | --- | +| `/basecamp:catch-up ` | What changed on a project, what was decided, what's open, and what needs you | +| `/basecamp:weekly-recap` | What you finished, what moved across your projects, and what's next week | +| `/basecamp:triage-hey` | Sorts your Hey! menu into needs-you, worth-knowing, and nothing-to-do, with draft replies | +| `/basecamp:plan-project ` | Turns a goal or notes into to-do lists or cards with owners and dates | +| `/basecamp:draft-check-in [question]` | Drafts your answer to an automatic check-in from what you actually did | +| `/basecamp:meeting-prep ` | A one-page brief and agenda from the project's recent activity | + +Claude shows you a plan or a draft before it creates or posts anything, +and every workflow that only reads leaves your Basecamp untouched. + +## What it can work with + +Projects and the people on them; to-dos and to-do lists; messages, +comments and boosts; Card Tables, cards and steps; schedules; Docs & Files; +Campfire chats and Pings; automatic check-ins; your assignments and Hey! +menu; and search across your account. + +## Your data + +The plugin connects to one place, Basecamp's hosted connector at +`https://mcp.basecamp.com/mcp`, run by 37signals. It acts as you: Claude +sees only what you can see in Basecamp and can change only what you can +change there. The plugin itself is instructions and stores nothing; the +connector passes Claude's requests to Basecamp's API on your behalf. Most +things you remove go to the Basecamp trash, where +they can be restored. Disconnect any time from the plugin's Connectors tab +or from Connected apps in your Basecamp profile menu. + +- Privacy policy: https://37signals.com/policies/privacy +- Terms: https://37signals.com/policies/terms +- Support: https://basecamp.com/support diff --git a/plugins/basecamp/commands/catch-up.md b/plugins/basecamp/commands/catch-up.md new file mode 100644 index 0000000..2bd47c3 --- /dev/null +++ b/plugins/basecamp/commands/catch-up.md @@ -0,0 +1,37 @@ +--- +description: Catch up on a Basecamp project — what changed, what was decided, what's still open, and what needs you. +argument-hint: +--- + +# Catch up on a project + +The project to catch up on: $ARGUMENTS + +1. **Find the project.** A link: `get_by_url`. A name: `list_projects`, or + `search` if the list is long. If several match, ask which; if none was + named, ask. +2. **Read what happened.** `get_project_timeline` for the recent activity. + It returns the latest 100 events and can't page, so if its oldest event + is newer than the start of the window, say the catch-up starts there. + `get_project` for its dock, then `list_messages` on its message board for + the latest discussions. Open what matters with `get_by_url` on its + `app_url`: a decision, a long thread, a document that changed. +3. **Who's asking.** `get_me` names the person, so you can tell what's + theirs. +4. **What's open now.** The timeline only shows what moved recently, so read + current state too, keeping this project's group from each: + `list_everything_todos` with `state` set to overdue, then to unassigned; + `list_everything_cards` with `state` set to overdue; and `get_my_assignments` + for what's on them. + +Reply in four short parts, linking every item to its `app_url`: + +- **What changed** in about the last week, or since the date they give: the + handful of things that matter, not every event. +- **Decided:** each decision, quoting the line that settled it. +- **Open:** unanswered questions, overdue or unassigned to-dos, stuck cards. +- **Needs you:** what's assigned to them, mentions them, or waits on their + answer. + +This is a read: post nothing and change nothing. Offer to dig into any +thread. diff --git a/plugins/basecamp/commands/draft-check-in.md b/plugins/basecamp/commands/draft-check-in.md new file mode 100644 index 0000000..88856f1 --- /dev/null +++ b/plugins/basecamp/commands/draft-check-in.md @@ -0,0 +1,33 @@ +--- +description: Draft your answer to a Basecamp automatic check-in, like "What did you work on this week?", from your own recent activity. +argument-hint: "[check-in question link or project]" +--- + +# Draft a check-in answer + +Which check-in: $ARGUMENTS + +1. **Find the question.** A link: `get_by_url`. A project: `get_project` + for its dock, then `get_questionnaire` and `list_questions`. If they + didn't say, ask which check-in. +2. **Read their voice.** `get_me`, then `get_answers_by_person` with the + question and their `person_id` for their last few answers: length, tone, + format. +3. **Gather what they did** over the question's period (a week, unless the + question says otherwise): `get_catchup` with `days` (its finished to-dos + are scoped to that window), and `get_project_timeline` on the projects + they were active in. Timelines and the catch-up's activity counts include + teammates' work: count an event as theirs only when its creator is the + person from `get_me`, and use the rest as context. `get_catchup` reaches back 14 days at most, so for a + longer period, such as a monthly check-in, say the draft covers only the + last 14 days. `get_my_completed_assignments` carries no completion dates, + so don't count its items as done in this period. For what's in + progress, next, and blocked, read `get_my_assignments`: current work + with no recent activity shows up only there. + +Draft the answer the way they write: what they finished, what's in +progress, what's next, and anything blocked, linking to the work. Show the +draft and ask what to change. + +Post only when they say to: `create_answer` with the question and the +answer as HTML in `content`. Then link to it. diff --git a/plugins/basecamp/commands/meeting-prep.md b/plugins/basecamp/commands/meeting-prep.md new file mode 100644 index 0000000..08fec1c --- /dev/null +++ b/plugins/basecamp/commands/meeting-prep.md @@ -0,0 +1,37 @@ +--- +description: Prep for a meeting about a Basecamp project — recent decisions, open questions, what's late, and a draft agenda. +argument-hint: +--- + +# Prep for a meeting + +The project and the meeting: $ARGUMENTS + +1. **Find the project.** A link: `get_by_url`. A name: `list_projects` or + `search`. If none was named, ask. +2. **Read the state of things.** + - `get_project_timeline` for what happened since the last meeting (a + week, unless they say). It returns the latest 100 events and can't + page; if they don't reach back that far, say where the brief starts. + - `get_project` for its dock, then `list_messages` on its message board, + and `get_by_url` on the threads that matter. + - Open work: `list_todolists` and `list_todos`, or `get_card_table`, for + what's overdue, unassigned, or stuck. + - `get_upcoming_schedule` from today to two weeks out for dates coming + up, keeping this project's entries and recurring occurrences. + `list_schedule_entries` lists a recurring series once, at its first + date, so it misses the next meeting of a series. + - `search` for the meeting's topic, to find the docs and threads behind + it. + +Reply with a one-page brief, linking every item to its `app_url`: + +- **Since last time:** decisions and changes, a line each. +- **Open questions:** each with who raised it. +- **At risk:** what's late or blocked, and who owns it. +- **Agenda:** five or fewer items, in order, each with the decision the + meeting needs to make. + +If they want it in Basecamp, offer to save the agenda as a document +(`create_document` in the project's Docs & Files, `vault_id` from the +dock), as a draft unless they say to publish it. diff --git a/plugins/basecamp/commands/plan-project.md b/plugins/basecamp/commands/plan-project.md new file mode 100644 index 0000000..864e456 --- /dev/null +++ b/plugins/basecamp/commands/plan-project.md @@ -0,0 +1,34 @@ +--- +description: Turn a goal, brief, or meeting notes into a Basecamp plan — to-do lists and to-dos with owners and dates, or cards on a Card Table. +argument-hint: +--- + +# Plan it in Basecamp + +What to plan, and where: $ARGUMENTS + +1. **Find the project.** A link: `get_by_url`. A name: `list_projects` or + `search`. If none fits, offer to create one with `create_project`, and + only create it once they agree. +2. **Read its dock.** `get_project` lists its tools. Plan into to-dos unless + they asked for cards or the project works from a Card Table + (`get_card_table` shows its columns). +3. **Find the people.** `list_project_people` for who can own each piece. + Don't guess an owner they didn't name. +4. **Propose the plan first**, as an outline: lists (or columns) with their + to-dos (or cards), each with an owner and a due date where one is clear. + Ask what to change. +5. **Create it once they approve.** + - To-dos: `create_todolist` in the project's to-do set (`todoset_id` from + the dock), then `create_todo` for each, with `assignee_ids` and + `due_on` (`YYYY-MM-DD`). + - Cards: `create_card` in the right column (`column_id`), then + `update_card` to set `assignee_ids`. + For to-dos, leave `notify` off unless they want people notified now. + Cards can't do that: `create_card` takes no assignees, so its `notify` + reaches no one, and `update_card` has no `notify`. If they want card + owners told, say so and offer to post or comment instead. + +Reply with what was created, linked, and anything you skipped and why. The +`posting-and-mentions` topic of `get_basecamp_guide` says who each post +notifies. diff --git a/plugins/basecamp/commands/triage-hey.md b/plugins/basecamp/commands/triage-hey.md new file mode 100644 index 0000000..56ec5a6 --- /dev/null +++ b/plugins/basecamp/commands/triage-hey.md @@ -0,0 +1,28 @@ +--- +description: Triage your Basecamp Hey! menu — sort what needs a reply from what's just FYI, draft replies, and clear what's handled. +--- + +# Triage my Hey! + +1. `get_my_notifications`: unreads first. Unreads come back in one list, + capped at 100; `page` pages only the read items, so it can't fetch more + unreads. If the list is full, say there may be more. +2. Open anything you can't judge from its excerpt with `get_by_url` on its + `app_url`. +3. Sort each unread into one of three groups: + - **Needs you:** a direct question, a mention asking for something, an + assignment, a decision waiting on them. + - **Worth knowing:** announcements and updates on their work. + - **Nothing to do:** boosts, automatic notices, threads that already + moved on. + +Reply with the three groups, each item a line with who, the project, the +gist, and a link. For **Needs you**, offer a short draft reply for each. + +Act only when the person says so: + +- **Reply:** `get_by_url` on the item gives the `reply_target`; post the + reply there, quoting it back to them first. Use `mentions` with person IDs + to @-mention someone. No `reply_target` means a reply can't be posted from + here; say so. +- **Clear:** `mark_as_read` with the `readables` of the items they choose. diff --git a/plugins/basecamp/commands/weekly-recap.md b/plugins/basecamp/commands/weekly-recap.md new file mode 100644 index 0000000..6f6c1b9 --- /dev/null +++ b/plugins/basecamp/commands/weekly-recap.md @@ -0,0 +1,35 @@ +--- +description: Recap your week in Basecamp — what you finished, what moved across your projects, and what's coming up next week. +argument-hint: "[days back, up to 14]" +--- + +# Recap my week + +How far back: $ARGUMENTS (a number of days, up to 14; a week if nothing was +given). + +1. `get_catchup` with `days` for announcements, to-dos assigned to and + completed by the person, their overdue to-dos, and activity by project. +2. `get_my_assignments` for what's still on their plate. For what they + finished, use only the catch-up's completed to-dos: they're the person's + own and scoped to the window. Project timelines include teammates' + completions, and `get_my_completed_assignments` has no dates. The + catch-up's to-dos carry no `app_url`: `get_todo` on each one you list + gives the link. +3. `get_upcoming_schedule` with `start_date` today and `end_date` a week out + for what's next. It covers the whole account, so keep only entries and + recurring occurrences whose participants include the person (`get_me` + names them). +4. For the two or three busiest projects in the catch-up, `get_project_timeline` + to say what actually moved, not just how much. + +Reply with: + +- **Done:** what they finished in the window, grouped by project. +- **Moved:** the notable changes in their projects, a line each. +- **Still open:** overdue first, then what's due next week. +- **Coming up:** schedule entries next week. + +Link every item to its `app_url`, name the project, and keep it to what a +person would read in two minutes. If they want to share it, offer to draft +it as a check-in answer or a message, and post only once they say so. diff --git a/plugins/basecamp/skills/basecamp/SKILL.md b/plugins/basecamp/skills/basecamp/SKILL.md new file mode 100644 index 0000000..c135cb9 --- /dev/null +++ b/plugins/basecamp/skills/basecamp/SKILL.md @@ -0,0 +1,86 @@ +--- +name: basecamp +description: Work with Basecamp through the Basecamp tools — find, read, create, and update projects, to-dos, cards, messages, comments, documents, schedules, and Campfire chats. Use for any Basecamp question or action, and for any basecamp.com link. +--- + +# Basecamp + +Every Basecamp action is its own tool, named for what it does: `get_*` and +`list_*` read; `create_*`, `update_*`, `complete_*`, `move_*`, `trash_*` and +the rest change data. Everything runs as the signed-in person and sees only +what they can see. + +The server keeps the reference notes on how Basecamp works: +`get_basecamp_guide` returns the core concepts with no `topic`, or one +topic — `concepts` (accounts, projects, the dock, and the IDs each tool +takes), `finding-things`, `my-work`, `posting-and-mentions` (HTML, mentions, +who is notified), and `deleting-and-trash`. Read the topic before a task it +covers that you have not done in this conversation. + +## Where to start + +- **A Basecamp link?** Pass it to `get_by_url` as `url`. It returns the IDs + the link names, the record itself, and, for anything that takes comments + when this connection can post them, `reply_target`: the tool and IDs a + reply goes to. A link to a comment answers with the comment and its + parent as the reply target, since comments are not threaded. No + `reply_target` means no reply can be posted from here. Don't read IDs out + of the link yourself. +- **By keyword?** `search`, then `get_by_url` with a result's `app_url` for + the full record. +- **Mine?** `get_my_assignments` (priorities first), `get_my_day` (today's + schedule and due to-dos), `get_overdue_todos`, `get_upcoming_schedule`, + `get_my_notifications` (the Hey! menu: unreads first), `get_catchup` + (what happened while away). +- **In a project?** `list_projects` or `search` to find it, then + `get_project` for its dock: each dock tool's ID is what that tool's + `list_*` and `create_*` calls take. +- **Long results** come a page at a time. A result carrying `next_cursor` + or `next_page` has more: pass that value back as `cursor` or `page` to + continue. + +## Accounts and projects + +Each connection works in one Basecamp account; `get_me` names the +signed-in person and that account. A link into another account is out of +reach from this connection — `get_by_url` says so; tell the person rather +than guess. When a request needs a project and doesn't name one, ask which +— don't pick one for them. + +## Writing + +- **Rich text** — message bodies, comments, documents, to-do and card notes + — is HTML; titles, to-do names and Campfire lines are plain text. The + `posting-and-mentions` topic lists the tags Basecamp keeps. +- **@-mentions.** `create_comment` takes `mentions`: person IDs from + `list_pingable_people` or `list_project_people`. +- **Assigning.** `assignee_ids` replaces the whole list; include everyone + who stays assigned. Dates are `YYYY-MM-DD`. +- **Notifications.** Posting can notify people, but not always: a message + or document created without `status: "active"` is an unposted draft, and + a to-do or event notifies only when `notify` is true. The + `posting-and-mentions` topic says who each post notifies; report what was + actually sent. Confirm with the person before posting on their behalf to + others, and quote what will be posted. + +## Removing things + +`trash_*` tools move an item to the Basecamp trash, where it can be +restored. Tools whose description says "Permanently" cannot be undone: run +one only when the person asked for exactly that deletion. The +`deleting-and-trash` topic covers the difference, and archiving. + +## Replying well + +Link to what you mention: every item carries an `app_url`. Name the project +and, when the person has several accounts, the account. Say plainly when +something could not be found or read rather than filling the gap. + +**Say what your reads covered.** Every summary, recap, brief or triage +states what its reads did and didn't cover, and never claims more than was +fetched. Reads stop short in known ways: a result marked truncated, a +timeline that ends at its latest 100 events, a window capped at 14 days, an +undated list, a thread whose comments you didn't open, records that include +other people's work. When one applies, say so in the reply ("Done covers +to-dos only", "the timeline reaches back to Tuesday") instead of presenting +the section as complete.