@clipboard-health/groundcrew 4.4.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,62 +16,55 @@
16
16
  <a href="./LICENSE"><img alt="license" src="https://img.shields.io/npm/l/@clipboard-health/groundcrew?style=flat-square&label=license&color=18181b&labelColor=18181b"></a>
17
17
  </p>
18
18
 
19
- ```text
20
- $ crew status HRD-446
21
- groundcrew status HRD-446
22
- ========================
23
- ticket: hrd-446 in-progress https://linear.app/example/issue/HRD-446
24
- title: Add retry logic to the sync job
25
- run: running; model=claude; updated=2026-05-26T00:01:00.000Z; resumes=0
26
- workspace: live
27
-
28
- Worktrees
29
- ---------
30
- - owner/repo host
31
- branch: rocky-hrd-446
32
- dir: /dev/workspaces/owner/repo-hrd-446
33
- git: dirty (2 modified, 1 untracked)
34
- ```
19
+ Groundcrew watches assigned tickets, creates isolated worktrees, launches agent CLIs in cmux or tmux, and leaves each ticket's work on its own branch. The longer product story is in [Tickets to pull requests while you sleep](https://www.clipboardworks.com/resources/blog/tickets-to-pull-requests-while-you-sleep).
35
20
 
36
21
  ## Why
37
22
 
