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
11 changes: 10 additions & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
18 changes: 18 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
@@ -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
53 changes: 48 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,43 @@ claude plugin install <plugin-name>

| 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

Expand All @@ -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 <checkout>` to copy and validate it, and commit
with the source commit it prints. `bin/sync-basecamp-plugin --check
<checkout>` reports drift.
55 changes: 55 additions & 0 deletions bin/check-marketplace
Original file line number Diff line number Diff line change
@@ -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"
60 changes: 60 additions & 0 deletions bin/sync-basecamp-plugin
Original file line number Diff line number Diff line change
@@ -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 <basecamp-mcp-server checkout> copy, validate, print the source commit
# bin/sync-basecamp-plugin --check <basecamp-mcp-server checkout> 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)"
21 changes: 21 additions & 0 deletions plugins/basecamp/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
8 changes: 8 additions & 0 deletions plugins/basecamp/.mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"basecamp": {
"type": "http",
"url": "https://mcp.basecamp.com/mcp"
}
}
}
58 changes: 58 additions & 0 deletions plugins/basecamp/README.md
Original file line number Diff line number Diff line change
@@ -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 <project>` | 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 <project> <goal>` | 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 <project> <topic>` | 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
37 changes: 37 additions & 0 deletions plugins/basecamp/commands/catch-up.md
Original file line number Diff line number Diff line change
@@ -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: <project name or link>
---

# 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.
33 changes: 33 additions & 0 deletions plugins/basecamp/commands/draft-check-in.md
Original file line number Diff line number Diff line change
@@ -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.
Loading
Loading