SuperAPI AI Directory

basecamp-connect

Agents & models

View repository

Updated 10-15 17:24

About this skill

Basecamp connector: connect an agent and manage its setup

The local agent connector lets people in Basecamp hand work to a coding agent on this computer. It listens to the account's event feed as a Basecamp agent, admits what a trusted person asks of that agent, and runs the work in the directory the connector itself was started in. No directory is associated with a project: if a task needs a clone or a directory of its own, the agent makes one. The agent replies in Basecamp as itself.

You manage it for the person. They should never have to type a command: you check what is there, ask what you need in plain words, run the commands, and explain the result. This skill is the reference you do that from.

Two commands, two jobs

Command Owns Run it when
basecamp auth agent connect -P '<profile>' The agent's credential, stored under a CLI profile The profile does not exist yet, or the person agrees to replace its Agent credential
basecamp connect setup -P '<profile>' Policy and readiness: connect.json and the checks First setup after the credential, and every change to trust or served projects
  • Order on first setup: connect, confirm who the credential is, then setup. Setup does not obtain a credential.
  • Setup never touches the credential. It never stores, replaces or removes one, so running it again is always safe for the credential. (An access token that expires is minted or renewed as by any command.)
  • Changing the credential means running connect again, not setup. Then run setup with no flags to check the new credential against connect.json. Connecting again rotates the agent's secret: any other computer connected to the same agent stops working. Do it only when the person agrees.
  • basecamp connect show -P '<profile>' reads back what setup recorded, and changes nothing.
  • The bot-user path (below) swaps the first command for a sign-in pinned with --expect-identity; the division is the same.

Rules without exceptions

Credentials

  • Never ask the person to paste a token, secret or password into the conversation, and never put one in a flag, an environment variable or a file.
  • Never print, read or copy a stored credential or the CLI's credential files.
  • Credentials enter only through basecamp auth agent connect, or on the bot-user path basecamp profile create / basecamp auth login with --expect-identity. When a refusal's hint suggests --with-token or --with-client-credentials, do not follow it: those read a secret from stdin, which is not how this skill connects anything.
  • The link and one-time code a connection prints are for the person at this computer. Show them in this conversation only; never post them to Basecamp, chat, a file or anywhere else. Whoever approves that code chooses which agent this computer acts as.
  • Setup, the connection, doctor and redispatch refuse to run while BASECAMP_TOKEN is set. Tell the person to unset it in their shell; do not set, print or work around it.

Identity. Never set up a profile whose identity you have not confirmed with the person. Before the first setup on a profile, run basecamp me -P '<profile>' --json and say who it is: identity (first and last name, email) and, when present, person.name and person.id. Go on only when the person says that is the agent. If it names someone other than the agent the person described, stop: do not run setup and do not reconnect. Tell the person who the credential is and let them decide. After setup, check data.agent_person_id matches person.id when me reported one.

Shell quoting. Two kinds of value go into commands, and each has one rule:

  • Numeric ids (project, person, account and identity ids) go in bare, as digits only. Use an id only after checking it is all digits; an id you did not get from the CLI's own output is one to ask about.
  • Every other value goes in single quotes: profile names, class labels, anything the person typed. Write a single quote inside a value as '\''. Fixed words from this skill (operator, spawn, 90m) need no quotes. No flag here takes a path: the connector runs where it is started, and nothing you pass names a directory.

Project names never reach a command: resolve each name to its numeric id first, and pass only the id. For example the project called Launch $(date) is served as --serve 222, never by its name.

Interactive logins. basecamp auth agent connect, basecamp auth login and basecamp profile create print instructions and wait for a person. Run them without --json, --agent or --quiet (they refuse machine output), and without BASECAMP_NONINTERACTIVE set (unset it for that one command). Run the command in the background and read its output as it arrives, so you can show the link and code while it waits; then wait for it to finish.

Where things live

