<p align="center">
  <img alt="groundcrew" src="./static/groundcrew-wordmark.svg" height="96">
</p>

<p align="center">
  Dispatch your task backlog to local, interactive AI coding agents. One git worktree per task, sandboxed by default.
</p>

<p align="center">
  <a href="https://www.npmjs.com/package/@clipboard-health/groundcrew"><img alt="npm" src="https://img.shields.io/npm/v/@clipboard-health/groundcrew?style=flat-square&label=npm&color=F7C600&labelColor=111111"></a>
  <a href="https://www.npmjs.com/package/@clipboard-health/groundcrew"><img alt="downloads" src="https://img.shields.io/npm/dw/@clipboard-health/groundcrew?style=flat-square&label=downloads&color=111111&labelColor=111111"></a>
  <a href="https://github.com/ClipboardHealth/groundcrew/actions/workflows/ci.yml"><img alt="ci" src="https://img.shields.io/github/actions/workflow/status/ClipboardHealth/groundcrew/ci.yml?style=flat-square&label=ci&color=77d94e&labelColor=111111"></a>
  <a href="./LICENSE"><img alt="license" src="https://img.shields.io/npm/l/@clipboard-health/groundcrew?style=flat-square&label=license&color=111111&labelColor=111111"></a>
</p>

<p align="center">
  <a href="./static/demo.tape"><img alt="Groundcrew dispatching tasks into tmux panes with coding agents running in parallel" src="./static/demo.gif" width="800"></a>
</p>

Groundcrew watches assigned tasks, creates isolated worktrees, launches agent CLIs in dedicated terminals, and leaves each task's work on its own PR-ready branch. For the backstory, read _[Tickets to pull requests while you sleep](https://www.clipboardworks.com/resources/blog/tickets-to-pull-requests-while-you-sleep)_.

## Why

- **Local.** Agents run on your machine with your tools, shell, and credentials. That makes them more steerable than remote agents, and easy to nudge when they drift.
- **Interactive.** Each task launches the real `claude`, `codex`, `cursor-agent`, or `pi` CLI in its own terminal pane, not a wrapper that approximates it. Watch any session live and take over when you need to.
- **One worktree per task.** Agents work in parallel without stepping on each other.
- **Sandboxed by default.** Safehouse or Docker Sandboxes isolate each agent on the host; `none` is an explicit escape hatch.
- **Pluggable task sources.** Linear by default; Jira and local files via [task sources](./docs/task-sources.md).
- **Multi-agent routing.** Ships `claude`, `codex`, `cursor` (plus a `cursor-grok` variant), and `pi` presets; bring your own CLI in config.

## Prerequisites

`crew doctor` checks all of these, so you can install as you go.