38
- - **Pluggable ticket sources.** Ships with a built-in Linear adapter (polls your API key viewer's `agent-*`-labeled issues, honors blockers); bring shell, Jira, or any source via `crew.config.ts`.
39
23
  - **One worktree per ticket.** Agents work in parallel without stepping on each other.
40
- - **Local-first sandboxing.** Safehouse on macOS, Docker Sandboxes on Linux/WSL, or an explicit `none` escape hatch.
41
- - **Multi-agent.** Ships with `claude` and `codex`; bring your own CLI via `crew.config.ts`.
24
+ - **Linear out of the box.** Assign yourself an `agent-*` labeled issue and Groundcrew can pick it up.
25
+ - **Local-first isolation.** Safehouse on macOS, Docker Sandboxes on Linux/WSL, or an explicit `none` escape hatch.
26
+ - **Multi-agent routing.** Ships with `claude` and `codex`; bring your own CLI in config.
42
27
 
43
28
  ## Quickstart
44
29
 
45
30
  ```bash
46
- # 1. Install Node 24, git, cmux or tmux, and the agent CLIs you'll use (claude, codex, ...).
31
+ # 1. Install Node >= 24, git, cmux or tmux, and the agent CLI you'll use.
47
32
 
48
- # 2. Install groundcrew
33
+ # 2. Install groundcrew.
49
34
  npm install -g @clipboard-health/groundcrew
50
35
 
51
- # 3. Scaffold a config and edit workspace.projectDir + workspace.knownRepositories
52
- crew init && $EDITOR crew.config.ts
36
+ # 3. Scaffold a global config.
37
+ # runner=none is the quickest path, but it runs agents unsandboxed on the host.
38
+ crew init --global --project-dir ~/dev --repo OWNER/REPO --runner none --model claude
53
39
 
54
- # 4. Clone the repos referenced in your config
55
- PROJECT_DIR="$HOME/dev/c"
56
- mkdir -p "$PROJECT_DIR/OWNER"
57
- git clone git@github.com:OWNER/REPO.git "$PROJECT_DIR/OWNER/REPO"
40
+ # 4. Run the clone commands printed by `crew init`.
58
41
 
59
- # 5. Export your Linear API key
42
+ # 5. If using Linear, export your API key.
60
43
  export GROUNDCREW_LINEAR_API_KEY="lin_api_..."
61
44
 
62
- # 6. Verify setup, then dispatch
45
+ # 6. Verify setup, then dispatch.
63
46
  crew doctor
64
47
  crew run --watch
65
48
  ```
66
49
 
67
- In Linear, assign tickets to yourself and add an `agent-*` label (`agent-claude`, `agent-codex`, or `agent-any`). Groundcrew picks them up across every team and project your API key can see.
50
+ `crew init --global` writes config to `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/`. Pass `--repo` more than once for multiple repos. Pass `--model claude` or `--model codex` to disable the missing CLI if you only have one installed.
51
+
52
+ ## Ticket Pickup
53
+
54
+ For Linear, assign tickets to yourself and add an `agent-*` label:
68
55
 
69
- `crew init --global` writes the config into `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/` instead of the cwd. Both forms refuse to overwrite — pass `--force` to replace, `--dry-run` to preview.
56
+ - `agent-claude`, `agent-codex`, or `agent-<name>` routes to that model.
57
+ - `agent-any` routes to the enabled model with the most available capacity.
58
+ - Tickets without an `agent-*` label are ignored by `crew run`; dispatch one manually with `crew start <TICKET>`.
59
+
60
+ Groundcrew scans `workspace.knownRepositories` to infer which repo a ticket belongs to. A ticket blocked by non-terminal blockers is skipped until those blockers are done.
70
61
 
71
62
  ## Commands
72
63
 
73
64
  ```bash
74
65
  crew init [--global | --local] [--force] [--dry-run] # create a crew.config.ts
66
+ [--project-dir <dir>] [--repo <repo>]...
67
+ [--runner <auto|safehouse|sdx|none>] [--model <claude|codex>]
75
68
  crew doctor # check setup
76
69
  crew status [<TICKET>] # inspect current state or one ticket
77
70
  crew run # one-shot orchestration
@@ -83,474 +76,46 @@ crew cleanup <TICKET> # tear down every workt
83
76
  crew upgrade [<version>] # reinstall crew globally through npm
84
77
  ```
85
78
 
86
- ## Manual repository bootstrap
87
-
88
- Groundcrew never clones repositories for you. Clone each `workspace.knownRepositories` entry into `workspace.projectDir` using the same relative path the config uses. For an `OWNER/REPO` entry:
89
-
90
- ```bash
91
- PROJECT_DIR="$HOME/dev/c"
92
- mkdir -p "$PROJECT_DIR/OWNER"
93
- git clone git@github.com:OWNER/REPO.git "$PROJECT_DIR/OWNER/REPO"
94
- # HTTPS works the same: git clone https://github.com/OWNER/REPO.git "$PROJECT_DIR/OWNER/REPO"
95
- ```
96
-
97
- Bare-name entries have no owner, so pick the remote URL yourself and clone to `$PROJECT_DIR/<name>`.
79
+ See [command details](./docs/commands.md) for status output, doctor behavior, and the stop/resume workflow.
98
80
 
99
81
  ## Configuration
100
82
 
101
83
  Two keys are required; everything else has a default.
102
84
 
103
- | Key | What |
104
- | ----------------------------- | ---------------------------------------------------------------------- |
105
- | `workspace.projectDir` | Parent dir for cloned repos and sibling ticket worktrees. |
106
- | `workspace.knownRepositories` | Repos searched for in ticket descriptions to infer where work belongs. |
107
-
108
- The branch prefix (`<prefix>-<TICKET>`) is derived from `os.userInfo().username` and isn't configurable. There is no `linear` config block — groundcrew picks up every issue assigned to your API key's viewer that carries an `agent-*` label across every visible team and project, governed by a single `orchestrator.maximumInProgress` budget.
109
-
110
- <details>
111
- <summary>Agent label routing</summary>
112
-
113
- - `agent-claude`, `agent-codex`, `agent-<name>` → that model.
114
- - `agent-any` → the model with the most available session capacity.
115
- - Unknown `agent-<name>` → falls back to `models.default` with a warning.
116
- - No `agent-*` label → ignored by `crew run`. Dispatch on demand with `crew start <TICKET>` (also falls back to `models.default`).
117
- - Todo tickets blocked by non-terminal blockers are skipped until their blockers reach a terminal status.
118
-
119
- Status classification uses Linear's workflow `state.type` (`unstarted`, `started`, `completed`, `canceled`, `duplicate`), so renamed status columns work without configuration. Parent issues with children are ignored — sub-issues are the work items.
120
-
121
- </details>
122
-
123
- <details>
124
- <summary>Config discovery</summary>
125
-
126
- Resolution order: `GROUNDCREW_CONFIG` → cosmiconfig project-walk from cwd (any of `crew.config.{ts,mjs,js,json}`, `.crewrc{,.json,.ts}`, `.config/crew.config.{ts,json}`, `.config/crewrc{,.json}`) → `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/crew.config.ts`. The "Loaded config from …" line at startup tells you which won.
127
-
128
- </details>
129
-
130
- <details>
131
- <summary>Full configuration reference</summary>
132
-
133
- | Key | Default | What it does |
134
- | ---------------------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
135
- | `sources` | `[]` | Additional pluggable ticket sources, dispatched alongside the built-in Linear adapter. Built-in kinds: `shell`, `linear`. |
136
- | `git.remote` | `"origin"` | Remote used for `fetch` and as the worktree base ref. |
137
- | `git.defaultBranch` | `"main"` | Branch fetched from `git.remote` and used as the worktree base. |
138
- | `workspace.projectDir` | **required** | Parent dir for cloned repos and sibling ticket worktrees. |
139
- | `workspace.knownRepositories` | **required** | Repos searched for in ticket descriptions to infer where work belongs. A ticket labeled for groundcrew (`agent-*`) fails fast when no known repo appears; unlabeled tickets are ignored. |
140
- | `orchestrator.maximumInProgress` | `4` | Cap on in-progress tickets at once for this `crew` instance. |
141
- | `orchestrator.pollIntervalMilliseconds` | `120_000` | Poll interval in `--watch` mode. |
142
- | `orchestrator.sessionLimitPercentage` | `85` | Number in `(0, 100]`. A model whose codexbar session window exceeds this percentage is skipped that tick. |
143
- | `models.default` | `"claude"` | Tiebreak for `agent-any` resolution and fallback for explicit but unknown `agent-*` labels. Also used by `crew start <TICKET>` for unlabeled tickets. `crew run` ignores unlabeled tickets and does not apply this default. Must exist in `models.definitions`. |
144
- | `models.definitions` | `{ claude, codex }` | Agent definitions. Additive merge with shipped defaults. |
145
- | `models.definitions.<name>.cmd` | — | Shell command launched for the model. Runs in the worktree through the resolved `local.runner`. `{{worktree}}` is replaced before launch; `{{sandbox}}` expands to the sbx sandbox name under the sdx runner and an empty string otherwise. |
146
- | `models.definitions.<name>.color` | — | Color for the workspace status pill (cmux only; tmux silently drops it). |
147
- | `models.definitions.<name>.usage` | optional | If set, codexbar usage is fetched for this model and gated by `sessionLimitPercentage`. Falls back to default when unset, with gating enabled for known models. When `usage.codexbar.source` is omitted, groundcrew uses `oauth` for Codex/Claude on macOS, `auto` for other macOS providers, and `cli` elsewhere. Set to `{ disabled: true }` to disable usage gating. |
148
- | `models.definitions.<name>.sandbox` | optional | Docker Sandboxes binding for the model. Required at launch when `local.runner` resolves to `sdx`. Fields: `agent` (required sbx agent name) and `setupCommand` (override for the inside-sandbox setup script). Groundcrew assumes the `groundcrew-<agent>` sandbox already exists. |
149
- | `models.definitions.<name>.preLaunch` | optional | Host-only shell snippet run **before** the agent exec and **outside** Safehouse/sdx. Exports survive into the launch shell; under the default `safehouse` runner they're only forwarded to the agent when listed via `preLaunchEnv` (recommended) or when `cmd` includes its own `safehouse --env-pass=NAMES`. `{{worktree}}` is substituted. A non-zero exit aborts launch. Not supported when `local.runner` resolves to `sdx` (v1). |
150
- | `models.definitions.<name>.preLaunchEnv` | optional | Companion to `preLaunch`: list of env var names to append to groundcrew's `safehouse-clearance` `--env-pass=` flag, so `preLaunch` exports reach the agent **without** overriding `cmd` and losing the project's egress allowlist. Each entry must match `[A-Za-z_][A-Za-z0-9_]*`. Under `runner: "none"` exports already inherit and `preLaunchEnv` is a no-op. An empty array is a uniform no-op in every runner; a **non-empty** list is rejected when `cmd` already starts with `safehouse` (user owns env forwarding) or when `runner` resolves to `sdx`. |
151
- | `models.definitions.<name>.disabled` | optional | When set to exactly `true`, drops the named shipped default (`claude` or `codex`). Doctor skips probing it; `agent-<name>` labels fall back to `models.default` with a warning. |
152
- | `prompts.initial` | unattended template | First message sent to the agent. Placeholders: `{{ticket}}`, `{{worktree}}`, `{{title}}`, `{{description}}`. Override this from `crew.config.ts` for team-specific statuses, tools, plugins, or review loops. |
153
- | `workspaceKind` | `"auto"` | Terminal session manager. `"auto"` picks `cmux` when on PATH, else `tmux`. Set to `"cmux"` or `"tmux"` to fail loudly when the chosen backend is missing. |
154
- | `local.runner` | `"auto"` | Local isolation backend. `"auto"` → `safehouse` on macOS, `sdx` on Linux/WSL. Explicit: `"safehouse"`, `"sdx"`, `"none"`. `"none"` is never picked implicitly. |
155
- | `logging.file` | XDG state path | Append-mode log file. `log()` / `logEvent()` tee here in addition to stdout. Defaults to `${XDG_STATE_HOME:-$HOME/.local/state}/groundcrew/groundcrew.log`. |
156
-
157
- </details>
158
-
159
- ### Per-session credentials (`preLaunch` + `preLaunchEnv`)
160
-
161
- Build secrets shuttle build-time values _into_ setup. `preLaunch` does the opposite for the _agent_ phase: it runs a host-shell snippet **outside** Safehouse/sdx so the minting snippet never sees `NPM_TOKEN` / `BUF_TOKEN`, and its exports still land in the launch shell. Use this when the agent needs a short-lived credential that has to be minted from something the sandbox can't reach (e.g. an engineer CLI session in Keychain), and you don't want any of that source material — build-time or otherwise — in the minting snippet's environment or in the agent's process.
162
-
163
- The "preLaunch never sees build secrets" contract is enforced differently per runner — same outcome, different mechanism:
164
-
165
- - **`runner: "safehouse"` (default):** `preLaunch` runs immediately after `cd`, **before** `secrets.env` is sourced into the launch shell. `.groundcrew/setup.sh` then runs inside its own profile-neutral `safehouse-clearance` wrap with `--env-pass=NPM_TOKEN,BUF_TOKEN`; build secrets are `unset` on the host before the agent's Safehouse wrap is exec'd.
166
- - **`runner: "none"`:** `secrets.env` is sourced first, `.groundcrew/setup.sh` runs on the host, build-secret names are `unset`, **then** `preLaunch` runs against a clean env, then the agent is exec'd.
167
-
168
- Same security invariant, enforced via source-after-mint ordering under `safehouse` and post-cleanup ordering under `none`. If your snippet depends on a tool installed by `setup.sh`, prefer `runner: "none"` (preLaunch runs after setup) or inline the dependency; under `safehouse`, preLaunch runs before setup.
169
-
170
- Under the default `safehouse` runner, the agent runs under a sanitized env allowlist — exports from `preLaunch` land in the launch shell but are stripped before reaching the agent unless they're forwarded. `preLaunchEnv` is the supported way to forward them: groundcrew passes the names via `--env-pass=` on the **agent** Safehouse wrap, so you keep the project's egress host allowlist (`clearance-allow-hosts`) without touching `cmd`. The names are scoped to the agent wrap only — the setup wrap and `.groundcrew/setup.sh` never see them, by design (setup runs profile-neutral and never carries the agent's grants).
171
-
172
- ```typescript
173
- models: {
174
- definitions: {
175
- claude: {
176
- preLaunch: "SESSION_TOKEN=$(your-mint-command) && export SESSION_TOKEN",
177
- preLaunchEnv: ["SESSION_TOKEN"],
178
- },
179
- },
180
- },
181
- ```
182
-
183
- `&&` ensures `export` only runs when the mint succeeded; a failed mint propagates non-zero out of `preLaunch` and aborts launch before the agent starts. `{{worktree}}` is substituted the same way as in `cmd`. Under `runner: "none"`, exports flow through unchanged and `preLaunchEnv` is a no-op. A **non-empty** `preLaunchEnv` is not supported when `local.runner` resolves to `sdx` in v1 (sdx does not forward arbitrary host env into the sandbox); validated early in `setupWorkspace`. An empty `preLaunchEnv: []` is a uniform no-op in every runner — it forwards zero names, so the unsupported-runner guards do not fire.
184
-
185
- <details>
186
- <summary>Manual fallback when <code>cmd</code> brings its own <code>safehouse</code> wrap</summary>
187
-
188
- If your `cmd` already starts with `safehouse` (because you need wrap flags groundcrew doesn't provide), groundcrew won't auto-compose `--env-pass=` for you and a **non-empty** `preLaunchEnv` is rejected at launch (an empty `[]` is accepted as a no-op). Add the names to your own `cmd` instead — note that this opts the model out of groundcrew's default `safehouse-clearance` wrap (egress allowlist), so re-supply `--append-profile` / `--env` yourself if you need it:
189
-
190
- ```typescript
191
- claude: {
192
- preLaunch: "SESSION_TOKEN=$(your-mint-command) && export SESSION_TOKEN",
193
- cmd: 'safehouse --env-pass=SESSION_TOKEN your-agent-cli',
194
- },
195
- ```
196
-
197
- </details>
198
-
199
- ## Runners
200
-
201
- `local.runner` picks the local isolation backend. `auto` resolves per platform.
202
-
203
- | Runner | Default on | Backend |
204
- | ----------- | ----------- | -------------------------------------------------------------------------------------------------------- |
205
- | `safehouse` | macOS | [Safehouse](https://agent-safehouse.dev/) — fastest local; cannot safely give the agent Docker. |
206
- | `sdx` | Linux / WSL | [Docker Sandboxes](https://docs.docker.com/sandboxes/) (`sbx`) — required when the agent needs `docker`. |
207
- | `none` | — | Unsandboxed escape hatch. Never picked implicitly; doctor warns when configured. |
208
-
209
- <details>
210
- <summary>Safehouse clearance allowlist</summary>
211
-
212
- Only applies when `local.runner` resolves to `safehouse`. Groundcrew starts `clearance` on `http://127.0.0.1:19999` and runs the agent through the bundled `safehouse-clearance` wrapper. Clearance refuses to start without an allowlist — see [its README](https://github.com/ClipboardHealth/core-utils/tree/main/packages/clearance) for proxy env vars, log paths, and DNS rules. Shortest path:
213
-
214
- ```bash
215
- CLEARANCE_ALLOW_HOSTS="api.openai.com,auth.openai.com,api.anthropic.com,mcp.linear.app,api.linear.app" \
216
- crew run --watch
217
- ```
218
-
219
- Groundcrew ships a starter file covering model APIs, Linear, Notion, Slack, Datadog, GitHub, npm, and common dev tooling at `$(npm root -g)/@clipboard-health/groundcrew/clearance-allow-hosts`. Point clearance at it (and optionally a personal file) via `CLEARANCE_ALLOW_HOSTS_FILES`:
220
-
221
- ```bash
222
- CLEARANCE_ALLOW_HOSTS_FILES="$(npm root -g)/@clipboard-health/groundcrew/clearance-allow-hosts:$HOME/.config/clearance/personal-allow-hosts" \
223
- crew run --watch
224
- ```
225
-
226
- Watch `${XDG_CACHE_HOME:-$HOME/.cache}/clearance/clearance.log` for `DENY` lines and add only the domains your agents actually need.
227
-
228
- `@clipboard-health/clearance` is pulled in transitively when you install groundcrew and provides the `clearance` / `clearance-ensure` bins used by Safehouse runs.
229
-
230
- </details>
231
-
232
- <details>
233
- <summary>Docker Sandboxes (sdx) setup</summary>
234
-
235
- Each model that runs under `sdx` needs a `sandbox: { agent: "<sbx-agent>" }` block in `crew.config.ts`. Groundcrew addresses the sandbox as `groundcrew-<agent>` (e.g. `groundcrew-claude`) and reuses one existing sandbox per agent across repos and tickets.
236
-
237
- First-time setup is manual:
238
-
239
- ```bash
240
- sbx create --name groundcrew-claude claude <projectDir>
241
- sbx exec -it groundcrew-claude claude auth login
242
- sbx exec -it groundcrew-claude gh auth login
243
- ```
244
-
245
- Replace `claude` with the sbx agent for the model and `<projectDir>` with `workspace.projectDir` from `crew.config.ts`. Manage lifecycle and auth with `sbx` directly (`sbx ls`, `sbx exec`, `sbx rm`). Groundcrew does not create, authenticate, regenerate, list, or remove sandboxes.
246
-
247
- </details>
248
-
249
- ## Inspecting status
250
-
251
- `crew status <TICKET>` prints a read-only snapshot for one ticket: cached title/URL when present, recorded run state, live workspace presence, matching worktrees, git dirtiness, PR links for matching branches, recent log lines when present, and the ticket status from the configured ticket source. It does not recover, tear down, resume, or mutate any local/remote state.
252
-
253
- `crew status` with no ticket prints the current inventory: known worktrees with cached ticket metadata, workspace/run-state agreement, attach hints, worktree paths, PR links, and stray sessions reported by the configured backend. Local worktree/session diagnostics are printed before ticket-source fetches complete; when the source fetch succeeds, status also prints slot usage plus Queue/Blocked sections for eligible Todo tickets. If the source fetch fails, Queue shows `unavailable: <reason>` and the slots line is omitted.
254
-
255
- Use `crew cleanup <TICKET>` to tear down stale worktrees and `crew resume <TICKET>` to reopen preserved work. Status is intentionally informational only.
256
-
257
- ## Doctor
258
-
259
- `crew doctor` checks host prerequisites only: config validity, ticket-source reachability (every configured source's `verify()`, including the built-in Linear adapter), required binaries on PATH, workspace backend availability, workspace.projectDir, local runner capability, and enabled model commands.
260
-
261
- <details>
262
- <summary>Sample ticket status output</summary>
263
-
264
- ```text
265
- groundcrew status HRD-442
266
- =========================
267
- ticket: hrd-442 in-progress https://linear.app/example/issue/HRD-442
268
- title: Multi-event extractor: year inference can produce date_start > date_end
269
- run: running; model=claude; updated=2026-05-26T00:01:00.000Z; resumes=0
270
- workspace: live
271
-
272
- Worktrees
273
- ---------
274
- - herds-social/herds host
275
- branch: paul-hrd-442
276
- dir: /dev/workspaces/herds-social/herds-hrd-442
277
- git: dirty (0 modified, 1 untracked)
278
- pr: https://github.com/herds-social/herds/pull/224 (open)
279
-
280
- Recent logs
281
- -----------
282
- [10:15:30] Workspace "hrd-442" launched
283
- ```
284
-
285
- </details>
286
-
287
- ### `crew start <TICKET>`
288
-
289
- Launches one ticket immediately, bypassing orchestrator eligibility. Use it to dispatch a specific ticket on demand — including unlabeled tickets that `crew run` ignores.
290
-
291
- ```bash
292
- crew start HRD-442
293
- crew start HRD-442 --dry-run
294
- ```
295
-
296
- ### `crew stop <TICKET>`
297
-
298
- Stops a live workspace pane while preserving the ticket worktree and branch. The manual pause button for cases where you need terminal capacity back, want to stop an agent that's going in the wrong direction, or need to inspect the diff before letting another agent continue.
299
-
300
- ```bash
301
- crew stop HRD-442 --reason "wrong implementation direction"
302
- crew status HRD-442
303
- crew resume HRD-442
304
- ```
305
-
306
- The command closes the cmux/tmux workspace if present, records local run state, and never tears down the worktree. If the workspace was already gone but the worktree is still present, stop records that fact so status can show the preserved branch.
307
-
308
- ### `crew resume <TICKET>`
309
-
310
- Reopens an existing ticket worktree with a continuation prompt. Resume never creates a new worktree; if none exists it fails and leaves re-dispatch to `crew start <ticket>`.
311
-
312
- The resume prompt tells the agent to inspect git status and diff before editing, includes the previous interrupt reason when recorded, and reuses the recorded model, repository, branch, runner, sandbox, and workspace backend. When no run-state file exists but a worktree does, resume falls back to Linear resolution for the model and ticket context.
313
-
314
- ## Secrets
315
-
316
- Groundcrew forwards a small allowlist of build-time secrets from your shell into the setup phase (so `npm install` can authenticate against private registries) and strips them before the agent runs. The agent process never inherits these values.
317
-
318
- Recognized names, defined in [`BUILD_SECRET_NAMES`](./src/lib/buildSecrets.ts):
319
-
320
- - `NPM_TOKEN`
321
- - `BUF_TOKEN`
322
-
323
- Set them in the shell you run `crew` from. Anything not in this list is ignored.
324
-
325
- <details>
326
- <summary>How the secret shuttle works</summary>
327
-
328
- For each ticket:
329
-
330
- 1. If any recognized var is set and non-empty, groundcrew writes `secrets.env` (mode `0600`) into the ticket's temp prompt dir as `KEY='value'` lines — see `stageBuildSecrets` in [`src/commands/setupWorkspace.ts`](./src/commands/setupWorkspace.ts).
331
- 2. The launch script sources `secrets.env` with `set -a` so the values are exported into the setup phase only (and under `sdx`, forwarded into the sandbox via `-e KEY` flags).
332
- 3. After setup completes, the script `unset`s every name in `BUILD_SECRET_NAMES` and then `rm -rf`s the entire prompt dir (including `secrets.env`) before `exec`'ing the agent. See `sourceSecretsLine` / `unsetSecretsLine` and the `rm -rf` / `exec` lines in [`src/lib/launchCommand.ts`](./src/lib/launchCommand.ts). The rollback path on setup failure ([`src/commands/setupWorkspace.ts`](./src/commands/setupWorkspace.ts)) wipes the prompt dir too.
333
-
334
- Net effect: by the time the agent process exists, the values are gone from the environment and the file is gone from disk.
335
-
336
- </details>
337
-
338
- ## Per-repo setup hook
339
-
340
- If `.groundcrew/setup.sh` exists in the repo root, groundcrew runs `bash .groundcrew/setup.sh --deps-only` before each agent launch; otherwise nothing runs. Same convention applies inside the sdx sandbox (overridable per-model via `models.definitions.<name>.sandbox.setupCommand`). No implicit `npm install`, `uv sync`, or anything else — groundcrew is language-agnostic, so opt in by adding the script.
341
-
342
- The `--deps-only` flag tells the script "you're being called by an automated system before an agent launches — skip anything interactive or one-time-only." The same script handles both modes; branch on `$1`:
343
-
344
- - **With `--deps-only`**: do the cheap recurring work this worktree needs (lockfile install, generate types, etc.). No prompts, no global installs, no `nvm` / `pyenv` bootstrap.
345
- - **Without the flag**: full interactive bootstrap. Use this when an engineer runs the script by hand for first-time onboarding, or when wiring it into another tool's SessionStart hook.
346
-
347
- Setup failures are advisory — groundcrew logs the non-zero exit and still launches the agent so a flaky network or stale lockfile doesn't block the session.
348
-
349
- <details>
350
- <summary>Examples</summary>
351
-
352
- **Python (uv):**
353
-
354
- ```bash
355
- #!/usr/bin/env bash
356
- set -euo pipefail
357
- if [ "${1:-}" = "--deps-only" ]; then
358
- uv sync --dev
359
- else
360
- uv sync --dev
361
- # ... extra one-time bootstrap (e.g., pre-commit install, db seed) ...
362
- fi
363
- ```
364
-
365
- **Node (npm):**
366
-
367
- ```bash
368
- #!/usr/bin/env bash
369
- set -euo pipefail
370
- if [ "${1:-}" = "--deps-only" ]; then
371
- npm clean-install
372
- else
373
- npm clean-install
374
- # ... extra one-time bootstrap (e.g., husky install, codegen, link local packages) ...
375
- fi
376
- ```
377
-
378
- **Docs-only or polyglot repo with no install step:** omit the script. With nothing at `.groundcrew/setup.sh`, groundcrew skips the hook silently.
379
-
380
- For a comprehensive real-world example (nvm bootstrap, hash-based skip-on-no-changes caching, portable SHA-256 detection), see [this repo's own `.groundcrew/setup.sh`](./.groundcrew/setup.sh). It's also symlinked at `.claude/setup.sh` so the same script doubles as a Claude Code SessionStart hook for this repo — that symlink is local convenience, not part of groundcrew's contract.
381
-
382
- To scaffold `.groundcrew/setup.sh` with a coding agent (Claude Code, Cursor, etc.), see [docs/setup-hook-agent-prompt.md](./docs/setup-hook-agent-prompt.md) — it encodes the contract above as a copy-pasteable prompt.
383
-
384
- </details>
385
-
386
- ## Pluggable ticket sources
387
-
388
- `sources` declares extra ticket-system adapters. They're verified at `crew run` startup and dispatched alongside the built-in Linear adapter, so a shell, Jira, or local-plan integration feeds the same orchestration loop as Linear.
389
-
390
- The built-in `shell` adapter runs command templates and reads JSON from stdout:
391
-
392
- ```ts
393
- export default {
394
- // ...
395
- sources: [
396
- {
397
- kind: "shell",
398
- name: "jira",
399
- commands: {
400
- verify: "jira me",
401
- fetch: "~/.config/groundcrew/jira-fetch.sh",
402
- resolveOne: "~/.config/groundcrew/jira-resolve.sh ${id}",
403
- markInProgress: "jira issue move ${id} 'In Progress'",
404
- },
405
- timeouts: { fetch: 60_000 },
406
- },
407
- ],
408
- };
409
- ```
410
-
411
- `commands.fetch` must print a JSON array of issues. `commands.resolveOne`, when set, must print one issue, print nothing for "not found", or exit `3` for "not found". `commands.markInProgress`, when set, receives the issue's `sourceRef` as JSON on stdin. `${id}`, `${canonicalId}`, and `${name}` placeholders are shell-quoted before substitution.
412
-
413
- ```json
414
- [
415
- {
416
- "id": "JIRA-123",
417
- "title": "Add retry logic",
418
- "description": "Ticket body",
419
- "status": "todo",
420
- "repository": "your-org/your-repo",
421
- "model": "claude",
422
- "assignee": "Alice",
423
- "updatedAt": "2026-05-22T15:00:00Z",
424
- "blockers": [{ "id": "JIRA-122", "title": "Schema migration", "status": "done" }],
425
- "hasMoreBlockers": false,
426
- "sourceRef": { "nativeId": "10042" }
427
- }
428
- ]
429
- ```
430
-
431
- Allowed `status` values are `todo`, `in-progress`, `in-review`, `done`, and `other`. Use `null` for `repository` or `model` when a ticket should not be groundcrew-eligible. `hasMoreBlockers` is optional and defaults to `false`; `sourceRef` is opaque data that groundcrew passes back to your writeback command.
432
-
433
- ## Prompt customization
434
-
435
- Groundcrew ships one model-agnostic unattended prompt by default. It tells the agent to make reasonable assumptions, follow repository instructions, run documented verification, review its diff, open a PR when GitHub/`gh` is available, and include a workspace continuation hint when known.
436
-
437
- For a personal workflow, keep the prompt next to your local config and load it with `readFileSync`:
438
-
439
85
  ```ts
440
- import { readFileSync } from "node:fs";
86
+ import type { Config } from "@clipboard-health/groundcrew";
441
87
 
442
88
  export default {
443
- // ...
444
- prompts: {
445
- initial: readFileSync(new URL("./initial-prompt.md", import.meta.url), "utf8"),
89
+ workspace: {
90
+ projectDir: "~/dev",
91
+ knownRepositories: ["OWNER/REPO"],
92
+ },
93
+ local: {
94
+ runner: "none",
446
95
  },
447
- };
448
- ```
449
-
450
- This keeps package defaults portable while letting your private config reference team-specific statuses, tools, plugins, or review loops.
451
-
452
- ## Disabling a shipped default model
453
-
454
- Groundcrew ships `claude` and `codex` as default model definitions, additively merged into every resolved config. To stop probing one:
455
-
456
- ```ts
457
- // crew.config.ts
458
- export default {
459
- // …
460
96
  models: {
461
97
  default: "claude",
462
98
  definitions: {
463
99
  codex: { disabled: true },
464
100
  },
465
101
  },
466
- };
467
- ```
468
-
469
- Effects:
470
-
471
- - `crew doctor` does not probe the disabled model's CLI. `crew doctor || exit 1` becomes viable as a CI gate when you only have one agent installed.
472
- - `agent-any` only resolves to enabled models.
473
- - An `agent-<disabled>` label on a ticket falls back to `models.default` with a warning in the log.
474
-
475
- Rules:
476
-
477
- - `disabled` only accepts shipped-default keys (`claude`, `codex`). A typo fails loudly at config load.
478
- - `disabled` must be exactly the boolean `true`.
479
- - It cannot be combined with `cmd`, `color`, or `usage` in the same entry.
480
- - `models.default` must point at an enabled model.
481
-
482
- ## Using 1Password for the API key
483
-
484
- `crew` reads `GROUNDCREW_LINEAR_API_KEY` first, then falls back to `LINEAR_API_KEY`. To resolve from 1Password:
485
-
486
- ```bash
487
- echo "GROUNDCREW_LINEAR_API_KEY='op://<vault>/LINEAR_API_KEY/credential'" > .env.1password
488
- op run --env-file .env.1password -- crew doctor
102
+ } satisfies Config;
489
103
  ```
490
104
 
491
- ## Troubleshooting
492
-
493
- First stop for "what exists locally right now": `crew status <ticket>` shows the ticket's worktrees, workspace presence, run state, logs, and ticket-source status. Use `crew doctor` when you need to verify host setup.
494
-
495
- <details>
496
- <summary>Safehouse-already-wrapped commands are not re-wrapped</summary>
497
-
498
- If a `models.definitions.<name>.cmd` already starts with `safehouse`, groundcrew assumes that command owns its Safehouse flags and does not add the `safehouse-clearance` wrapper a second time. Changing the proxy's allowlist after it's running requires killing the PID in `${XDG_CACHE_HOME:-$HOME/.cache}/clearance/clearance.pid` so the next launch picks up the new env.
499
-
500
- </details>
105
+ There is no `linear` config block. Groundcrew reads `GROUNDCREW_LINEAR_API_KEY` first, then falls back to `LINEAR_API_KEY`.
501
106
 
502
- <details>
503
- <summary>Dead tmux windows vanish by default</summary>
107
+ ## Reference
504
108
 
505
- When a wrapped agent command fails (e.g. `safehouse-clearance` not found, `npm install` crash), the tmux window closes immediately and the error scrolls into the void. Set `GROUNDCREW_KEEP_DEAD_WINDOWS=1` in the env you launch `crew` from to flip the per-window `remain-on-exit` to `on`; the window stays open with the error visible. Close it manually with `tmux kill-window -t groundcrew:<ticket>` after diagnosis. tmux backend only.
506
-
507
- </details>
508
-
509
- <details>
510
- <summary>Tickets stay in-progress until something else moves them</summary>
511
-
512
- Groundcrew sets a ticket to `Started` (the first workflow state with `type === "started"` on that team) when it provisions a workspace and never advances it. The next transition (typically "In Review" when a PR opens) is left to your Linear automation rules.
513
-
514
- </details>
515
-
516
- <details>
517
- <summary>Claude launches in auto mode by default</summary>
518
-
519
- Groundcrew creates isolated per-ticket worktrees for unattended runs, so the shipped `claude` command is `claude --permission-mode auto` to let Claude proceed without stopping for clarifying questions while keeping its built-in safety prompts intact. Override `models.definitions.claude.cmd` for `bypassPermissions` if you need to suppress tool-permission prompts entirely, or for a stricter mode.
520
-
521
- </details>
522
-
523
- <details>
524
- <summary>Doctor's command introspection is shallow</summary>
525
-
526
- Doctor reports the resolved local runner (safehouse / sdx / none) and whether its prerequisites are met, then tokenizes model `cmd` and checks the first two non-flag tokens against PATH. Boolean flags without values, env-var assignments (`FOO=1`), shell pipelines, and subshells are not parsed — verify those manually. When `local.runner` is `"none"`, doctor surfaces a single WARNING line.
527
-
528
- </details>
529
-
530
- <details>
531
- <summary>Doctor checks every enabled model</summary>
532
-
533
- `models.definitions` includes both shipped defaults (`claude`, `codex`) by default via additive merge. If you only intend to label tickets `agent-claude` and don't have `codex` installed, set `models.definitions.codex: { disabled: true }`. Without that, doctor exits non-zero on a missing `codex` binary even though `crew run` would never route to it.
534
-
535
- </details>
536
-
537
- <details>
538
- <summary>Switch to tmux if cmux is misbehaving</summary>
539
-
540
- Set `workspaceKind: "tmux"` to force the tmux backend when cmux's CLI/socket bridge is flaky (symptoms: `cmux --json list-workspaces` returning `Failed to write to socket (Broken pipe)` or `Socket not found at ...cmux.sock` on every tick). tmux is more reliable — just a unix socket, no GUI app — at the cost of losing cmux's status pills, notifications, and sidebar.
541
-
542
- </details>
543
-
544
- <details>
545
- <summary>Agent CLI must accept a positional prompt</summary>
546
-
547
- The handoff is `<your cmd> "<prompt>"`. `claude`, `codex`, and `cursor-agent` all support this.
548
-
549
- </details>
109
+ - [Configuration](./docs/configuration.md): discovery order, repo layout, full config table, prompt customization.
110
+ - [Runners](./docs/runners.md): Safehouse, Docker Sandboxes, and the `none` escape hatch.
111
+ - [Credentials](./docs/credentials.md): Linear API keys, 1Password, build secrets, and `preLaunch`.
112
+ - [Setup hooks](./docs/setup-hooks.md): `.groundcrew/setup.sh --deps-only` for per-repo dependency setup.
113
+ - [Ticket sources](./docs/ticket-sources.md): custom shell/Jira/local-plan adapters.
114
+ - [Troubleshooting](./docs/troubleshooting.md): common operational pitfalls and fixes.
550
115
 
551
116
  ## Development
552
117
 
553
- Clone the repo and the `crew` / `crew:op` scripts execute straight from TypeScript source — no build step needed.
118
+ Clone the repo and run the CLI from TypeScript source:
554
119
 
555
120
  ```bash
556
121
  cd ~/dev/c/groundcrew
@@ -560,11 +125,7 @@ node --run crew -- doctor
560
125
  node --run crew:op -- run --watch
561
126
  ```
562
127
 
563
- Both forms discover config via cosmiconfig — project-walk from cwd for `crew.config.ts` and friends, then `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/crew.config.ts`. Set `GROUNDCREW_CONFIG` to point elsewhere. The `crew:op` wrapper additionally reads `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/op.env` (1Password env-file with `op://` references resolved at launch).
564
-
565
- Logs land in `${XDG_STATE_HOME:-$HOME/.local/state}/groundcrew/groundcrew.log` by default (override via `logging.file`).
566
-
567
- Source edits in `src/**` are picked up on the next invocation. Requires Node ≥ 24 (native `.ts` type stripping).
128
+ Both forms discover config through cosmiconfig. Source edits in `src/**` are picked up on the next invocation. Requires Node >= 24.
568
129
 
569
130
  ## License
570
131
 
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAmPA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCvD"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAoPA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCvD"}
package/dist/cli.js CHANGED
@@ -110,7 +110,7 @@ async function doctorCli(argv) {
110
110
  const SUBCOMMANDS = {
111
111
  init: {
112
112
  summary: "Create a crew.config.ts in the cwd (or --global into the XDG config dir)",
113
- usage: "[--global | --local] [--force] [--dry-run]",
113
+ usage: "[--global | --local] [--force] [--dry-run] [--project-dir <dir>] [--repo <repo>]... [--runner <runner>] [--model <model>]",
114
114
  invoke: initConfigCli,
115
115
  },
116
116
  run: {
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgJH,wBAAsB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CA8E/C"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAyKH,wBAAsB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CA8E/C"}