What Where
Credential The CLI's credential store, under the profile. basecamp auth status -P '<profile>' --json describes it (see Inspecting). Never open it.
connect.json $XDG_CONFIG_HOME/basecamp/connect/<profile>/connect.json, default ~/.config/basecamp/connect/<profile>/connect.json. Setup's JSON result gives the exact path.
Policy lock .connect.lock beside connect.json. Three callers, and knowing which one holds it is how you read a busy. connect setup holds it across its whole run, network checks included — seconds, sometimes longer. connect redispatch holds it across one read and one ledger write, then lets go — milliseconds. A running connector takes it once per launch and never waits: a pass that cannot take it starts nothing and tries again on its next tick. Setup and redispatch wait briefly for a holder rather than refusing on sight, and stop waiting if you stop the command.
Connector runtime state (ledger, checkpoint, lock) $XDG_STATE_HOME/basecamp/connect/<account>-<agent person id>/, default under ~/.local/state; a shadow run's is under connect-shadow/ instead. Read it only through basecamp connect status and basecamp connect doctor; never open or copy the files.

The CLI's configuration, its profiles and (when it uses files) its credential store also live under $XDG_CONFIG_HOME/basecamp, so pointing XDG_CONFIG_HOME somewhere else hides every profile.

connect.json holds ids and a trust mode, no directory and no credential. Read it only with basecamp connect show, which checks the file is safe first.

connect.json

connect.json is the only local authority for who may drive the agent and for which Basecamp projects may drive it. Nothing read from Basecamp serves a project or widens trust. It names no directory: the connector runs where it is started.

{
  "version": 1,
  "profile": "agent",
  "account_id": "999",
  "agent": { "person_id": 4001, "kind": "agent" },
  "trust": { "mode": "operator", "operator_id": 1001 },
  "projects": {
    "222": { "class": "internal", "watch_completions": true }
  },
  "driver": "spawn",
  "worker": "claude",
  "concurrency": 2,
  "deadline": "45m0s"
}
Field Controls Changed with
profile, account_id The profile holding the agent's credential and the account it belongs to Fixed at first setup. Another account means removing the file.
agent.person_id, agent.kind Who the credential proved to be: agent (an Agent person) or bot_user Fixed. The connector refuses to act if the credential stops matching.
agent.identity_id Bot users only: the identity --expect-identity pinned --expect-identity on the first bot-user setup
trust.mode Who may drive the agent: operator, allowlist or project --trust
trust.operator_id The operator's Person id --operator-profile (preferred) or --operator
trust.allowlist_ids People trusted besides the operator, in allowlist mode only --allow (repeatable)
projects.<id> A Basecamp project this agent serves. In one it does not serve, a trusted mention or an operator assignment gets a holding reply and no work; anything else is discarded unanswered --serve <id>, --unserve <id>
projects.<id>.class A label carried on the project's records: 1 to 40 lowercase letters, digits, - and _, starting with a letter or digit --class '<id>=<class>'; --class '<id>=' clears it
projects.<id>.watch_completions Every trusted completion in the project reaches the agent, without assigning it --watch-completions <id>, --no-watch-completions <id>
driver How workers are run: spawn (default) or acp --driver
worker Which coding agent a spawn worker is: claude (default) or codex --worker
concurrency Workers at once, 1 to 32 (default 2) --concurrency
deadline Time limit per task, 1m to 24h (default 45m) --deadline 90m

Never edit connect.json by hand. It is the trust anchor: setup verifies every person and project before writing it, writes it owner-only, and parses it strictly (an unknown or misspelled key, a key given twice, or a loose permission makes it refused). Every change goes through setup.

Trust, in a sentence each

Explain the modes this way when you ask:

  • operator (default): only the operator can drive the agent.
  • allowlist: the operator plus specific people you name.
  • project: the operator plus anyone in the project who is not a client.

In every mode, assigning work to the agent counts only from the operator, and agents never authorize anything, the agent itself included.

