@clipboard-health/groundcrew 1.12.6 → 2.0.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.
Files changed (38) hide show
  1. package/README.md +32 -80
  2. package/configExample.ts +1 -18
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +0 -11
  5. package/dist/commands/dispatcher.d.ts.map +1 -1
  6. package/dist/commands/dispatcher.js +2 -7
  7. package/dist/commands/doctor.d.ts.map +1 -1
  8. package/dist/commands/doctor.js +5 -16
  9. package/dist/commands/setupWorkspace.d.ts +1 -2
  10. package/dist/commands/setupWorkspace.d.ts.map +1 -1
  11. package/dist/commands/setupWorkspace.js +8 -77
  12. package/dist/index.d.ts +1 -2
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +0 -1
  15. package/dist/lib/boardSource.d.ts +1 -5
  16. package/dist/lib/boardSource.d.ts.map +1 -1
  17. package/dist/lib/boardSource.js +5 -12
  18. package/dist/lib/config.d.ts +4 -22
  19. package/dist/lib/config.d.ts.map +1 -1
  20. package/dist/lib/config.js +4 -69
  21. package/dist/lib/launchCommand.d.ts +1 -14
  22. package/dist/lib/launchCommand.d.ts.map +1 -1
  23. package/dist/lib/launchCommand.js +3 -46
  24. package/dist/lib/localRunner.d.ts.map +1 -1
  25. package/dist/lib/localRunner.js +2 -2
  26. package/dist/lib/worktrees.d.ts +8 -24
  27. package/dist/lib/worktrees.d.ts.map +1 -1
  28. package/dist/lib/worktrees.js +94 -353
  29. package/package.json +2 -2
  30. package/dist/commands/remoteSetup.d.ts +0 -54
  31. package/dist/commands/remoteSetup.d.ts.map +0 -1
  32. package/dist/commands/remoteSetup.js +0 -1032
  33. package/dist/lib/remoteSetupCommand.d.ts +0 -2
  34. package/dist/lib/remoteSetupCommand.d.ts.map +0 -1
  35. package/dist/lib/remoteSetupCommand.js +0 -31
  36. package/dist/lib/spriteRemoteRunnerProvider.d.ts +0 -76
  37. package/dist/lib/spriteRemoteRunnerProvider.d.ts.map +0 -1
  38. package/dist/lib/spriteRemoteRunnerProvider.js +0 -381
package/README.md CHANGED
@@ -15,7 +15,7 @@ This installs the `crew` binary. `@clipboard-health/clearance` is pulled in tran
15
15
 
16
16
  ## Quickstart
17
17
 
