@clipboard-health/groundcrew 4.0.1 → 4.0.3
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 +70 -80
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +64 -28
- package/dist/commands/doctor.d.ts +1 -6
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +24 -37
- package/dist/commands/interruptWorkspace.d.ts.map +1 -1
- package/dist/commands/interruptWorkspace.js +4 -4
- package/dist/commands/setupRepos.d.ts.map +1 -1
- package/dist/commands/setupRepos.js +2 -13
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +178 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/lib/util.d.ts +14 -2
- package/dist/lib/util.d.ts.map +1 -1
- package/dist/lib/util.js +39 -2
- package/package.json +1 -1
- package/dist/commands/ticketCheck.d.ts +0 -22
- package/dist/commands/ticketCheck.d.ts.map +0 -1
- package/dist/commands/ticketCheck.js +0 -23
- package/dist/commands/ticketDoctor.d.ts +0 -223
- package/dist/commands/ticketDoctor.d.ts.map +0 -1
- package/dist/commands/ticketDoctor.js +0 -1134
package/README.md
CHANGED
|
@@ -17,24 +17,31 @@
|
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
```text
|
|
20
|
-
$ crew
|
|
21
|
-
groundcrew
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
20
|
+
$ crew status HRD-446
|
|
21
|
+
groundcrew status HRD-446
|
|
22
|
+
========================
|
|
23
|
+
ticket: hrd-446
|
|
24
|
+
|
|
25
|
+
Config snapshot
|
|
26
|
+
---------------
|
|
27
|
+
projectDir: /dev/workspaces
|
|
28
|
+
repositories: owner/repo
|
|
29
|
+
git: remote=origin; defaultBranch=main
|
|
30
|
+
workspaceKind: auto
|
|
31
|
+
|
|
32
|
+
Worktree state
|
|
33
|
+
--------------
|
|
34
|
+
- owner/repo host
|
|
35
|
+
branch: rocky-hrd-446
|
|
36
|
+
git: dirty (2 modified, 1 untracked)
|
|
37
|
+
|
|
38
|
+
Workspace probe
|
|
39
|
+
---------------
|
|
40
|
+
live: yes
|
|
41
|
+
|
|
42
|
+
Last Linear status
|
|
43
|
+
------------------
|
|
44
|
+
In Progress (state.type=started) — Add retry logic to the sync job
|
|
38
45
|
```
|
|
39
46
|
|
|
40
47
|
## Why
|
|
@@ -75,16 +82,18 @@ In Linear, assign tickets to yourself and add an `agent-*` label (`agent-claude`
|
|
|
75
82
|
```bash
|
|
76
83
|
crew init [--global | --local] [--force] [--dry-run] # create a crew.config.ts
|
|
77
84
|
crew doctor # check setup
|
|
78
|
-
crew
|
|
79
|
-
crew run # one-shot
|
|
85
|
+
crew status [<TICKET>] # inspect current state or one ticket
|
|
86
|
+
crew run # one-shot orchestration
|
|
80
87
|
crew run --watch # poll forever
|
|
81
|
-
crew
|
|
88
|
+
crew start <TICKET> # provision + launch one ticket now
|
|
82
89
|
crew setup repos [<repo>...] [--dry-run] # clone known repos via gh
|
|
83
|
-
crew
|
|
90
|
+
crew stop <TICKET> [--reason <text>] # stop workspace, keep worktree
|
|
84
91
|
crew resume <TICKET> # reopen a paused ticket
|
|
85
92
|
crew cleanup <TICKET> # tear down every worktree for a ticket
|
|
86
93
|
```
|
|
87
94
|
|
|
95
|
+
Deprecated aliases still work but print a warning and will be removed in the next major version: `crew interrupt` → `crew stop`, `crew run --ticket <TICKET>` → `crew start <TICKET>`, `crew doctor --ticket <TICKET>` → `crew status <TICKET>`.
|
|
96
|
+
|
|
88
97
|
## Configuration
|
|
89
98
|
|
|
90
99
|
Two keys are required; everything else has a default.
|
|
@@ -102,7 +111,7 @@ The branch prefix (`<prefix>-<TICKET>`) is derived from `os.userInfo().username`
|
|
|
102
111
|
- `agent-claude`, `agent-codex`, `agent-<name>` → that model.
|
|
103
112
|
- `agent-any` → the model with the most available session capacity.
|
|
104
113
|
- Unknown `agent-<name>` → falls back to `models.default` with a warning.
|
|
105
|
-
- No `agent-*` label → ignored by `crew run`. Dispatch on demand with `crew
|
|
114
|
+
- No `agent-*` label → ignored by `crew run`. Dispatch on demand with `crew start <TICKET>` (also falls back to `models.default`).
|
|
106
115
|
- Todo tickets blocked by non-terminal blockers are skipped until their blockers reach a terminal status.
|
|
107
116
|
|
|
108
117
|
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.
|
|
@@ -129,7 +138,7 @@ Resolution order: `GROUNDCREW_CONFIG` → cosmiconfig project-walk from cwd (any
|
|
|
129
138
|
| `orchestrator.maximumInProgress` | `4` | Cap on in-progress tickets at once for this `crew` instance. |
|
|
130
139
|
| `orchestrator.pollIntervalMilliseconds` | `120_000` | Poll interval in `--watch` mode. |
|
|
131
140
|
| `orchestrator.sessionLimitPercentage` | `85` | Number in `(0, 100]`. A model whose codexbar session window exceeds this percentage is skipped that tick. |
|
|
132
|
-
| `models.default` | `"claude"` | Tiebreak for `agent-any` resolution and fallback for explicit but unknown `agent-*` labels. Also used by `crew
|
|
141
|
+
| `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`. |
|
|
133
142
|
| `models.definitions` | `{ claude, codex }` | Agent definitions. Additive merge with shipped defaults. |
|
|
134
143
|
| `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. |
|
|
135
144
|
| `models.definitions.<name>.color` | — | Color for the workspace status pill (cmux only; tmux silently drops it). |
|
|
@@ -185,91 +194,72 @@ Groundcrew auto-creates sandboxes when missing but never deletes them — they p
|
|
|
185
194
|
|
|
186
195
|
</details>
|
|
187
196
|
|
|
188
|
-
##
|
|
197
|
+
## Inspecting status
|
|
189
198
|
|
|
190
|
-
`crew
|
|
199
|
+
`crew status <TICKET>` prints a read-only snapshot for one ticket: resolved config, matching worktrees, workspace probe result, recorded run state, recent log lines for that ticket, and the latest Linear status. It does not fetch, recover, tear down, resume, or mutate any local/remote state.
|
|
191
200
|
|
|
192
|
-
|
|
201
|
+
`crew status` with no ticket prints the current inventory: known worktrees with workspace/run-state presence plus live workspaces reported by the configured backend.
|
|
193
202
|
|
|
194
|
-
|
|
203
|
+
Use `crew cleanup <TICKET>` to tear down stale worktrees and `crew resume <TICKET>` to reopen preserved work. Status is intentionally informational only.
|
|
195
204
|
|
|
196
|
-
|
|
197
|
-
- `--no-fetch` — skip the upfront `git fetch origin <branch>` before checking remote presence.
|
|
205
|
+
## Doctor
|
|
198
206
|
|
|
199
|
-
|
|
200
|
-
| ---------------- | --------------------------------------------------------------------------------------------- |
|
|
201
|
-
| `pr-open` | Nothing — the PR is the source of truth. |
|
|
202
|
-
| `pr-merged` | Done. |
|
|
203
|
-
| `in-flight` | The ticket is still being worked on; the verdict line names the workspace pane to attach to. |
|
|
204
|
-
| `recoverable` | Run the printed `nextStep` exactly. |
|
|
205
|
-
| `interrupted` | Resume the preserved worktree with `crew resume <ticket>` or inspect it by hand. |
|
|
206
|
-
| `failed-launch` | Fix the launch failure, then run `crew resume <ticket>` or `crew cleanup <ticket>`. |
|
|
207
|
-
| `would-dispatch` | Pre-dispatch checks pass; the orchestrator will pick the ticket up on its next tick. |
|
|
208
|
-
| `ineligible` | A resolution or eligibility check failed; the reason after the colon names the failing check. |
|
|
209
|
-
| `unresolvable` | The Linear ticket couldn't be fetched; the reason after the colon names the error. |
|
|
210
|
-
| `lost` | No trace exists. Re-dispatch via `crew run --ticket <ticket>`. |
|
|
207
|
+
`crew doctor` checks host prerequisites only: config validity, Linear reachability, required binaries on PATH, workspace backend availability, workspace.projectDir, local runner capability, and enabled model commands.
|
|
211
208
|
|
|
212
209
|
<details>
|
|
213
|
-
<summary>Sample output
|
|
214
|
-
|
|
215
|
-
The Workspace section appends an attach hint to the pane name when the workspace backend exposes one (e.g. `tmux attach -t <session>:<pane>` or `cmux attach <name>`), so the verdict line is immediately actionable.
|
|
210
|
+
<summary>Sample ticket status output</summary>
|
|
216
211
|
|
|
217
212
|
```text
|
|
218
|
-
groundcrew
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
Resolution
|
|
222
|
-
[ok] Ticket exists in Linear ("Multi-event extractor: year inference can produce date_start > date_end")
|
|
223
|
-
[ok] Status is Todo
|
|
224
|
-
(skipped — post-dispatch — pre-dispatch checks are irrelevant)
|
|
225
|
-
|
|
226
|
-
Eligibility
|
|
227
|
-
(skipped — post-dispatch — pre-dispatch checks are irrelevant)
|
|
213
|
+
groundcrew status HRD-442
|
|
214
|
+
=========================
|
|
215
|
+
ticket: hrd-442
|
|
228
216
|
|
|
229
217
|
Run state
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
[ok] Recorded worktree (/Users/paul/dev/groundcrew-workspaces/herds-social/herds-hrd-442)
|
|
233
|
-
[ok] Recorded branch (paul-hrd-442)
|
|
234
|
-
[ok] Resume count (0)
|
|
218
|
+
---------
|
|
219
|
+
running; model=claude; updated=2026-05-26T00:01:00.000Z; resumes=0
|
|
235
220
|
|
|
236
221
|
Worktree
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
222
|
+
--------
|
|
223
|
+
- herds-social host
|
|
224
|
+
branch: paul-hrd-442
|
|
225
|
+
dir: /Users/paul/dev/groundcrew-workspaces/herds-social/herds-hrd-442
|
|
226
|
+
git: dirty (0 modified, 1 untracked)
|
|
240
227
|
|
|
241
228
|
Workspace
|
|
242
|
-
|
|
229
|
+
---------
|
|
230
|
+
live: yes
|
|
231
|
+
|
|
232
|
+
Last Linear status
|
|
233
|
+
------------------
|
|
234
|
+
In Progress (state.type=started) — Multi-event extractor: year inference can produce date_start > date_end
|
|
235
|
+
```
|
|
243
236
|
|
|
244
|
-
|
|
245
|
-
[ok] Local branch exists (paul-hrd-442, 2 ahead / 0 behind origin/main)
|
|
237
|
+
</details>
|
|
246
238
|
|
|
247
|
-
|
|
248
|
-
[ok] Branch present on origin
|
|
239
|
+
### `crew start <TICKET>`
|
|
249
240
|
|
|
250
|
-
|
|
251
|
-
[ok] Open PR for this branch (#224 https://github.com/herds-social/herds/pull/224)
|
|
241
|
+
Provisions and launches one ticket immediately, bypassing orchestrator eligibility. Use it to dispatch a specific ticket on demand — including unlabeled tickets that `crew run` ignores. (Replaces the deprecated `crew run --ticket <TICKET>`.)
|
|
252
242
|
|
|
253
|
-
|
|
243
|
+
```bash
|
|
244
|
+
crew start HRD-442
|
|
245
|
+
crew start HRD-442 --dry-run
|
|
254
246
|
```
|
|
255
247
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
### `crew interrupt <TICKET>`
|
|
248
|
+
### `crew stop <TICKET>`
|
|
259
249
|
|
|
260
|
-
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.
|
|
250
|
+
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. (Replaces the deprecated `crew interrupt <TICKET>`.)
|
|
261
251
|
|
|
262
252
|
```bash
|
|
263
|
-
crew
|
|
264
|
-
crew
|
|
253
|
+
crew stop HRD-442 --reason "wrong implementation direction"
|
|
254
|
+
crew status HRD-442
|
|
265
255
|
crew resume HRD-442
|
|
266
256
|
```
|
|
267
257
|
|
|
268
|
-
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,
|
|
258
|
+
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.
|
|
269
259
|
|
|
270
260
|
### `crew resume <TICKET>`
|
|
271
261
|
|
|
272
|
-
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
|
|
262
|
+
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>`.
|
|
273
263
|
|
|
274
264
|
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.
|
|
275
265
|
|
|
@@ -452,7 +442,7 @@ op run --env-file .env.1password -- crew doctor
|
|
|
452
442
|
|
|
453
443
|
## Troubleshooting
|
|
454
444
|
|
|
455
|
-
First stop for "
|
|
445
|
+
First stop for "what exists locally right now": `crew status <ticket>` shows the ticket's worktrees, workspace presence, run state, logs, and latest Linear status. Use `crew doctor` when you need to verify host setup.
|
|
456
446
|
|
|
457
447
|
<details>
|
|
458
448
|
<summary>Safehouse-already-wrapped commands are not re-wrapped</summary>
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAwQA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCvD"}
|
package/dist/cli.js
CHANGED
|
@@ -8,12 +8,20 @@ import { resumeWorkspaceCli } from "./commands/resumeWorkspace.js";
|
|
|
8
8
|
import { sandboxCli } from "./commands/sandbox/index.js";
|
|
9
9
|
import { setupReposCli } from "./commands/setupRepos.js";
|
|
10
10
|
import { setupWorkspaceCli } from "./commands/setupWorkspace.js";
|
|
11
|
+
import { statusCli } from "./commands/status.js";
|
|
11
12
|
import { createDefaultUpgradeCliOptions, upgradeCli } from "./commands/upgrade.js";
|
|
12
13
|
import { computeUpgradeNudge, defaultUpgradeCheckCachePath, fetchLatestVersion, } from "./lib/upgrade.js";
|
|
13
|
-
import { errorMessage, readEnvironmentVariable, readTicketArgument, writeError, writeOutput, } from "./lib/util.js";
|
|
14
|
+
import { errorMessage, parseDryRunPositionals, readEnvironmentVariable, readTicketArgument, writeError, writeOutput, } from "./lib/util.js";
|
|
14
15
|
const NUDGE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
15
16
|
const NUDGE_FETCH_TIMEOUT_MS = 1000;
|
|
16
17
|
const requireFromCli = createRequire(import.meta.url);
|
|
18
|
+
/**
|
|
19
|
+
* Prints a deprecation warning to stderr naming the canonical command and that
|
|
20
|
+
* the old form is removed in the next major, then lets the caller proceed.
|
|
21
|
+
*/
|
|
22
|
+
function warnDeprecated(forms) {
|
|
23
|
+
writeError(`crew ${forms.oldForm} is deprecated and will be removed in the next major version; use crew ${forms.newForm} instead.`);
|
|
24
|
+
}
|
|
17
25
|
function setupUsage() {
|
|
18
26
|
return "Usage: crew setup repos [--dry-run] [<repo>...]";
|
|
19
27
|
}
|
|
@@ -53,6 +61,16 @@ async function runCli(argv) {
|
|
|
53
61
|
await orchestrate({ watch, dryRun });
|
|
54
62
|
return;
|
|
55
63
|
}
|
|
64
|
+
warnDeprecated({ oldForm: "run --ticket", newForm: "start" });
|
|
65
|
+
await setupWorkspaceCli(ticket, { dryRun });
|
|
66
|
+
}
|
|
67
|
+
const START_USAGE = "crew start <ticket> [--dry-run]";
|
|
68
|
+
async function startCli(argv) {
|
|
69
|
+
const { dryRun, positionals } = parseDryRunPositionals(argv, START_USAGE);
|
|
70
|
+
const [ticket, ...extras] = positionals;
|
|
71
|
+
if (ticket === undefined || ticket.length === 0 || extras.length > 0) {
|
|
72
|
+
throw new Error(`Usage: ${START_USAGE}`);
|
|
73
|
+
}
|
|
56
74
|
await setupWorkspaceCli(ticket, { dryRun });
|
|
57
75
|
}
|
|
58
76
|
async function upgradeCliInvoke(argv) {
|
|
@@ -79,31 +97,27 @@ async function maybeRunUpgradeNudge(metadata) {
|
|
|
79
97
|
writeError(message);
|
|
80
98
|
}
|
|
81
99
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
86
|
-
const argument = argv[index];
|
|
87
|
-
if (argument === "--ticket") {
|
|
88
|
-
ticket = readTicketArgument(argv, index, "doctor");
|
|
89
|
-
index += 1;
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (argument === "--no-linear" || argument === "--no-fetch") {
|
|
93
|
-
remainingArgs.push(argument);
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
throw new Error(`crew doctor: unknown argument: ${argument}`);
|
|
100
|
+
function doctorTicketAlias(argv) {
|
|
101
|
+
if (argv[0] !== "--ticket") {
|
|
102
|
+
return undefined;
|
|
97
103
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
+
const ticket = readTicketArgument(argv, 0, "doctor");
|
|
105
|
+
if (argv.length > 2) {
|
|
106
|
+
throw new Error("Usage: crew status [<ticket>]");
|
|
107
|
+
}
|
|
108
|
+
return ticket;
|
|
109
|
+
}
|
|
110
|
+
async function doctorCli(argv) {
|
|
111
|
+
const aliasTicket = doctorTicketAlias(argv);
|
|
112
|
+
if (aliasTicket !== undefined) {
|
|
113
|
+
warnDeprecated({ oldForm: "doctor --ticket", newForm: "status" });
|
|
114
|
+
await statusCli([aliasTicket]);
|
|
104
115
|
return;
|
|
105
116
|
}
|
|
106
|
-
|
|
117
|
+
if (argv.length > 0) {
|
|
118
|
+
throw new Error("Usage: crew doctor");
|
|
119
|
+
}
|
|
120
|
+
const ok = await doctor();
|
|
107
121
|
process.exitCode = ok ? process.exitCode : 1;
|
|
108
122
|
}
|
|
109
123
|
const SUBCOMMANDS = {
|
|
@@ -113,25 +127,44 @@ const SUBCOMMANDS = {
|
|
|
113
127
|
invoke: initConfigCli,
|
|
114
128
|
},
|
|
115
129
|
run: {
|
|
116
|
-
summary: "Run the orchestrator (one-shot by default)
|
|
117
|
-
usage: "[--watch] [--dry-run]
|
|
130
|
+
summary: "Run the orchestrator: poll sources and start eligible tickets (one-shot by default)",
|
|
131
|
+
usage: "[--watch] [--dry-run]",
|
|
118
132
|
invoke: runCli,
|
|
119
133
|
},
|
|
134
|
+
start: {
|
|
135
|
+
summary: "Provision and launch one ticket immediately, bypassing eligibility",
|
|
136
|
+
usage: "<ticket> [--dry-run]",
|
|
137
|
+
invoke: startCli,
|
|
138
|
+
},
|
|
120
139
|
doctor: {
|
|
121
|
-
summary: "Verify
|
|
122
|
-
usage: "
|
|
140
|
+
summary: "Verify host prerequisites (PATH tools, config validity, Linear reachability)",
|
|
141
|
+
usage: "",
|
|
123
142
|
invoke: doctorCli,
|
|
124
143
|
},
|
|
144
|
+
status: {
|
|
145
|
+
summary: "Print read-only groundcrew state, or one ticket's local/Linear status",
|
|
146
|
+
usage: "[<ticket>]",
|
|
147
|
+
invoke: statusCli,
|
|
148
|
+
},
|
|
125
149
|
cleanup: {
|
|
126
150
|
summary: "Tear down a worktree",
|
|
127
151
|
usage: "[--force] <ticket>",
|
|
128
152
|
invoke: cleanupWorkspaceCli,
|
|
129
153
|
},
|
|
130
|
-
|
|
154
|
+
stop: {
|
|
131
155
|
summary: "Stop a live ticket workspace while preserving its worktree",
|
|
132
156
|
usage: "<ticket> [--reason <text>]",
|
|
133
157
|
invoke: interruptWorkspaceCli,
|
|
134
158
|
},
|
|
159
|
+
interrupt: {
|
|
160
|
+
summary: "Deprecated alias for `crew stop`",
|
|
161
|
+
usage: "<ticket> [--reason <text>]",
|
|
162
|
+
deprecated: true,
|
|
163
|
+
invoke: async (argv) => {
|
|
164
|
+
warnDeprecated({ oldForm: "interrupt", newForm: "stop" });
|
|
165
|
+
await interruptWorkspaceCli(argv);
|
|
166
|
+
},
|
|
167
|
+
},
|
|
135
168
|
resume: {
|
|
136
169
|
summary: "Reopen an existing ticket worktree with a continuation prompt",
|
|
137
170
|
usage: "<ticket>",
|
|
@@ -162,6 +195,9 @@ function printHelp() {
|
|
|
162
195
|
writeOutput("");
|
|
163
196
|
writeOutput("Commands:");
|
|
164
197
|
for (const [name, command] of Object.entries(SUBCOMMANDS)) {
|
|
198
|
+
if (command.deprecated === true) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
165
201
|
writeOutput(` ${name.padEnd(width)} ${command.summary}`);
|
|
166
202
|
writeOutput(` ${" ".repeat(width)} → crew ${name} ${command.usage}`);
|
|
167
203
|
}
|
|
@@ -2,10 +2,5 @@
|
|
|
2
2
|
* doctor — verify groundcrew prerequisites against the resolved config.
|
|
3
3
|
* Returns true if every required check passes; false otherwise.
|
|
4
4
|
*/
|
|
5
|
-
export
|
|
6
|
-
ticket?: string;
|
|
7
|
-
/** Extra flags after `--ticket <id>`; currently `--no-linear` and `--no-fetch`. */
|
|
8
|
-
ticketArgv?: string[];
|
|
9
|
-
}
|
|
10
|
-
export declare function doctor(options?: DoctorOptions): Promise<boolean>;
|
|
5
|
+
export declare function doctor(): Promise<boolean>;
|
|
11
6
|
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAoJH,wBAAsB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CA8E/C"}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { existsSync, statSync } from "node:fs";
|
|
6
6
|
import { loadConfig, } from "../lib/config.js";
|
|
7
|
+
import { createBoardSource } from "../lib/boardSource.js";
|
|
7
8
|
import { detectHostCapabilities, which } from "../lib/host.js";
|
|
8
9
|
import { resolveLocalRunner } from "../lib/localRunner.js";
|
|
9
10
|
import { gatedModels } from "../lib/usage.js";
|
|
10
|
-
import { errorMessage, resolveLinearApiKey, writeOutput } from "../lib/util.js";
|
|
11
|
+
import { errorMessage, getLinearClient, resolveLinearApiKey, writeOutput } from "../lib/util.js";
|
|
11
12
|
import { resolveWorkspaceKind } from "../lib/workspaces.js";
|
|
12
|
-
import { parseTicketDoctorFlags, runTicketDoctor } from "./ticketDoctor.js";
|
|
13
13
|
// Tokenization stops after this many non-flag tokens. Two is enough to
|
|
14
14
|
// catch wrapper + wrapped CLI commands like `safehouse claude --foo`.
|
|
15
15
|
const MAX_TOKENS_PER_CMD = 2;
|
|
@@ -26,22 +26,33 @@ async function checkCmd(cmd, required, hint) {
|
|
|
26
26
|
}
|
|
27
27
|
return result;
|
|
28
28
|
}
|
|
29
|
-
function
|
|
29
|
+
async function checkLinearReachability(config) {
|
|
30
30
|
const resolved = resolveLinearApiKey();
|
|
31
|
-
if (resolved
|
|
31
|
+
if (resolved === undefined) {
|
|
32
32
|
return {
|
|
33
|
-
name: "linear
|
|
33
|
+
name: "linear reachability",
|
|
34
|
+
ok: false,
|
|
35
|
+
required: true,
|
|
36
|
+
hint: "export $GROUNDCREW_LINEAR_API_KEY or $LINEAR_API_KEY",
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
await createBoardSource({ config, client: getLinearClient() }).verify();
|
|
41
|
+
return {
|
|
42
|
+
name: "linear reachability",
|
|
34
43
|
ok: true,
|
|
35
44
|
required: true,
|
|
36
45
|
hint: `set via $${resolved.source}`,
|
|
37
46
|
};
|
|
38
47
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
catch (error) {
|
|
49
|
+
return {
|
|
50
|
+
name: "linear reachability",
|
|
51
|
+
ok: false,
|
|
52
|
+
required: true,
|
|
53
|
+
hint: errorMessage(error),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
45
56
|
}
|
|
46
57
|
function checkDir(path, label) {
|
|
47
58
|
// statSync can throw on permission errors or path races; surface those
|
|
@@ -120,31 +131,7 @@ function format(check) {
|
|
|
120
131
|
const hint = check.hint !== undefined && check.hint.length > 0 ? ` — ${check.hint}` : "";
|
|
121
132
|
return `${tag}${check.name}${hint}`;
|
|
122
133
|
}
|
|
123
|
-
export async function doctor(
|
|
124
|
-
if (options.ticket !== undefined) {
|
|
125
|
-
return await doctorTicket(options.ticket, options.ticketArgv ?? []);
|
|
126
|
-
}
|
|
127
|
-
return await doctorHost();
|
|
128
|
-
}
|
|
129
|
-
async function doctorTicket(ticket, ticketArgv) {
|
|
130
|
-
try {
|
|
131
|
-
const flags = parseTicketDoctorFlags(ticketArgv);
|
|
132
|
-
return await runTicketDoctor({
|
|
133
|
-
ticket,
|
|
134
|
-
doLinear: flags.doLinear,
|
|
135
|
-
doFetch: flags.doFetch,
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
catch (error) {
|
|
139
|
-
const displayTicket = ticket.toUpperCase();
|
|
140
|
-
const header = `groundcrew doctor --ticket ${displayTicket}`;
|
|
141
|
-
writeOutput(header);
|
|
142
|
-
writeOutput("=".repeat(header.length));
|
|
143
|
-
writeOutput(`[--] config: ${errorMessage(error)}`);
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
async function doctorHost() {
|
|
134
|
+
export async function doctor() {
|
|
148
135
|
writeOutput("groundcrew doctor");
|
|
149
136
|
writeOutput("=================");
|
|
150
137
|
let config;
|
|
@@ -174,7 +161,7 @@ async function doctorHost() {
|
|
|
174
161
|
const workspaceOutcome = resolveWorkspaceOutcome(config, host);
|
|
175
162
|
reportWorkspaceKind(config, workspaceOutcome);
|
|
176
163
|
const checks = [
|
|
177
|
-
|
|
164
|
+
await checkLinearReachability(config),
|
|
178
165
|
await checkCmd("git", true, "https://git-scm.com/"),
|
|
179
166
|
...(await workspaceChecks(workspaceOutcome)),
|
|
180
167
|
checkDir(config.workspace.projectDir, "workspace.projectDir"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interruptWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/interruptWorkspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAMnE,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"interruptWorkspace.d.ts","sourceRoot":"","sources":["../../src/commands/interruptWorkspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAMnE,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAqGD,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAGzE"}
|
|
@@ -15,20 +15,20 @@ function parseArguments(argv) {
|
|
|
15
15
|
if (argument === "--reason") {
|
|
16
16
|
const value = argv[index + 1];
|
|
17
17
|
if (value === undefined || value.length === 0 || value.startsWith("-")) {
|
|
18
|
-
throw new Error("crew
|
|
18
|
+
throw new Error("crew stop --reason: reason text is required");
|
|
19
19
|
}
|
|
20
20
|
reason = value;
|
|
21
21
|
index += 1;
|
|
22
22
|
continue;
|
|
23
23
|
}
|
|
24
24
|
if (argument.startsWith("-")) {
|
|
25
|
-
throw new Error(`Unknown option: ${argument}\nUsage: crew
|
|
25
|
+
throw new Error(`Unknown option: ${argument}\nUsage: crew stop <ticket> [--reason <text>]`);
|
|
26
26
|
}
|
|
27
27
|
positionals.push(argument);
|
|
28
28
|
}
|
|
29
29
|
const [ticket, ...extras] = positionals;
|
|
30
30
|
if (ticket === undefined || ticket.length === 0 || extras.length > 0) {
|
|
31
|
-
throw new Error("Usage: crew
|
|
31
|
+
throw new Error("Usage: crew stop <ticket> [--reason <text>]");
|
|
32
32
|
}
|
|
33
33
|
return { ticket: ticket.toLowerCase(), ...(reason === undefined ? {} : { reason }) };
|
|
34
34
|
}
|
|
@@ -100,7 +100,7 @@ export async function interruptWorkspace(config, options) {
|
|
|
100
100
|
},
|
|
101
101
|
});
|
|
102
102
|
log(`Interrupted ${ticket}; worktree preserved at ${source.worktreeDir}`);
|
|
103
|
-
log(`Next: crew
|
|
103
|
+
log(`Next: crew status ${ticket}`);
|
|
104
104
|
}
|
|
105
105
|
export async function interruptWorkspaceCli(argv) {
|
|
106
106
|
const config = await loadConfig();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setupRepos.d.ts","sourceRoot":"","sources":["../../src/commands/setupRepos.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAInE,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,oBAAoB,GAAG,gBAAgB,CAAC;AAEvF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,oDAAoD;IACpD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,wEAAwE;IACxE,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,wCAAwC;IACxC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,EAAE,CAAC;IACzC,mEAAmE;IACnE,SAAS,EAAE,OAAO,CAAC;CACpB;AA6JD,wBAAsB,UAAU,CAC9B,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CA0D3B;
|
|
1
|
+
{"version":3,"file":"setupRepos.d.ts","sourceRoot":"","sources":["../../src/commands/setupRepos.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAInE,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,oBAAoB,GAAG,gBAAgB,CAAC;AAEvF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,oDAAoD;IACpD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,wEAAwE;IACxE,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,wCAAwC;IACxC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,EAAE,CAAC;IACzC,mEAAmE;IACnE,SAAS,EAAE,OAAO,CAAC;CACpB;AA6JD,wBAAsB,UAAU,CAC9B,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CA0D3B;AAcD,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAajE"}
|
|
@@ -10,7 +10,7 @@ import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
|
10
10
|
import { runCommandAsync } from "../lib/commandRunner.js";
|
|
11
11
|
import { loadConfig } from "../lib/config.js";
|
|
12
12
|
import { which } from "../lib/host.js";
|
|
13
|
-
import { errorMessage, log, writeOutput } from "../lib/util.js";
|
|
13
|
+
import { errorMessage, log, parseDryRunPositionals, writeOutput } from "../lib/util.js";
|
|
14
14
|
function emptyResult() {
|
|
15
15
|
return {
|
|
16
16
|
existing: [],
|
|
@@ -190,18 +190,7 @@ export async function setupRepos(config, options) {
|
|
|
190
190
|
return result;
|
|
191
191
|
}
|
|
192
192
|
function parseArguments(argv) {
|
|
193
|
-
|
|
194
|
-
const positionals = [];
|
|
195
|
-
for (const argument of argv) {
|
|
196
|
-
if (argument === "--dry-run") {
|
|
197
|
-
dryRun = true;
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
if (argument.startsWith("-")) {
|
|
201
|
-
throw new Error(`Unknown option: ${argument}\nUsage: crew setup repos [--dry-run] [<repo>...]`);
|
|
202
|
-
}
|
|
203
|
-
positionals.push(argument);
|
|
204
|
-
}
|
|
193
|
+
const { dryRun, positionals } = parseDryRunPositionals(argv, "crew setup repos [--dry-run] [<repo>...]");
|
|
205
194
|
const options = { dryRun };
|
|
206
195
|
if (positionals.length > 0) {
|
|
207
196
|
options.only = positionals;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ResolvedConfig } from "../lib/config.ts";
|
|
2
|
+
export interface StatusOptions {
|
|
3
|
+
ticket?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function status(config: ResolvedConfig, options?: StatusOptions): Promise<void>;
|
|
6
|
+
export declare function statusCli(argv: string[]): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAWnE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAuLD,wBAAsB,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU/F;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
|