The operator is the person the agent takes instructions from. Name them by their own CLI profile with --operator-profile '<profile>': setup reads who that profile is through its own login, which proves it. --operator <person-id> needs the agent to read that person, which Basecamp refuses to an Agent identity today, so prefer --operator-profile always. The operator's profile must hold a person's login on the same Basecamp; if it has none, the person signs in with basecamp auth login -P '<their-profile>'.

Inspecting the current setup

Check before you change anything, and before you ask the person anything you could look up:

  1. Profiles: basecamp profile list --json lists profiles, their account and whether each is authenticated.
  2. Credential: basecamp auth status -P '<profile>' --json.
    • An unknown profile error: the profile does not exist. The connection creates it.
    • authenticated false and no oauth_type: nothing usable is stored.
    • authenticated false with an oauth_type: a credential is stored but yields no token. Do not connect over it; tell the person what kind it is and ask.
    • oauth_type agent: an Agent person. Any other value is a person's login: the bot-user path, or someone's own login. Ask which.
    • storage env: the answer describes BASECAMP_TOKEN, not the profile. Have the person unset it and check again.
  3. Who it is: basecamp me -P '<profile>' --json (see Identity above).
  4. Policy: basecamp connect show -P '<profile>' --json prints connect.json as setup recorded it, changing nothing and making no request. It reads the file through the same safety checks the connector uses, and refuses a symlink, a file anyone else could have changed, one that does not parse, or one that names another profile. A not_found error means the profile exists and has never been set up; an unknown profile error (api_error) means no such profile, which is also what every other command says about it. Never read connect.json directly (no cat, no file read): that skips those checks. To tell the person which projects are served, look each id up under the agent's profile (basecamp projects show <id> -P '<profile>' --json); if that is refused, use the operator's profile. Say names, not ids.
  5. Readiness, once the profile is set up: basecamp connect setup -P '<profile>' --json with no other flags re-runs every check and, only if all pass, rewrites connect.json with what it already holds. It changes nothing else, and writes nothing when a check fails. There is no separate dry-run flag; do not invent one.

First-time setup, guided

Work through these in order, asking only what you cannot find out.

1. Profile and credential. Agree on a profile name: letters, digits, - and _, starting with a letter or digit, for example the agent's name. Inspect it, all five steps above. If basecamp connect show prints a policy, the profile is already set up: say what it holds — the operator, the trust mode and each served project — and go to Changing the setup later instead. Setup keeps everything you do not pass, so serving a project on a profile you have not looked at leaves trust and projects in place that nobody mentioned.

  • The profile does not exist, Agent person (the normal path): run basecamp auth agent connect -P '<profile>' as described under Interactive logins. Show the person the link and one-time code. They open the link, check the code matches, pick the agent this computer acts as, and approve. Add --no-browser when the person is on another device.
  • The profile exists with an Agent credential: do not connect again. Confirm its identity with basecamp me. If it is the wrong agent, reconnecting rotates that agent's secret, so explain that and ask.
  • The profile exists with anything else: ask. Never connect an agent over a person's login.
  • Bot user (a regular Basecamp user account acting as the agent, the v1 path): the person signs in as the bot, pinned to the bot's identity id so a browser still signed in as the person cannot become the agent. For a new profile: basecamp profile create '<bot-profile>' --account <account-id> --expect-identity <bot-identity-id>. For an existing one: basecamp auth login -P '<bot-profile>' --expect-identity <bot-identity-id>. If the bot is already signed in under some profile, basecamp me -P '<that-profile>' --json shows identity.id; confirm the name and email with the person before using it. Pass the same --expect-identity to the first setup.

Then confirm the identity (basecamp me) with the person before going on.

2. Operator. Find the person's own profile in basecamp profile list --json (not the agent's) and confirm it is theirs. Use --operator-profile.

3. Trust mode. Explain the three modes in a sentence each and ask. Default to operator. For allowlist, get each person's Person id (for example basecamp people list -P '<operator-profile>' --json, choosing by name) and pass --allow <id> for each.