- **Node 24.14.1:** [nvm](https://github.com/nvm-sh/nvm): `nvm install 24.14.1`.
- **git:** e.g., `brew install git`, `apt install git`.
- **A terminal multiplexer:** [tmux](https://github.com/tmux/tmux/wiki/Installing) (cross-platform), [cmux](https://cmux.com/) (macOS), or [zellij](https://zellij.dev/).
- **An agent CLI:** [Claude Code](https://code.claude.com/docs/en/quickstart), [Codex](https://developers.openai.com/codex/quickstart?setup=cli), the [Cursor CLI](https://docs.cursor.com/en/cli/overview) (`cursor-agent`, for the `cursor` and `cursor-grok` presets), and/or [Pi](https://pi.dev/).
- **A sandbox runner:** [Docker Sandboxes](https://docs.docker.com/ai/sandboxes/) (cross-platform) or [Safehouse](https://agent-safehouse.dev/) on macOS. Skip only with `--runner none`.

## Quickstart

```bash
# 1. Install groundcrew.
npm install -g @clipboard-health/groundcrew@latest

# 2. Scaffold a global config. Agents are sandboxed by default
#    (Safehouse/Docker Sandboxes); add --runner none to run unsandboxed on the host.
crew init --global --project-dir ~/dev --repo OWNER/REPO --agent claude

# 3. Run the clone commands printed by `crew init`.

# 4. Safehouse runs use groundcrew's bundled clearance allowlist automatically.
#    Add extra hosts later via CLEARANCE_ALLOW_HOSTS or CLEARANCE_ALLOW_HOSTS_FILES.

# 5. Using Linear? Export your API key. (Jira and other trackers: see Task Pickup.)
export GROUNDCREW_LINEAR_API_KEY="lin_api_..."

# 6. Verify setup, then dispatch.
crew doctor
crew run --watch
```

`crew init --global` writes config to `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/`. Pass `--repo` more than once for multiple repos. `--agent claude`, `--agent codex`, `--agent cursor`, or `--agent pi` chooses the single built-in agent preset to enable in the generated config.

Pi needs credentials only for the provider its selected model uses—not every provider. Authenticate that provider before unattended use; see [Pi provider authentication](./docs/credentials.md#pi-provider-authentication).

When using Pi through tmux 3.5 or later, add the following to `~/.tmux.conf` so tmux preserves modified keys such as `Shift+Enter` and `Ctrl+Enter`:

```tmux
set -g extended-keys on
set -g extended-keys-format csi-u
```

Stop or finish every active Groundcrew task and save work in every other tmux session before running `tmux kill-server`; it terminates the entire shared tmux server, including unrelated user sessions. The change takes effect the next time tmux starts. These are server-wide options, so Groundcrew deliberately does not change them for you. With tmux 3.2–3.4, omit `extended-keys-format`; see [Pi's tmux setup](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/tmux.md).

## Task Pickup

**Not on Linear?** Use Jira or local files via [task sources](./docs/task-sources.md).

Linear works out of the box: assign tasks to yourself and add an `agent-*` label.

- `agent-claude`, `agent-codex`, `agent-pi`, or `agent-<name>` routes to that enabled launch profile.
- `agent-any` routes to the enabled agent with the most session headroom, after skipping agents over their session limit or weekly paced budget.
- Tasks without an `agent-*` label are ignored by `crew run`; dispatch one manually with `crew start <TASK>`.

Agent names are launch profiles, so you can use them to pick model tiers. For example,
define `claude-fable` with `claude --model claude-fable-5 --permission-mode auto`
and `claude-opus` with `claude --model claude-opus-4-8 --permission-mode auto`,
then label tasks with `agent-claude-fable` or `agent-claude-opus`.

Groundcrew scans `workspace.knownRepositories` to infer which repo a task belongs to.

A task blocked by non-terminal blockers is skipped until those blockers are done.

### The task description is the prompt

Groundcrew sends each agent a generic unattended-execution prompt plus the task title and description. The prompt says how to work: read the repo instructions, make the smallest sensible change, verify it, and produce the requested output. The task description says what to do.

Write tasks as complete agent instructions: the goal, the context and constraints, links to logs or screenshots, how to verify, and the output you want. A vague task gets a vague PR.

## Commands

```bash
crew init [--global | --local] [--force] [--dry-run]     # create a crew.config.ts
          [--project-dir <dir>] [--repo <repo>]...
          [--runner <auto|safehouse|sdx|none>] [--agent <claude|codex|cursor|pi>]
crew doctor                                              # check setup
crew source list|verify [<source>]                       # inspect configured task sources
crew task list [--source <name>]                         # list tasks across sources
crew task get <TASK> [--source <name>] [--prompt]        # inspect one task or its prompt
crew task create "Title" --source <name> [--agent <name>] # create a source task
crew task done <TASK> [--allow-dirty]                    # mark a no-PR task done
crew task validate [<source>]                            # validate task content
crew status [<TASK>] [--json [--local-only]]           # inspect current state, or emit it as JSON
crew run [--watch]                                       # one-shot or --watch forever
crew start <TASK>                                      # provision + launch one task now
crew stop <TASK> [--reason <text>]                     # stop workspace, keep worktree
crew resume [--new] <TASK>                             # reopen a paused task (--new: fresh conversation)
crew open <pr> | --branch <name> [--repo <owner/repo>] # iterate on an existing PR or branch
         [--prompt <text> | --prompt-file <path>] [--task <id>] [--dry-run]
crew cleanup [--force] <TASK>                          # tear down every worktree for a task
crew cleanup [--force] --all                           # tear down every idle worktree (no live workspace)
crew upgrade [<version>]                                 # reinstall crew globally through npm
crew completions <bash|zsh|fish>                        # print a shell completion script
```

See [command details](./docs/commands.md) for status output, doctor behavior, and the stop/resume workflow.

### Status snapshots for external monitors

`crew status --json` prints two documents and writes them beside the log file:

```text
<state-dir>/status-local.json     # worktrees, run states, sessions, git status
<state-dir>/status-remote.json    # board, pull requests
```

The two are split by cost. The local tier is local subprocess work, so it is
safe to poll every few seconds. The remote tier is network-bound and
rate-limited, so poll it near your `pollIntervalMilliseconds`.

```bash
crew status --json                # both tiers
crew status --json --local-only   # local tier only; never touches the network
```

`--local-only` is a guarantee about that invocation, not a computed outcome, so
a monitor's fast loop cannot stall on a slow board or a `gh` timeout.

Three rules a reader must honor:

- **Subtract locally.** `status-remote.json` ships board classification without
  the local worktree subtraction. Remove tasks present in the local document
  from `inProgress`, `queueReady`, and `queueBlocked` yourself. Precomputing
  that join would report a just-dispatched task as still queued until the next
  slow poll, which is a false statement rather than stale data.
- **Join pull requests on the worktree directory.** `pullRequestsByWorktree` is
  keyed by absolute worktree path, not by task, because a task with two
  worktrees has two branches. An empty or missing entry means no pull requests
  were found, or the `gh` lookup for that worktree failed; the two are not
  distinguishable.
- **Read the right timestamp.** `payload.capturedAt` describes the last
  successful fetch; `lastAttemptAt` describes the most recent try. Read
  `lastAttemptStatus` to answer "is the board healthy", and `capturedAt` to
  answer "how old is this queue". A failed fetch keeps the previous payload, so
  last-known-good data survives an outage while the document still says the
  board is unreachable.

Durations are never stored, only start instants, so a reader must derive
elapsed time itself. A cached duration would show a frozen clock, which reads
as "the agent stopped working" when it did not.

## Configuration

Workspace settings and at least one enabled agent are required; everything else has a default.

```ts
import type { Config } from "@clipboard-health/groundcrew";

export default {
  workspace: {
    projectDir: "~/dev",
    // Optional: all worktrees go here regardless of where each repo lives.
    // worktreeDir: "~/dev/worktrees",
    // Strings live under projectDir; use { name, projectDirOverride } to override per repo.
    knownRepositories: ["OWNER/REPO"],
  },
  agents: {
    default: "claude",
    definitions: {
      claude: {},
    },
  },
  defaults: {
    hooks: {
      // No-op placeholder; replace with your repo's setup, e.g. "npm ci".
      prepareWorktree: "true",
    },
  },
} satisfies Config;
```

Changing `workspace.worktreeDir` only affects worktrees discovered under the new
root. Clean up existing worktrees before switching it, or temporarily unset
`worktreeDir` when you need `crew cleanup` to find worktrees created beside the
repos.

There is no `linear` config block. Groundcrew reads `GROUNDCREW_LINEAR_API_KEY` first, then falls back to `LINEAR_API_KEY`.

## Reference

- [Configuration](./docs/configuration.md): discovery order, repo layout, scripted/sparse-checkout (graft) worktrees, full config table, prompt customization.
- [Runners](./docs/runners.md): Safehouse, Docker Sandboxes, and the `none` escape hatch.
- [Credentials](./docs/credentials.md): Linear API keys, 1Password, build secrets, and `preLaunch`.
- [Prepare worktree hooks](./docs/setup-hooks.md): `.groundcrew/config.json` `hooks.prepareWorktree` for per-repo dependency setup.
- [Operator host setup](./docs/setup-hooks.md#unsandboxedhooks-operator-only-host-setup): `crew.config.ts` `knownRepositories[].unsandboxedHooks.prepareWorktree` runs trusted, per-repo setup on the host outside the sandbox.
- [Task sources](./docs/task-sources.md): custom shell/Jira/local-plan adapters.
- [Development](./docs/development.md): local source workflow and README/demo asset regeneration.
- [Troubleshooting](./docs/troubleshooting.md): common operational pitfalls and fixes.

## License

[MIT](./LICENSE)