18
- 1. **Install prereqs.** Node 24, `git`, `cmux` _or_ `tmux`, and the agent CLIs themselves (`claude`, `codex`, `cursor-agent`, ...). Local runs require macOS with [Safehouse](https://agent-safehouse.dev/) on `PATH`; Linux/WSL hosts must run tickets through the configured remote runner by adding the `agent-remote` label. Sprite is currently the only remote provider. Optional: `codexbar` for session-usage gating. The `workspaceKind` config key picks the workspace backend (`auto` resolves to cmux when installed, else tmux).
18
+ 1. **Install prereqs.** Node 24, `git`, `cmux` _or_ `tmux`, and the agent CLIs themselves (`claude`, `codex`, `cursor-agent`, ...). Groundcrew is **macOS-only** and requires [Safehouse](https://agent-safehouse.dev/) on `PATH`. Optional: `codexbar` for session-usage gating. The `workspaceKind` config key picks the workspace backend (`auto` resolves to cmux when installed, else tmux).
19
19
 
20
20
  2. **Create a Linear project to scope your work.** Any team works — make a project inside it and drop tickets in. The orchestrator polls by project, not by team, so you don't need a dedicated team.
21
21
 
@@ -61,12 +61,9 @@ This installs the `crew` binary. `@clipboard-health/clearance` is pulled in tran
61
61
  op run --env-file .env.1password -- crew doctor
62
62
  ```
63
63
 
64
- 5. **Prepare the runner and agent auth.** Groundcrew supports one local runner and one remote runner:
65
- - macOS local: `cmux` or `tmux` workspace, Safehouse on `PATH`, `clearance`, and locally authenticated agent CLIs.
66
- - macOS remote: local `cmux` or `tmux` workspace that launches the configured remote runner.
67
- - Linux/WSL remote: `cmux` or `tmux` workspace that launches the configured remote runner. Label tickets `agent-remote`; local execution is not supported.
64
+ 5. **Prepare the runner and agent auth.** Groundcrew supports one runner: a `cmux` or `tmux` workspace on macOS, with Safehouse on `PATH`, `clearance`, and locally authenticated agent CLIs.
68
65
 
69
- Local setup fails before creating a worktree when the host is not macOS or `safehouse` is missing. `models.isolation`, per-model `isolation`, and per-model `sandbox` are legacy keys and now fail config validation.
66
+ Setup fails before creating a worktree when the host is not macOS or `safehouse` is missing. `models.isolation`, per-model `isolation`, and per-model `sandbox` are legacy keys and now fail config validation.
70
67
 
71
68
  6. **Set the clearance allowlist for local macOS runs.** Groundcrew starts `clearance` from `@clipboard-health/clearance` on `http://127.0.0.1:19999` (skipping the launch if something is already listening) 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 the proxy's env vars, log paths, and DNS rules. The shortest path is to set the env before `crew run`:
72
69
 
@@ -84,36 +81,7 @@ This installs the `crew` binary. `@clipboard-health/clearance` is pulled in tran
84
81
 
85
82
  Watch `${XDG_CACHE_HOME:-$HOME/.cache}/clearance/clearance.log` for `DENY` lines and add only the domains your agents actually need.
86
83
 
87
- 7. **Optional: prepare a remote runner.** The current remote provider is Sprite, so setup is intentionally explicit: choose which agent CLIs to authenticate and which MCP servers to add. `--mcp` adds and authenticates only the servers you name; it does not attempt to authenticate every MCP server visible in your Claude account.
88
-
89
- ```bash
90
- crew remote setup crew-claude-1 \
91
- --claude \
92
- --codex \
93
- --copy-local-codex-auth \
94
- --datadog \
95
- --github \
96
- --git-name "Your Name" \
97
- --git-email "you@users.noreply.github.com" \
98
- --mcp linear \
99
- --mcp slack \
100
- --checkpoint
101
- ```
102
-
103
- Known MCP aliases are `linear`, `slack`, and `notion`. For another HTTP MCP server, pass `--mcp name=https://example.com/mcp`. The command creates the remote runner if needed, prepares `~/dev`, configures Git, runs selected auth flows, and adds selected MCP servers to Claude Code. With the Sprite provider, `--claude` detects Claude Code's dynamic localhost OAuth callback port inside the remote runner and temporarily runs `sprite proxy` for that port while login runs. Selected MCP servers authenticate through one interactive Claude Code session by default, and when combined with `--claude`, setup skips the separate headless Claude login and uses that one session for both Claude login and MCP auth while forwarding Claude localhost callback ports. `--copy-local-codex-auth` copies `${CODEX_HOME:-$HOME/.codex}/auth.json` into `/home/sprite/.codex/auth.json` and then verifies `codex login status`; it never prints the file contents. `--datadog` installs a pinned `pup` release in the remote runner, verifies its checksum, installs the `dd-pup` guidance for Claude and Codex, verifies `pup auth status`, and when auth is missing temporarily runs `sprite proxy` for the OAuth callback while `pup auth login --read-only` runs in the remote runner. Open the Datadog URL printed by `pup` if your terminal does not open it automatically.
104
-
105
- Repo setup is separate from runner setup and should run after the ticket branch exists, immediately before launching an agent. It clones/fetches the repo in the remote runner using `git.remote`, checks out the requested branch (creating it from the base branch when it does not exist on that remote), forwards only build-time secrets for the dependency install, removes the temporary secret file, clears those env vars, and then exits. It uses groundcrew's remote setup command.
106
-
107
- ```bash
108
- op run --env-file "${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/op.env" -- \
109
- crew remote bootstrap crew-claude-1 core-utils --branch rocky-team-123
110
- ```
111
-
112
- By default bootstrap forwards any locally set `NPM_TOKEN` and `BUF_TOKEN`. Use repeated `--secret <ENV_NAME>` to require a specific set, or `--no-secrets` for public installs. Do not checkpoint after repo bootstrap; dependency state is branch-specific and should be refreshed per ticket.
113
-
114
- To run a ticket remotely through the orchestrator or `crew run --ticket`, label it with `agent-remote` plus the agent label you want, for example `agent-claude` or `agent-codex`. `agent-remote` alone uses `models.default`. Groundcrew keeps cmux/tmux local, creates a per-ticket git worktree in the remote runner under `remote.worktreeRoot`, and asks the configured provider to launch the agent. The Sprite provider emits `sprite exec --tty`. Use `crew remote sessions [<runner-name>]` to inspect active remote sessions and `crew remote attach <session-id-or-command> [--runner <runner-name>]` to attach to one; both commands default to `remote.runnerName` when the runner name is omitted.
115
-
116
- 8. **Run.** Doctor first, then a dry run, then the real thing:
84
+ 7. **Run.** Doctor first, then a dry run, then the real thing:
117
85
 
118
86
  ```bash
119
87
  crew doctor
@@ -126,37 +94,31 @@ This installs the `crew` binary. `@clipboard-health/clearance` is pulled in tran
126
94
 
127
95
  Required fields are marked **required**; everything else has a default and can be omitted from `config.ts`.
128
96
 
129
- | Key | Default | What it does |
130
- | --------------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
131
- | `linear.projectSlug` | **required** | Linear project URL slug (e.g. `ai-strategy-5152195762f3`). The trailing 12-char hex `slugId` is what's matched against Linear's API; the leading name keeps `config.ts` self-documenting and the lookup survives project renames. |
132
- | `linear.statuses.todo` | `"Todo"` | Status name picked up for new work. |
133
- | `linear.statuses.inProgress` | `"In Progress"` | Status set after a workspace is provisioned; counts toward `maximumInProgress`. |
134
- | `linear.statuses.done` | `"Done"` | Status that triggers worktree cleanup. |
135
- | `linear.statuses.terminal` | `["Done"]` | Additional status names treated as terminal for cleanup, board remaining counts, and blocker checks. The `done` status is always included. |
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 local sibling ticket worktrees. Remote ticket worktrees live in `remote.worktreeRoot` on the remote runner. |
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 tickets in `linear.statuses.inProgress` at once. |
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 run --ticket <TICKET>` for unlabeled tickets. `crew run` without `--ticket` 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. Local macOS runs execute in the worktree through Safehouse/clearance; remote runs execute inside the remote worktree. `{{worktree}}` is replaced before launch and legacy `{{sandbox}}` expands to an empty string. |
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`. Omit to never gate. When `usage.codexbar.source` is omitted, groundcrew uses `auto` on macOS and `cli` elsewhere. |
148
- | `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. See "Disabling a shipped default" below. |
149
- | `prompts.initial` | (template) | First message sent to the agent. Placeholders: `{{ticket}}`, `{{worktree}}`, `{{title}}`, `{{description}}`. |
150
- | `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. tmux windows live in a dedicated `groundcrew` session. |
151
- | `remote.provider` | `"sprite"` | Remote runner provider used for tickets labeled `agent-remote`. Sprite is currently the only provider. |
152
- | `remote.runnerName` | `"crew-claude-1"` | Remote runner used for tickets labeled `agent-remote`. |
153
- | `remote.owner` | `"ClipboardHealth"` | GitHub owner used when cloning bare repository names inside the remote runner. |
154
- | `remote.repoRoot` | `"/home/sprite/dev"` | Shared clone root inside the remote runner. |
155
- | `remote.worktreeRoot` | `"/home/sprite/groundcrew/worktrees"` | Per-ticket remote worktree root inside the remote runner. |
156
- | `remote.secretNames` | `["NPM_TOKEN", "BUF_TOKEN"]` | Build-only env vars that may be uploaded for remote dependency setup, then unset before the agent process starts. |
157
- | `logging.file` | XDG state path | Append-mode log file destination. `log()` / `logEvent()` tee here in addition to stdout, so a vanished workspace doesn't take the evidence with it. Defaults to `${XDG_STATE_HOME:-$HOME/.local/state}/groundcrew/groundcrew.log`. |
158
-
159
- The branch prefix (`<prefix>-<TICKET>`) is derived from your OS username (`os.userInfo().username`), not configured. Agent selection looks for a top-level Linear label named `agent-<model>` (e.g. `agent-claude`, `agent-codex`). Add `agent-remote` to run that ticket in the configured remote runner instead of locally; `agent-remote` is a modifier label, not a model. **`crew run` without `--ticket` only fetches tickets with an `agent-*` label** — the GraphQL query filters them server-side, so unlabeled tickets are never returned by Linear's API and do not appear in the rendered board. Use `crew run --ticket <TICKET>` to provision an unlabeled ticket on demand (manual setup falls back to `models.default`). The reserved label `agent-any` routes the ticket to the configured model with the most available session capacity (lowest codexbar session-used percent), skipping any model already over `sessionLimitPercentage`. With no usage data, `agent-any` resolves to `models.default`. The name `any` cannot be used in `models.definitions`. Todo tickets blocked by Linear issues that are not in `linear.statuses.terminal` are skipped until their blockers reach a terminal status.
97
+ | Key | Default | What it does |
98
+ | --------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
99
+ | `linear.projectSlug` | **required** | Linear project URL slug (e.g. `ai-strategy-5152195762f3`). The trailing 12-char hex `slugId` is what's matched against Linear's API; the leading name keeps `config.ts` self-documenting and the lookup survives project renames. |
100
+ | `linear.statuses.todo` | `"Todo"` | Status name picked up for new work. |
101
+ | `linear.statuses.inProgress` | `"In Progress"` | Status set after a workspace is provisioned; counts toward `maximumInProgress`. |
102
+ | `linear.statuses.done` | `"Done"` | Status that triggers worktree cleanup. |
103
+ | `linear.statuses.terminal` | `["Done"]` | Additional status names treated as terminal for cleanup, board remaining counts, and blocker checks. The `done` status is always included. |
104
+ | `git.remote` | `"origin"` | Remote used for `fetch` and as the worktree base ref. |
105
+ | `git.defaultBranch` | `"main"` | Branch fetched from `git.remote` and used as the worktree base. |
106
+ | `workspace.projectDir` | **required** | Parent dir for cloned repos and sibling ticket worktrees. |
107
+ | `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. |
108
+ | `orchestrator.maximumInProgress` | `4` | Cap on tickets in `linear.statuses.inProgress` at once. |
109
+ | `orchestrator.pollIntervalMilliseconds` | `120_000` | Poll interval in `--watch` mode. |
110
+ | `orchestrator.sessionLimitPercentage` | `85` | Number in `(0, 100]`. A model whose codexbar session window exceeds this percentage is skipped that tick. |
111
+ | `models.default` | `"claude"` | Tiebreak for `agent-any` resolution and fallback for explicit but unknown `agent-*` labels. Also used by `crew run --ticket <TICKET>` for unlabeled tickets. `crew run` without `--ticket` ignores unlabeled tickets and does not apply this default. Must exist in `models.definitions`. |
112
+ | `models.definitions` | `{ claude, codex }` | Agent definitions. Additive merge with shipped defaults. |
113
+ | `models.definitions.<name>.cmd` | — | Shell command launched for the model. Runs in the worktree through Safehouse/clearance. `{{worktree}}` is replaced before launch and legacy `{{sandbox}}` expands to an empty string. |
114
+ | `models.definitions.<name>.color` | — | Color for the workspace status pill (cmux only; tmux silently drops it). |
115
+ | `models.definitions.<name>.usage` | optional | If set, codexbar usage is fetched for this model and gated by `sessionLimitPercentage`. Omit to never gate. When `usage.codexbar.source` is omitted, groundcrew uses `auto` on macOS and `cli` elsewhere. |
116
+ | `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. See "Disabling a shipped default" below. |
117
+ | `prompts.initial` | (template) | First message sent to the agent. Placeholders: `{{ticket}}`, `{{worktree}}`, `{{title}}`, `{{description}}`. |
118
+ | `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. tmux windows live in a dedicated `groundcrew` session. |
119
+ | `logging.file` | XDG state path | Append-mode log file destination. `log()` / `logEvent()` tee here in addition to stdout, so a vanished workspace doesn't take the evidence with it. Defaults to `${XDG_STATE_HOME:-$HOME/.local/state}/groundcrew/groundcrew.log`. |
120
+
121
+ The branch prefix (`<prefix>-<TICKET>`) is derived from your OS username (`os.userInfo().username`), not configured. Agent selection looks for a top-level Linear label named `agent-<model>` (e.g. `agent-claude`, `agent-codex`). **`crew run` without `--ticket` only fetches tickets with an `agent-*` label** — the GraphQL query filters them server-side, so unlabeled tickets are never returned by Linear's API and do not appear in the rendered board. Use `crew run --ticket <TICKET>` to provision an unlabeled ticket on demand (manual setup falls back to `models.default`). The reserved label `agent-any` routes the ticket to the configured model with the most available session capacity (lowest codexbar session-used percent), skipping any model already over `sessionLimitPercentage`. With no usage data, `agent-any` resolves to `models.default`. The name `any` cannot be used in `models.definitions`. Todo tickets blocked by Linear issues that are not in `linear.statuses.terminal` are skipped until their blockers reach a terminal status.
160
122
 
161
123
  ### Disabling a shipped default
162
124
 
@@ -191,34 +153,24 @@ Rules:
191
153
  ## Manual commands
192
154
 
193
155
  ```bash
194
- crew remote setup crew-claude-1 --claude --codex --copy-local-codex-auth --datadog --github --mcp linear --checkpoint
195
- crew remote bootstrap crew-claude-1 core-utils --branch rocky-team-123
196
- crew remote sessions
197
- crew remote attach <session-id-or-command> --runner crew-claude-1
198
- crew remote ps crew-claude-1
199
- crew remote interrupt <process-group-id> --runner crew-claude-1
200
156
  crew run --ticket <TICKET>
201
157
  crew setup repos [--dry-run] [<repo>...]
202
158
  crew cleanup <TICKET>
203
159
  ```
204
160
 
205
- `crew run --ticket <TICKET>` provisions a single ticket the same way the orchestrator would: the repo is parsed from the ticket's Linear description, the model comes from the ticket's `agent-*` label, manual setup falls back to `models.default` for unlabeled tickets, and `agent-remote` is honored. If the description does not mention a repo from `workspace.knownRepositories`, setup fails before provisioning. `--watch` and `--ticket` are mutually exclusive — `--watch` drives the orchestrator loop; `--ticket` provisions one ticket and exits. `crew cleanup <TICKET>` resolves to every tracked worktree carrying that ticket id (host and remote kinds, across repos) and tears them all down. To inspect remote sessions, run `crew remote sessions` or pass an explicit runner name. To attach to a listed session id or command selector, run `crew remote attach <session-id-or-command>`. If an attached agent appears stuck in a long-running shell tool, use `crew remote ps <runner-name>` to find the child process group (`PGID`) under the agent, then use `crew remote interrupt <PGID> --runner <runner-name>` to send SIGINT to that child command without killing the agent session. With the Sprite provider, if cleanup cannot remove a remote worktree because the agent is still running, stop that session with `sprite sessions kill -s crew-claude-1 <session-id>` and retry cleanup. To inspect codexbar session windows directly, run `codexbar usage`; the orchestrator already gates on this internally via `orchestrator.sessionLimitPercentage`.
161
+ `crew run --ticket <TICKET>` provisions a single ticket the same way the orchestrator would: the repo is parsed from the ticket's Linear description and the model comes from the ticket's `agent-*` label (manual setup falls back to `models.default` for unlabeled tickets). If the description does not mention a repo from `workspace.knownRepositories`, setup fails before provisioning. `--watch` and `--ticket` are mutually exclusive — `--watch` drives the orchestrator loop; `--ticket` provisions one ticket and exits. `crew cleanup <TICKET>` resolves to every tracked worktree carrying that ticket id (across repos) and tears them all down. To inspect codexbar session windows directly, run `codexbar usage`; the orchestrator already gates on this internally via `orchestrator.sessionLimitPercentage`.
206
162
 
207
163
  ## Gotchas
208
164
 
209
- - **Local execution is macOS plus Safehouse only.** There is no `models.isolation` strategy and no direct local execution mode. On Linux/WSL, label tickets `agent-remote` and run them through the configured remote runner.
165
+ - **Execution is macOS plus Safehouse only.** There is no `models.isolation` strategy and no direct local execution mode. Linux/WSL is not supported.
210
166
  - **Safehouse-already-wrapped commands are not re-wrapped.** 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.
211
167
  - **Legacy Docker Sandboxes state is unmanaged.** Groundcrew no longer discovers or cleans `.sbx` worktrees or persistent Docker Sandboxes containers. If you have old state, inspect and remove it manually with `sbx`.
212
- - **Remote cleanup is also conservative.** `crew cleanup` removes tracked remote worktrees and branches, but it does not kill active remote sessions. Use `crew remote sessions [<runner-name>]` to inspect sessions and, with the Sprite provider, `sprite sessions kill -s <runner-name> <session-id>` when Git reports a worktree is busy.
213
168
  - **Dead tmux windows vanish by default.** 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` (any non-empty value works) 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.
214
- - **Long-running remote shell tools block agent input.** Claude Code and similar TUI agents cannot accept a new prompt while one of their shell tools is still running. Use `crew remote ps <runner-name>` to inspect the remote process tree; interrupt the tool's child `PGID`, not the agent session `PGID`, with `crew remote interrupt <PGID> --runner <runner-name>`.
215
- - **Codex auth in remote runners may need auth-file copy.** If `crew remote setup <runner-name> --codex` finishes interactive login but `codex login status` still fails inside the remote runner, rerun with `--copy-local-codex-auth` after confirming local Codex auth works.
216
- - **Usage source defaults are OS-aware.** `codexbar` usage uses `--source auto` on macOS so CodexBar can prefer account/web sources and fall back as it supports. On Linux/WSL it uses `--source cli`, so install the CodexBar Linux CLI and authenticate the provider CLIs inside that environment.
217
169
  - **Status names matter.** If your team uses `Started` instead of `In Progress`, set `linear.statuses.inProgress = "Started"`.
218
170
  - **Leaf-only.** Parent issues with children are ignored — sub-issues are the work items.
219
171
  - **Tickets stay in the in-progress status until something else moves them.** Groundcrew sets a ticket to `inProgress` when it provisions a workspace and never advances it. The next transition (typically "in review" when a PR opens) is left to your team's Linear automation rules.
220
172
  - **Project must be on a single Linear team in practice.** Cross-team projects work — the orchestrator caches the in-progress state ID per team — but every team in the project must use the same status name for `linear.statuses.inProgress`.
221
- - **Claude launches in bypass-permissions mode by default.** Groundcrew creates isolated per-ticket worktrees and unattended remote sessions, so the shipped `claude` command is `claude --permission-mode bypassPermissions` to avoid workspace-trust and tool-permission prompts blocking automation. Override `models.definitions.claude.cmd` if you want a stricter mode.
173
+ - **Claude launches in bypass-permissions mode by default.** Groundcrew creates isolated per-ticket worktrees for unattended runs, so the shipped `claude` command is `claude --permission-mode bypassPermissions` to avoid workspace-trust and tool-permission prompts blocking automation. Override `models.definitions.claude.cmd` if you want a stricter mode.
222
174
  - **Doctor's command introspection is shallow.** Doctor reports whether the host can run local tickets with macOS plus Safehouse, then tokenizes model `cmd` and checks the first two non-flag tokens against PATH (so `safehouse claude --foo` checks both `safehouse` and `claude`). Boolean flags without values, env-var assignments (`FOO=1`), shell pipelines, and subshells are not parsed — verify those manually. In particular, `npx -y claude` and `env FOO=1 claude` only check the wrapper, not the wrapped CLI.
223
175
  - **Doctor checks every enabled model, including shipped defaults you didn't disable.** `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 }` (see "Disabling a shipped default" under "Config reference"). Without that, doctor exits non-zero on a missing `codex` binary even though `crew run` would never route to it.
224
176
  - **Switch to tmux if cmux is misbehaving.** 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 vertical-tab sidebar.
package/configExample.ts CHANGED
@@ -38,8 +38,7 @@ export const config: Config = {
38
38
  // // Additive: defaults for `claude` and `codex` are merged in unless you
39
39
  // // re-declare those keys here. Add a third agent (e.g. `cursor`) by
40
40
  // // dropping it in this map and tagging tickets with `agent-cursor`.
41
- // // Local runs on macOS are always wrapped with Safehouse/clearance.
42
- // // Linux/WSL users should label tickets `agent-remote` to use the remote runner.
41
+ // // Groundcrew runs agent commands through Safehouse/clearance unless already Safehouse-wrapped.
43
42
  // definitions: {
44
43
  // cursor: {
45
44
  // cmd: "cursor-agent",
@@ -64,22 +63,6 @@ export const config: Config = {
64
63
  // // session and lose status-pill painting (cmux-only feature).
65
64
  // workspaceKind: "auto",
66
65
  //
67
- // remote: {
68
- // // Provider implementation. Sprite is currently the only provider.
69
- // provider: "sprite",
70
- // // Tickets labeled `agent-remote` run through this shared remote runner.
71
- // runnerName: "crew-claude-1",
72
- // // Bare repository names are cloned as `${owner}/${repo}` inside the remote runner.
73
- // owner: "ClipboardHealth",
74
- // // Absolute paths inside the remote runner. Groundcrew creates one shared
75
- // // clone per repo and one remote git worktree per ticket.
76
- // repoRoot: "/home/sprite/dev",
77
- // worktreeRoot: "/home/sprite/groundcrew/worktrees",
78
- // // Build-only env vars forwarded for remote dependency setup, then
79
- // // unset before the agent process starts.
80
- // secretNames: ["NPM_TOKEN", "BUF_TOKEN"],
81
- // },
82
- //
83
66
  // logging: {
84
67
  // // Append-mode log file destination. `log()` / `logEvent()` tee here
85
68
  // // in addition to stdout, so a vanished workspace doesn't take the
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAsIA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BvD"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AA0HA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BvD"}
package/dist/cli.js CHANGED
@@ -2,7 +2,6 @@ import { createRequire } from "node:module";
2
2
  import { cleanupWorkspaceCli } from "./commands/cleanupWorkspace.js";
3
3
  import { doctor } from "./commands/doctor.js";
4
4
  import { orchestrate } from "./commands/orchestrator.js";
5
- import { remoteCli } from "./commands/remoteSetup.js";
6
5
  import { setupReposCli } from "./commands/setupRepos.js";
7
6
  import { setupWorkspaceCli } from "./commands/setupWorkspace.js";
8
7
  import { errorMessage, writeError, writeOutput } from "./lib/util.js";
@@ -77,16 +76,6 @@ const SUBCOMMANDS = {
77
76
  usage: "repos [--dry-run] [<repo>...]",
78
77
  invoke: setupCli,
79
78
  },
80
- remote: {
81
- summary: "Create, authenticate, bootstrap, and inspect a remote runner",
82
- usage: "setup <runner-name> [--claude] [--codex] [--datadog] [--github] [--mcp <alias|name=url>] [--checkpoint]\n" +
83
- " → crew remote bootstrap <runner-name> <repo> [--branch <branch>]\n" +
84
- " → crew remote sessions [<runner-name>]\n" +
85
- " → crew remote attach <session-id-or-command> [--runner <runner-name>]\n" +
86
- " → crew remote ps [<runner-name>]\n" +
87
- " → crew remote interrupt <process-group-id> [--runner <runner-name>]",
88
- invoke: remoteCli,
89
- },
90
79
  };
91
80
  function printHelp() {
92
81
  const width = Math.max(...Object.keys(SUBCOMMANDS).map((key) => key.length));
@@ -1 +1 @@
1
- {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/commands/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,uBAAuB,CAAC;AACjG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAczD,UAAU,cAAc;IACtB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,UAAU,EAAE;QAClB,KAAK,EAAE,UAAU,CAAC;QAClB,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;QAC1C,+FAA+F;QAC/F,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;QACvD,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,CAAC,EAAE,WAAW,CAAC;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAwNjE"}
1
+ {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/commands/dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,uBAAuB,CAAC;AACjG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAczD,UAAU,cAAc;IACtB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,UAAU,EAAE;QAClB,KAAK,EAAE,UAAU,CAAC;QAClB,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;QAC1C,+FAA+F;QAC/F,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;QACvD,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,CAAC,EAAE,WAAW,CAAC;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,GAAG,UAAU,CAmNjE"}
@@ -57,7 +57,6 @@ export function createDispatcher(deps) {
57
57
  ticket: verdict.issue.id,
58
58
  blockers: verdict.blockers,
59
59
  model: verdict.model,
60
- runner: verdict.issue.runner,
61
60
  });
62
61
  }
63
62
  async function startEligibleIssue(start, dryRun, signal) {
@@ -68,14 +67,13 @@ export function createDispatcher(deps) {
68
67
  if (dryRun) {
69
68
  log(
70
69
  /* v8 ignore next @preserve -- classifyTodo forces recovery=false in dry-run, so the resume branch can't fire here */
71
- `[dry-run] Would ${recovery ? "resume" : "start"} ${issue.id} in ${issue.repository} (${issue.model}, ${issue.runner})`);
70
+ `[dry-run] Would ${recovery ? "resume" : "start"} ${issue.id} in ${issue.repository} (${issue.model})`);
72
71
  logEvent("dispatch", {
73
72
  outcome: "skipped",
74
73
  reason: "dry_run",
75
74
  ticket: issue.id,
76
75
  model: issue.model,
77
76
  repository: issue.repository,
78
- runner: issue.runner,
79
77
  });
80
78
  return;
81
79
  }
@@ -88,7 +86,6 @@ export function createDispatcher(deps) {
88
86
  repository: issue.repository,
89
87
  ticket: issue.id,
90
88
  model: issue.model,
91
- runner: issue.runner,
92
89
  };
93
90
  await (signal === undefined
94
91
  ? setupWorkspace(config, setupOptions)
@@ -100,7 +97,6 @@ export function createDispatcher(deps) {
100
97
  ticket: issue.id,
101
98
  model: issue.model,
102
99
  repository: issue.repository,
103
- runner: issue.runner,
104
100
  });
105
101
  }
106
102
  catch (error) {
@@ -110,7 +106,6 @@ export function createDispatcher(deps) {
110
106
  ticket: issue.id,
111
107
  model: issue.model,
112
108
  repository: issue.repository,
113
- runner: issue.runner,
114
109
  error: errorMessage(error),
115
110
  });
116
111
  }
@@ -183,7 +178,7 @@ export function createDispatcher(deps) {
183
178
  log(`${slots} slot(s) available, starting ${starts.length} ticket(s): ${starts.map(({ issue }) => `${issue.id}(${issue.model})`).join(", ")}`);
184
179
  logEvent("dispatch", {
185
180
  outcome: "starting",
186
- tickets: starts.map(({ issue }) => `${issue.id}:${issue.model}:${issue.runner}`),
181
+ tickets: starts.map(({ issue }) => `${issue.id}:${issue.model}`),
187
182
  });
188
183
  for (const start of starts) {
189
184
  // oxlint-disable-next-line no-await-in-loop -- one workspace at a time avoids racing on git
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAmIH,wBAAsB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAmE/C"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAiIH,wBAAsB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CA6D/C"}
@@ -89,8 +89,6 @@ function commandTokensToCheck(cmd) {
89
89
  function gatherToolTokens(config) {
90
90
  const all = new Set();
91
91
  for (const definition of Object.values(config.models.definitions)) {
92
- // Local runs execute the agent command on the host; remote runs need the
93
- // same command in the remote runner, but doctor cannot know ticket labels in advance.
94
92
  for (const token of commandTokensToCheck(definition.cmd)) {
95
93
  all.add(token);
96
94
  }
@@ -150,9 +148,7 @@ export async function doctor() {
150
148
  for (const token of toolTokens) {
151
149
  const required = localCapability.ok;
152
150
  // oxlint-disable-next-line no-await-in-loop -- doctor reports tools in deterministic order
153
- const check = await checkCmd(token, required, required
154
- ? undefined
155
- : "required for local runs; remote runs need this inside the remote runner");
151
+ const check = await checkCmd(token, required, required ? undefined : "required for local runs");
156
152
  checks.push(check);
157
153
  }
158
154
  if (anyModelUsesUsage(config)) {
@@ -174,21 +170,14 @@ export async function doctor() {
174
170
  return true;
175
171
  }
176
172
  function localCapabilityCheck(host) {
177
- if (!host.isMacOS) {
178
- return {
179
- name: "local runner (macOS + Safehouse)",
180
- ok: false,
181
- required: false,
182
- hint: "required for local runs; on Linux/WSL use agent-remote with the remote runner",
183
- };
184
- }
173
+ const supportsLocalRunner = host.isSafehouseSupported && host.hasSafehouse;
185
174
  return {
186
175
  name: "local runner (macOS + Safehouse)",
187
- ok: host.hasSafehouse,
176
+ ok: supportsLocalRunner,
188
177
  required: false,
189
- hint: host.hasSafehouse
178
+ hint: supportsLocalRunner
190
179
  ? "ready"
191
- : "required for local runs; install Safehouse from https://agent-safehouse.dev/ and ensure `safehouse` is on PATH",
180
+ : "groundcrew requires macOS with Safehouse on PATH (install from https://agent-safehouse.dev/)",
192
181
  };
193
182
  }
194
183
  function reportLocalCapability(check) {
@@ -1,4 +1,4 @@
1
- import { type ResolvedConfig, type WorkspaceRunner } from "../lib/config.ts";
1
+ import { type ResolvedConfig } from "../lib/config.ts";
2
2
  interface TicketDetails {
3
3
  title: string;
4
4
  description: string;
@@ -7,7 +7,6 @@ export interface SetupWorkspaceOptions {
7
7
  ticket: string;
8
8
  repository: string;
9
9
  model: string;
10
- runner?: WorkspaceRunner;
11
10
  /** When provided, skip the Linear lookup for prompt-template fields. */
12
11
  details?: TicketDetails;
13
12
  }
@@ -1 +1 @@
1
- {"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AAOA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAC;AAc1B,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAgBD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,wEAAwE;IACxE,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAsED,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,GAAE,wBAA6B,GACxC,OAAO,CAAC,IAAI,CAAC,CA8Ef;AAuHD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAyBf"}
1
+ {"version":3,"file":"setupWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/setupWorkspace.ts"],"names":[],"mappings":"AAOA,OAAO,EAAkC,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AASvF,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAgBD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAmED,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,EAC9B,UAAU,GAAE,wBAA6B,GACxC,OAAO,CAAC,IAAI,CAAC,CAoEf;AA6CD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAoBf"}
@@ -3,15 +3,14 @@ import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { ensureClearance } from "@clipboard-health/clearance";
5
5
  import { fetchResolvedIssue } from "../lib/boardSource.js";
6
- import { BUILD_SECRET_NAMES, loadConfig, } from "../lib/config.js";
6
+ import { BUILD_SECRET_NAMES, loadConfig } from "../lib/config.js";
7
7
  import { detectHostCapabilities } from "../lib/host.js";
8
- import { buildLaunchCommand, buildRemoteLaunchCommand, shellSingleQuote, } from "../lib/launchCommand.js";
8
+ import { buildLaunchCommand, shellSingleQuote } from "../lib/launchCommand.js";
9
9
  import { createLinearIssueStatusUpdater } from "../lib/linearIssueStatus.js";
10
10
  import { assertLocalRunnerRequirements } from "../lib/localRunner.js";
11
- import { getRemoteRunnerProvider } from "../lib/spriteRemoteRunnerProvider.js";
12
11
  import { errorMessage, getLinearClient, log, readEnvironmentVariable } from "../lib/util.js";
13
12
  import { workspaces } from "../lib/workspaces.js";
14
- import { repoDirFor, worktrees } from "../lib/worktrees.js";
13
+ import { worktrees } from "../lib/worktrees.js";
15
14
  async function fetchTicket(ticket) {
16
15
  const client = getLinearClient();
17
16
  const issue = await client.issue(ticket.toUpperCase());
@@ -34,9 +33,9 @@ function renderPrompt(template, variables) {
34
33
  * dir is `rm -rf`'d by the launch command (and rollback path), so cleanup
35
34
  * is already handled.
36
35
  */
37
- function stageBuildSecrets(promptDir, secretNames = BUILD_SECRET_NAMES) {
36
+ function stageBuildSecrets(promptDir) {
38
37
  const lines = [];
39
- for (const name of secretNames) {
38
+ for (const name of BUILD_SECRET_NAMES) {
40
39
  const value = readEnvironmentVariable(name);
41
40
  if (value === undefined || value.length === 0) {
42
41
  continue;
@@ -71,23 +70,14 @@ function stagePrompt(input) {
71
70
  }
72
71
  export async function setupWorkspace(config, options, runOptions = {}) {
73
72
  const { ticket, repository, model } = options;
74
- const runner = options.runner ?? "local";
75
73
  const { signal } = runOptions;
76
74
  const definition = config.models.definitions[model];
77
75
  if (!definition) {
78
76
  throw new Error(`Unknown model: ${model}`);
79
77
  }
80
- if (runner === "remote") {
81
- await setupRemoteWorkspace({
82
- config,
83
- options: { ...options, runner },
84
- ...(signal === undefined ? {} : { signal }),
85
- });
86
- return;
87
- }
88
78
  assertLocalRunnerRequirements(await detectHostCapabilities(signal));
89
79
  await ensureClearance({ logger: log });
90
- const spec = { repository, ticket, model, runner };
80
+ const spec = { repository, ticket };
91
81
  const created = signal === undefined
92
82
  ? await worktrees.create(config, spec)
93
83
  : await worktrees.create(config, spec, signal);
@@ -136,64 +126,6 @@ export async function setupWorkspace(config, options, runOptions = {}) {
136
126
  throw error;
137
127
  }
138
128
  }
139
- async function resolveTicketDetails(options) {
140
- if (options.details !== undefined) {
141
- return options.details;
142
- }
143
- log(`Fetching ${options.ticket} from Linear...`);
144
- return await fetchTicket(options.ticket);
145
- }
146
- async function setupRemoteWorkspace(arguments_) {
147
- const { config, options, signal } = arguments_;
148
- const { ticket, repository, model } = options;
149
- const definition = config.models.definitions[model];
150
- /* v8 ignore next 3 @preserve -- setupWorkspace validates the model before routing here */
151
- if (definition === undefined) {
152
- throw new Error(`Unknown model: ${model}`);
153
- }
154
- log(`Workspace runner: remote (${config.remote.provider}:${config.remote.runnerName})`);
155
- const spec = { repository, ticket, model, runner: "remote" };
156
- const created = signal === undefined
157
- ? await worktrees.create(config, spec)
158
- : await worktrees.create(config, spec, signal);
159
- const { branchName, dir: remoteWorktreeDir } = created;
160
- const worktreeName = `${repository}-${ticket}`;
161
- let promptDir;
162
- try {
163
- const ticketDetails = await resolveTicketDetails(options);
164
- const stagedPrompt = stagePrompt({ config, ticket, ticketDetails, worktreeName });
165
- promptDir = stagedPrompt.directory;
166
- const secretsFile = stageBuildSecrets(promptDir, config.remote.secretNames);
167
- const remotePromptFile = `/tmp/groundcrew-${ticket}-prompt.txt`;
168
- const remoteSecretsFile = secretsFile === undefined ? undefined : `/tmp/groundcrew-${ticket}-secrets.env`;
169
- const remoteLaunchCommand = buildRemoteLaunchCommand({
170
- definition,
171
- provider: getRemoteRunnerProvider(config.remote.provider),
172
- remoteConfig: config.remote,
173
- promptFile: stagedPrompt.file,
174
- remotePromptFile,
175
- worktreeDir: remoteWorktreeDir,
176
- secretNames: config.remote.secretNames,
177
- ...(secretsFile === undefined ? {} : { secretsFile, remoteSecretsFile }),
178
- });
179
- const launchCmd = stageWorkspaceLaunchCommand(promptDir, remoteLaunchCommand);
180
- log("Opening workspace...");
181
- await workspaces.open(config, {
182
- name: ticket,
183
- cwd: repoDirFor(config, repository),
184
- command: launchCmd,
185
- status: { text: `${model}:remote`, color: definition.color, icon: "sparkle" },
186
- }, signal);
187
- log(`Workspace "${ticket}" launched (${model}, remote)`);
188
- log(` Worktree: ${remoteWorktreeDir}`);
189
- log(` Branch: ${branchName}`);
190
- log(` Remote: ${config.remote.provider}:${config.remote.runnerName}`);
191
- }
192
- catch (error) {
193
- await rollbackWorktree({ config, entry: created, promptDir });
194
- throw error;
195
- }
196
- }
197
129
  async function rollbackWorktree(arguments_) {
198
130
  log(`Setup failed; rolling back worktree ${arguments_.entry.repository}-${arguments_.entry.ticket}...`);
199
131
  let result;
@@ -234,16 +166,15 @@ export async function setupWorkspaceCli(ticket, options = {}) {
234
166
  const config = await loadConfig();
235
167
  const client = getLinearClient();
236
168
  const resolved = await fetchResolvedIssue({ client, config, ticket });
237
- log(`Resolved ${ticket}: repository=${resolved.repository}, model=${resolved.model}, runner=${resolved.runner}`);
169
+ log(`Resolved ${ticket}: repository=${resolved.repository}, model=${resolved.model}`);
238
170
  if (options.dryRun === true) {
239
- log(`[dry-run] Would launch ${ticket} in ${resolved.repository} (${resolved.model}, ${resolved.runner})`);
171
+ log(`[dry-run] Would launch ${ticket} in ${resolved.repository} (${resolved.model})`);
240
172
  return;
241
173
  }
242
174
  await setupWorkspace(config, {
243
175
  ticket: ticket.toLowerCase(),
244
176
  repository: resolved.repository,
245
177
  model: resolved.model,
246
- runner: resolved.runner,
247
178
  details: { title: resolved.title, description: resolved.description },
248
179
  });
249
180
  await createLinearIssueStatusUpdater({ config, client }).markInProgress({
package/dist/index.d.ts CHANGED
@@ -2,9 +2,8 @@ export { run } from "./cli.ts";
2
2
  export { cleanupWorkspace, type CleanupWorkspaceOptions } from "./commands/cleanupWorkspace.ts";
3
3
  export { doctor } from "./commands/doctor.ts";
4
4
  export { orchestrate, type OrchestratorOptions } from "./commands/orchestrator.ts";
5
- export { bootstrapRemoteRepository, type RemoteBootstrapOptions, type RemoteSetupOptions, setupRemoteRunner, } from "./commands/remoteSetup.ts";
6
5
  export { setupWorkspace, type SetupWorkspaceOptions } from "./commands/setupWorkspace.ts";
7
- export type { Config, ModelDefinition, RemoteRunnerConfig, RemoteRunnerProviderName, ResolvedConfig, WorkspaceRunner, } from "./lib/config.ts";
6
+ export type { Config, ModelDefinition, ResolvedConfig } from "./lib/config.ts";
8
7
  export { loadConfig } from "./lib/config.ts";
9
8
  export { getUsageByModel, type UsageByModel } from "./lib/usage.ts";
10
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAChG,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,KAAK,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,EACL,yBAAyB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,iBAAiB,GAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,YAAY,EACV,MAAM,EACN,eAAe,EACf,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,EACd,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAChG,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,KAAK,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -2,7 +2,6 @@ export { run } from "./cli.js";
2
2
  export { cleanupWorkspace } from "./commands/cleanupWorkspace.js";
3
3
  export { doctor } from "./commands/doctor.js";
4
4
  export { orchestrate } from "./commands/orchestrator.js";
5
- export { bootstrapRemoteRepository, setupRemoteRunner, } from "./commands/remoteSetup.js";
6
5
  export { setupWorkspace } from "./commands/setupWorkspace.js";
7
6
  export { loadConfig } from "./lib/config.js";
8
7
  export { getUsageByModel } from "./lib/usage.js";
@@ -4,7 +4,7 @@
4
4
  * typed `BoardState` instead of raw nodes.
5
5
  */
6
6
  import type { LinearClient } from "@linear/sdk";
7
- import { type ResolvedConfig, type WorkspaceRunner } from "./config.ts";
7
+ import { type ResolvedConfig } from "./config.ts";
8
8
  export interface Blocker {
9
9
  id: string;
10
10
  title: string;
@@ -22,8 +22,6 @@ export interface Issue {
22
22
  repository: string | undefined;
23
23
  /** `undefined` when the ticket has no `agent-*` label — i.e. not groundcrew's concern. */
24
24
  model: string | undefined;
25
- /** `undefined` when the ticket has no `agent-*` label — i.e. not groundcrew's concern. */
26
- runner: WorkspaceRunner | undefined;
27
25
  teamId: string;
28
26
  blockers: Blocker[];
29
27
  hasMoreBlockers: boolean;
@@ -36,7 +34,6 @@ export interface Issue {
36
34
  export type GroundcrewIssue = Issue & {
37
35
  model: string;
38
36
  repository: string;
39
- runner: WorkspaceRunner;
40
37
  };
41
38
  export declare function isGroundcrewIssue(issue: Issue): issue is GroundcrewIssue;
42
39
  export interface BoardState {
@@ -70,7 +67,6 @@ interface ResolvedIssue {
70
67
  description: string;
71
68
  repository: string;
72
69
  model: string;
73
- runner: WorkspaceRunner;
74
70
  teamId: string;
75
71
  }
76
72
  /**