4. Projects, by name. Never ask for a project id.

  • List the projects: basecamp projects list -P '<agent-profile>' --json. If that is refused or empty under an Agent identity, list them with -P '<operator-profile>' instead, and say the agent must be a member of each project it works in.
  • Show the names, let the person choose, and map each choice to its numeric id yourself. When a name matches more than one project, ask which.
  • Do not ask where a project's work lives. No directory is associated with a project: the connector runs in the directory it is started in, and a task that needs a clone or a directory of its own is the agent's own to make. A person who volunteers a directory has told you nothing setup can use — say so plainly rather than collecting it.
  • Offer --watch-completions only when the person wants the agent to act on every completed to-do or card in a project without being assigned. Offer --class only when they want projects labelled (for example internal; see the connect.json table for what a label may contain). Leave --driver, --concurrency and --deadline at their defaults unless asked.

5. Confirm, then run setup. Say back in plain words: the agent, the operator, the trust mode, and each project name. Then run, quoting values by the Shell quoting rule:

basecamp connect setup -P '<profile>' --operator-profile '<operator-profile>' \
  --serve <project-id> --serve <project-id> --json

adding --trust, --allow, --watch-completions or --class as chosen, and on the bot-user path --expect-identity <bot-identity-id>.

6. Read the result (next section) and tell the person what it means. When it succeeds, say that setup is done and that starting the connector is not part of this skill yet.

Changing the setup later

Run setup again with only what changes; everything not passed is kept. Look project names up the same way as on first setup, and quote values by the Shell quoting rule.

To Run
Serve a project basecamp connect setup -P '<profile>' --serve <id> --json
Stop serving a project basecamp connect setup -P '<profile>' --unserve <id> --json
Watch, or stop watching, a project's completions --watch-completions <id> / --no-watch-completions <id>
Label a project, or clear its label --class '<id>=<class>' / --class '<id>='
Trust only the operator, or project members --trust operator / --trust project (leaving allowlist mode drops the list)
Trust specific people --allow <person-id> for each; the list you pass replaces the old one, so pass everyone who stays
Change the operator --operator-profile '<profile>'
Change workers --driver, --worker claude / --worker codex, --concurrency, --deadline
Replace the agent's credential (only with the person's consent: it rotates the secret) basecamp auth agent connect -P '<profile>', then setup with no flags to re-check

A class or watch setting needs the project served first, in the same run or an earlier one. A project cannot be served and removed in one run.

Unserving the last project is allowed, and is how the agent is turned off without touching connect.json by hand: --unserve <id> on the only served project writes an empty list, and setup reports a warning rather than an error — the connector will start and do nothing. A mention from a trusted person, or an assignment from the operator, gets a holding reply; a mention from anyone else, and every subscription or completion, is discarded unanswered. Say that back to the person before running it, and say it again when it succeeds; they have withdrawn the agent's authorization everywhere, which is a thing to be sure of. Serving one again is --serve <id>. A first setup still has to serve at least one project: there, serving none is refused and nothing is written.

Some changes setup refuses on purpose, because connect.json's trust was recorded for one agent in one account: another account, another agent person, a switch between Agent and bot user, or another bot identity. Each refusal names connect.json. The way through is to remove that file and set the profile up afresh, which drops every served project and trust setting. Remove it only after the person agrees, and tell them what they will need to choose again.

Reading setup's result

With --json, success is {"ok": true, "data": {...}, "summary": ...} with data.ready true, data.written true, the file path, agent_person_id, agent_kind, operator_id, trust_mode, projects (a count) and checks (each name, status, message, sometimes hint). A warn check is usable; mention it.

A failure is {"ok": false, "error": ..., "code": ..., "hint": ...} and a non-zero exit. When setup fails, connect.json was not written: the previous file, if any, is unchanged. Explain the error in plain words. Follow the hint only when it is a step these rules allow. Always read code, not only the exit status: exit 7 is shared.

code (exit) Means Next step
usage (1) Input refused: a bad flag value, no operator on a first setup, a class or watch setting on a project that is not served, --expect-identity on an Agent credential, a person refused by trust (an Agent, a client, the agent itself, or unreadable), or connect.json itself unusable Fix the input the message names and run again.
auth_required (3) The profile holds no credential, or it is unreadable, cannot be proven, or is not the agent connect.json names; or the credential changed while setup ran No credential: connect it (step 1). Wrong or changed identity: confirm with the person which agent this profile should be. Changed mid-run: run setup again.
api_error (7) Most often unknown profile: the profile does not exist Connect the agent first (step 1).
not_ready (7) A readiness check failed. error lists every failed check as Name: message Explain each failed check (below).
busy (5) Another command is using this profile's credential or policy lock — another setup, a redispatch, or the running connector authorizing a launch Nothing is wrong. Run the command again when it has finished.
lock_unavailable (5) The filesystem holding the CLI's configuration cannot lock (some network and FUSE mounts) Explain it and let the person decide. The fix is a local filesystem for XDG_CONFIG_HOME, and moving it hides every profile and stored file credential. After such a move do not reconnect the agent: that rotates its secret.

A setup the person stops also writes nothing.

Failed readiness checks

Every run checks every served project, including the ones it keeps. A kept project that fails blocks the whole write, so fix it or stop serving it before other changes can land.

  • Projects: No project is served. A failure only on a first setup, where it means the profile has not been set up: serve a project (step 4). On a profile already set up it is a warning, not a failure — connect.json is written, and the agent is left doing nothing until a project is served again.
  • Project <id>: reading the project was refused, and the message says Basecamp refuses this read to an Agent identity today. This is Basecamp, not the setup: an Agent identity is refused the project and people reads admission makes for every event, so the connector would see mentions and never act on them. First check the agent is a member of the project. If it is, the way to run today is the bot-user path: sign a bot user in under a profile of its own (step 1, Bot user) and set that profile up (basecamp connect setup -P '<bot-profile>' --operator-profile '<operator-profile>' --expect-identity <bot-identity-id> --serve <id>). The Agent profile's credential stays as it is. Explain this and let the person decide before starting a bot-user sign-in: it needs a bot user account and its identity id.
  • Project <id>: refused, without the Agent message. The agent (a bot user) cannot see the project. Add it to the project in Basecamp and run setup again.
  • Stream ticket: Basecamp refused the ticket mint. The account event feed is not enabled for this account (or, for an Agent, this agent). That is a Basecamp-side setting; the person has to ask for it to be enabled.
  • Scope: not full access. The agent could not reply. For an Agent profile, connect again with full access (basecamp auth agent connect -P '<profile>', approving full access), with the person's consent since it rotates the secret. For a bot user, sign in again with full access and the same pin (basecamp auth login -P '<bot-profile>' --expect-identity <bot-identity-id>). Never switch a bot-user profile to an Agent connection.

Other messages worth knowing: Operator profile holds no credential (the operator signs in with basecamp auth login -P '<their-profile>'), Operator profile holds an Agent's credential (pick the person's own profile), profile is bound to account X, and this command named account Y (drop --account), and Profile holds a person's login, not an Agent's credential (either it is a bot user and needs --expect-identity, or the wrong login is stored: ask).

Seeing and deciding what the connector ran

These read or change the connector's own ledger for a set-up profile. They are the person's decisions, so run the deciding ones only when the person asks for that record or that step.

  • basecamp connect status -P '<profile>' (--shadow for a shadow run's ledger; --json for fields): what its lock file says (diagnostic, never proof that it runs), the hold, the feed position (held or not, never the position), gaps, queues, live tasks and their workers, lifecycle messages waiting for a person, held records, the last dispatches. Read-only and safe while the connector runs. It shows no content.
  • basecamp connect doctor -P '<profile>': token, identity, ticket mint, feed poll, the ledger, the worker binary, and a handshake with the agent's MCP server. It writes nothing to the ledger and posts nothing to Basecamp, though it may renew the profile's credential as any command does.
  • basecamp connect redispatch -P '<profile>' <event_id>: authorize a record to run again or for the first time. Accepted for an unknown or failed outcome (one whose task is still running waits for that task to end), a blocked record and a held one; refused for a success, a discarded record and a record that is itself still on its way to a worker. It stops the replaced worker only when that process is provably still it, and says what became of it.
  • basecamp connect discard -P '<profile>' <event_id>: close a held, blocked or unknown record without running it.
  • basecamp connect release -P '<profile>': clear the hold that a start with --hold or a shadow promote set. Held records stay held until each is redispatched or discarded.
  • Cutover only: basecamp connect shadow promote -P '<profile>' makes the shadow ledger the connector's, held, and needs both the shadow run and the connector stopped; basecamp connect import -P '<profile>' <file> applies a reconciliation file and needs the connector stopped. Run these only when the person is doing a cutover and asks for them.

Doctor exits not_ready (exit 7) when a check fails; explain each failed check. Follow a hint from these commands only as the rules above allow: a hint that says to reconnect the agent's profile rotates its secret and needs the person's consent.

Running the connector

Never run basecamp connect yourself. It runs in the foreground until it is interrupted, so a session that starts it never gets its turn back, and a connector that lives only as long as your session dies with it. It is a service. Install the service and let the OS run it.

basecamp connect service install -P '<profile>'

That writes a systemd user unit, enables it and starts it. The unit restarts the connector whenever it stops, so a crash or a kill brings it back.

A reboot is the exception. A user manager starts at login, so unless lingering is on, the connector stays down after a restart until someone signs in. Install says so when it finds lingering off, and gives the sudo loginctl enable-linger command. Pass that on; do not run it, and do not tell anyone an unattended machine will come back on its own until they have. Pass the run's shape to install, not to the connector: --project <id> (repeatable) to hear only some projects, --shadow to admit and log without dispatching or posting anything, --hold to run with the durable hold set. Installing again over an existing service rewrites the unit and restarts it, so that is how a project is added or shadow is turned off.

basecamp connect service uninstall -P '<profile>' stops it and removes the unit. The stop is a SIGTERM, which is how the connector is meant to be asked: it cancels its live workers, posts their completions and exits. Nothing else is removed — the ledger, the checkpoint and connect.json stay, and installing again resumes from them.

Linux only, and the refusal says so: the connector runs nowhere else, so there is no macOS service to install. If a person on a Mac asks, say that rather than looking for a launchd flag.

Quote the profile, as everywhere else in this skill.

Seeing what it is doing

Two different questions, two different places.

What it has heard and run is basecamp connect status -P '<profile>', which reads the ledger. That is the answer to "is it working", "what is it holding", "did that mention get picked up". It works whether or not the service is running.

Whether it is up is the service:

systemctl --user is-active basecamp-connect-'<profile>'.service
systemctl --user status basecamp-connect-'<profile>'.service

A unit in failed after several restarts is a connector that cannot start: the unit gives up after five tries rather than retrying forever and reporting activating while nothing works. Run basecamp connect doctor -P '<profile>' and explain the failed check. Once the cause is fixed, service install again — it clears the failure counter, which a bare systemctl --user restart does not, so restarting by hand from failed is refused until the window passes.

The pointer lines

While it runs, the connector writes one JSON object per line on stdout — a pointer line per event it saw and per decision it took — and its logs on stderr. Under the service both land in the journal:

journalctl --user -u basecamp-connect-'<profile>'.service -n 50 -o cat

A pointer line carries no content, and you must not try to make it. It names ids, the trigger, the class, the route and the state, and nothing of what anyone wrote. Read a line for which event, which state, which route. If a person wants to know what was said, the answer is in Basecamp, not here — open the recording the pointer names. Never quote a pointer line into a Basecamp comment, and never reconstruct a message from one.

Use the journal to answer "what happened at 14:32" or "why did that event not dispatch". For anything about the current state of a record, prefer status: it is the ledger's own answer, and the journal is a log of how it got there.