capyai 0.4.0 → 0.5.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/AGENTS.md +9 -121
- package/README.md +3 -1
- package/bin/capy.ts +2 -0
- package/package.json +1 -1
- package/skills/capy/SKILL.md +235 -64
- package/src/api.ts +45 -18
- package/src/commands/_shared.ts +4 -0
- package/src/commands/diff-pr.ts +6 -1
- package/src/commands/monitoring.ts +16 -10
- package/src/commands/quality.ts +5 -6
- package/src/commands/setup.ts +46 -0
- package/src/commands/tasks.ts +4 -2
- package/src/commands/threads.ts +7 -3
- package/src/commands/triage.ts +271 -0
- package/src/config.ts +1 -3
- package/src/github.ts +1 -1
- package/src/mcp.ts +145 -22
- package/src/types.ts +0 -2
- package/src/watch.ts +4 -4
package/AGENTS.md
CHANGED
|
@@ -81,7 +81,7 @@ Config file locations:
|
|
|
81
81
|
- Claude Desktop: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
|
|
82
82
|
- Cursor: `.cursor/mcp.json`
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
25 MCP tools with full API parity:
|
|
85
85
|
|
|
86
86
|
| Tool | What it does | Annotations |
|
|
87
87
|
|------|-------------|-------------|
|
|
@@ -104,8 +104,12 @@ Config file locations:
|
|
|
104
104
|
| `capy_models` | List models | readOnly, idempotent |
|
|
105
105
|
| `capy_pool_status` | Warm pool config + VM status | readOnly, idempotent |
|
|
106
106
|
| `capy_pool_update` | Update warm pool config | openWorld |
|
|
107
|
+
| `capy_pool_test` | Test VM boot with setup commands | openWorld |
|
|
107
108
|
| `capy_pool_instances` | List warm pool VMs | readOnly, idempotent |
|
|
108
109
|
| `capy_pool_clear` | Clear/refresh warm pool | destructive |
|
|
110
|
+
| `capy_projects` | List all projects | readOnly, idempotent |
|
|
111
|
+
| `capy_project` | Get project details (repos, code, config) | readOnly, idempotent |
|
|
112
|
+
| `capy_triage` | Actionable triage with categories + recs (brief mode available) | readOnly |
|
|
109
113
|
|
|
110
114
|
Tools with predictable outputs (`capy_captain`, `capy_build`, `capy_review`, `capy_approve`, `capy_retry`) declare `outputSchema` for typed structured content per the 2025-03-26 MCP spec.
|
|
111
115
|
|
|
@@ -123,128 +127,12 @@ capy models --json
|
|
|
123
127
|
|
|
124
128
|
You are now fully configured.
|
|
125
129
|
|
|
126
|
-
|
|
130
|
+
## Usage
|
|
127
131
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
### Delegate work
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
capy captain "Implement feature X. Files: src/foo.ts. Tests required." --json
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Returns `{ "threadId": "...", "url": "..." }`. Save the `threadId`.
|
|
137
|
-
|
|
138
|
-
### Wait for completion
|
|
132
|
+
For the full operating manual (commands, workflows, decision trees, guardrails), install the capy skill:
|
|
139
133
|
|
|
140
134
|
```bash
|
|
141
|
-
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
Blocks until the thread reaches a terminal state. Returns the full thread object with `tasks` array. Each task has an `identifier` (like `SCO-1`).
|
|
145
|
-
|
|
146
|
-
### Review quality
|
|
147
|
-
|
|
148
|
-
```bash
|
|
149
|
-
capy review <taskId> --json
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
Returns `{ "task": "SCO-1", "quality": { "pass": true, "passed": 5, "total": 5, "gates": [...] } }`.
|
|
153
|
-
|
|
154
|
-
Read `quality.pass`. If `true`, approve. If `false`, read `quality.gates` for what failed.
|
|
155
|
-
|
|
156
|
-
### Approve or retry
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
# If quality.pass is true:
|
|
160
|
-
capy approve <taskId> --json
|
|
161
|
-
|
|
162
|
-
# If quality.pass is false:
|
|
163
|
-
capy retry <taskId> --fix="describe what to fix" --json
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
`retry` returns `{ "newThread": "..." }`. Wait on that new thread ID, then review again.
|
|
167
|
-
|
|
168
|
-
### The full loop
|
|
169
|
-
|
|
170
|
-
```bash
|
|
171
|
-
THREAD=$(capy captain "your prompt" --json | jq -r '.threadId')
|
|
172
|
-
capy wait "$THREAD" --timeout=600 --json
|
|
173
|
-
|
|
174
|
-
TASK=$(capy threads get "$THREAD" --json | jq -r '.tasks[0].identifier')
|
|
175
|
-
QUALITY=$(capy review "$TASK" --json)
|
|
176
|
-
PASS=$(echo "$QUALITY" | jq -r '.quality.pass')
|
|
177
|
-
|
|
178
|
-
while [ "$PASS" != "true" ]; do
|
|
179
|
-
GATES=$(echo "$QUALITY" | jq -r '.quality.gates[] | select(.pass == false) | .name + ": " + .detail')
|
|
180
|
-
NEW=$(capy retry "$TASK" --fix="Fix these failures: $GATES" --json | jq -r '.newThread')
|
|
181
|
-
capy wait "$NEW" --timeout=600 --json
|
|
182
|
-
TASK=$(capy threads get "$NEW" --json | jq -r '.tasks[0].identifier')
|
|
183
|
-
QUALITY=$(capy review "$TASK" --json)
|
|
184
|
-
PASS=$(echo "$QUALITY" | jq -r '.quality.pass')
|
|
185
|
-
done
|
|
186
|
-
|
|
187
|
-
capy approve "$TASK" --json
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### Background monitoring
|
|
191
|
-
|
|
192
|
-
For async fire-and-forget work. Sets a cron job that polls and runs your notification command when done.
|
|
193
|
-
|
|
194
|
-
```bash
|
|
195
|
-
capy watch <threadId>
|
|
196
|
-
capy config notifyCommand "<your notification command> {text}"
|
|
135
|
+
npx skills add yazcaleb/capy-cli
|
|
197
136
|
```
|
|
198
137
|
|
|
199
|
-
`
|
|
200
|
-
- `openclaw system event --text {text} --mode now`
|
|
201
|
-
- `echo {text} >> ~/capy-notifications.log`
|
|
202
|
-
|
|
203
|
-
List watches: `capy watches --json`. Remove: `capy unwatch <id>`.
|
|
204
|
-
|
|
205
|
-
### All commands
|
|
206
|
-
|
|
207
|
-
Every command supports `--json` for structured output. Errors always return `{ "error": { "code": "...", "message": "..." } }`.
|
|
208
|
-
|
|
209
|
-
| Command | What it does |
|
|
210
|
-
|---------|-------------|
|
|
211
|
-
| `capy captain "<prompt>"` | Start Captain thread |
|
|
212
|
-
| `capy build "<prompt>"` | Start Build agent (small isolated tasks) |
|
|
213
|
-
| `capy start <id>` | Start/resume a backlog task |
|
|
214
|
-
| `capy stop <id> [reason]` | Stop a running task |
|
|
215
|
-
| `capy msg <id> "<text>"` | Message a running task |
|
|
216
|
-
| `capy wait <id> --timeout=N` | Block until terminal state |
|
|
217
|
-
| `capy review <id>` | Run quality gates (pass/fail) |
|
|
218
|
-
| `capy re-review <id>` | Trigger fresh Greptile review |
|
|
219
|
-
| `capy approve <id>` | Approve if gates pass |
|
|
220
|
-
| `capy retry <id> --fix="..."` | Retry with context from failure |
|
|
221
|
-
| `capy status` | Dashboard (all threads + tasks) |
|
|
222
|
-
| `capy list [status]` | List tasks (filter: in_progress, needs_review, backlog, archived) |
|
|
223
|
-
| `capy get <id>` | Task details (jams, PR state, credits) |
|
|
224
|
-
| `capy diff <id>` | View diff (file-by-file with patches) |
|
|
225
|
-
| `capy pr <id> [title]` | Create PR for a task |
|
|
226
|
-
| `capy watch <id>` | Cron poll + notify on completion |
|
|
227
|
-
| `capy unwatch <id>` | Stop watching |
|
|
228
|
-
| `capy watches` | List active watches |
|
|
229
|
-
| `capy threads list` | List Captain threads |
|
|
230
|
-
| `capy threads get <id>` | Thread details (tasks, PRs) |
|
|
231
|
-
| `capy threads msg <id> "<text>"` | Message a thread |
|
|
232
|
-
| `capy threads stop <id>` | Stop a thread |
|
|
233
|
-
| `capy threads messages <id>` | Read thread conversation history |
|
|
234
|
-
| `capy pool` | Warm pool status |
|
|
235
|
-
| `capy pool set --size=N --age=M` | Update warm pool config |
|
|
236
|
-
| `capy pool test` | Test VM boot |
|
|
237
|
-
| `capy pool instances [status]` | List pool VMs |
|
|
238
|
-
| `capy pool instance <id>` | VM detail + logs |
|
|
239
|
-
| `capy pool clear [--replenish]` | Clear/refresh pool |
|
|
240
|
-
| `capy config [key] [value]` | Get/set config |
|
|
241
|
-
| `capy models` | List available models |
|
|
242
|
-
| `capy tools` | Show all commands + env vars |
|
|
243
|
-
|
|
244
|
-
### Prompting tips
|
|
245
|
-
|
|
246
|
-
Bad: `"Fix the CI issue"`
|
|
247
|
-
|
|
248
|
-
Good: `"Fix CI for crypto-trading pack. The changeset file is missing. Add a changeset entry for @veto/crypto-trading. Run changeset validation. Reference: PLW-201."`
|
|
249
|
-
|
|
250
|
-
Always include: specific files, specific functions, acceptance criteria, references to related tasks/issues.
|
|
138
|
+
This loads `skills/capy/SKILL.md` into your context with everything you need to orchestrate Capy agents.
|
package/README.md
CHANGED
|
@@ -54,6 +54,8 @@ Every command supports `--json` for machine-readable output.
|
|
|
54
54
|
| `capy pr <id>` | Create PR for task |
|
|
55
55
|
| `capy watch <id>` | Cron poll + notify on completion |
|
|
56
56
|
| `capy threads [list\|get\|msg\|stop\|messages]` | Manage Captain threads |
|
|
57
|
+
| `capy projects [list\|get]` | List/get projects |
|
|
58
|
+
| `capy triage [ids] [--brief]` | Actionable triage with categories + recommendations |
|
|
57
59
|
| `capy pool [status\|set\|test\|instances\|clear]` | Manage warm pool VMs |
|
|
58
60
|
| `capy models` | List available models |
|
|
59
61
|
| `capy config [key] [value]` | Get/set config |
|
|
@@ -88,7 +90,7 @@ For agents that prefer MCP over CLI:
|
|
|
88
90
|
}
|
|
89
91
|
```
|
|
90
92
|
|
|
91
|
-
|
|
93
|
+
25 tools with full API parity, including projects, triage, and warm pool management.
|
|
92
94
|
|
|
93
95
|
## Config
|
|
94
96
|
|
package/bin/capy.ts
CHANGED
|
@@ -24,9 +24,11 @@ const main = defineCommand({
|
|
|
24
24
|
msg: () => import("../src/commands/tasks.js").then(m => m.msg),
|
|
25
25
|
diff: () => import("../src/commands/diff-pr.js").then(m => m.diff),
|
|
26
26
|
pr: () => import("../src/commands/diff-pr.js").then(m => m.pr),
|
|
27
|
+
projects: () => import("../src/commands/setup.js").then(m => m.projects),
|
|
27
28
|
models: () => import("../src/commands/setup.js").then(m => m.models),
|
|
28
29
|
tools: () => import("../src/commands/setup.js").then(m => m.tools),
|
|
29
30
|
status: () => import("../src/commands/setup.js").then(m => m.status),
|
|
31
|
+
triage: () => import("../src/commands/triage.js").then(m => m.triage),
|
|
30
32
|
review: () => import("../src/commands/quality.js").then(m => m.review),
|
|
31
33
|
"re-review": () => import("../src/commands/quality.js").then(m => m.reReview),
|
|
32
34
|
approve: () => import("../src/commands/quality.js").then(m => m.approve),
|
package/package.json
CHANGED
package/skills/capy/SKILL.md
CHANGED
|
@@ -3,101 +3,272 @@ name: capy
|
|
|
3
3
|
description: Orchestrate Capy.ai coding agents with quality gates. Delegate coding work, wait for completion, review quality, approve or retry.
|
|
4
4
|
metadata:
|
|
5
5
|
author: yazcaleb
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "0.5.0"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# capy
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
You orchestrate Capy.ai coding agents. You start them, wait for them, gate their output on quality, then approve or retry. This skill makes you a 10x Capy orchestrator.
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## When to use this skill
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
Use capy when the user wants to:
|
|
16
|
+
- Delegate coding work to an AI agent (not do it locally)
|
|
17
|
+
- Check on tasks running on Capy (status, triage, review)
|
|
18
|
+
- Approve, retry, or stop Capy tasks
|
|
19
|
+
- Create PRs for completed Capy work
|
|
20
|
+
- Manage Capy projects, warm pool VMs, or configuration
|
|
21
|
+
|
|
22
|
+
Do NOT use capy when:
|
|
23
|
+
- The user wants you to write code yourself (just write it)
|
|
24
|
+
- The user is talking about a different CI/CD system
|
|
25
|
+
- The task is too small to delegate (a one-line fix, a config change)
|
|
26
|
+
|
|
27
|
+
## Core objects
|
|
28
|
+
|
|
29
|
+
Understand these three objects before doing anything.
|
|
30
|
+
|
|
31
|
+
**Thread** — A Captain session. You give it a prompt, it plans and may spawn multiple tasks. Threads have long opaque IDs (UUIDs). Terminal states: `idle`, `archived`, `completed`. You start threads with `capy captain`.
|
|
32
|
+
|
|
33
|
+
**Task** — A single unit of coding work. Has a short identifier like `SCO-15`. A task produces a diff (code changes) and optionally a pull request. Terminal states: `needs_review`, `archived`, `completed`, `failed`. You start standalone tasks with `capy build`.
|
|
34
|
+
|
|
35
|
+
**Jam** — An execution run inside a task. Each jam has a model, a status, and credit usage. A task can have multiple jams (one per retry). When `jam.status` is `"idle"` and credits are zero, that jam is finished. The task is done working. You cannot send it more messages.
|
|
36
|
+
|
|
37
|
+
**Lifecycle:**
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
[you] captain/build
|
|
41
|
+
→ [capy agent] in_progress (writing code, running tests)
|
|
42
|
+
→ [capy agent] needs_review (agent stopped, diff may exist)
|
|
43
|
+
→ [you] check diff, create PR, run quality gates
|
|
44
|
+
→ [you] approve OR retry
|
|
18
45
|
```
|
|
19
46
|
|
|
20
|
-
|
|
47
|
+
`needs_review` means the agent finished working. It does NOT mean the code is ready to merge. You must still check the diff, create a PR, review quality gates, and approve.
|
|
48
|
+
|
|
49
|
+
## Decision tree
|
|
50
|
+
|
|
51
|
+
When you encounter a task, look at its status and act accordingly. Do exactly one thing.
|
|
52
|
+
|
|
53
|
+
**If status is `in_progress`:**
|
|
54
|
+
→ Wait for it. `capy wait <id> --timeout=600 --json`
|
|
55
|
+
|
|
56
|
+
**If status is `needs_review`:**
|
|
57
|
+
1. Check if it has a diff: `capy get <id> --json` and look at `pullRequest` field
|
|
58
|
+
2. If no diff was produced (no `pullRequest`, and `capy diff <id> --json` returns `stats.files: 0`):
|
|
59
|
+
→ Task is stuck. Retry with instructions: `capy retry <id> --fix="describe what went wrong" --json`
|
|
60
|
+
3. If diff exists but no PR:
|
|
61
|
+
→ Create a PR first: `capy pr <id> --json`
|
|
62
|
+
→ Then review: `capy review <id> --json`
|
|
63
|
+
4. If diff exists and PR exists:
|
|
64
|
+
→ Review: `capy review <id> --json`
|
|
65
|
+
→ If `quality.pass` is true → `capy approve <id> --json`
|
|
66
|
+
→ If `quality.pass` is false → check which gates failed (see Quality Gates section)
|
|
67
|
+
|
|
68
|
+
**If status is `backlog`:**
|
|
69
|
+
→ Start it. `capy start <id> --json`
|
|
70
|
+
|
|
71
|
+
**If status is `failed`:**
|
|
72
|
+
→ Retry. `capy retry <id> --fix="..." --json`
|
|
73
|
+
|
|
74
|
+
**If status is `archived`:**
|
|
75
|
+
→ Ignore. This task is dead.
|
|
76
|
+
|
|
77
|
+
**If PR state is `merged`:**
|
|
78
|
+
→ Done. Nothing to do.
|
|
79
|
+
|
|
80
|
+
## Guardrails
|
|
81
|
+
|
|
82
|
+
These are the mistakes agents make. Do not make them.
|
|
83
|
+
|
|
84
|
+
1. **Never message a task with idle jams.** If the last jam has `status: "idle"` and zero credits, the task is finished. It cannot receive messages. Sending `capy msg` will appear to succeed but nothing happens. If you need to change something, use `capy retry` to start a new attempt.
|
|
85
|
+
|
|
86
|
+
2. **Never create a new Captain thread for work that already has a diff.** If a task produced code changes, that work exists. Create a PR for the existing task with `capy pr <id>`. Starting a new Captain thread throws away the existing work and burns credits.
|
|
87
|
+
|
|
88
|
+
3. **Never call `capy review` on a task with no PR.** It will fail with `error.code: "no_pr"`. Always create the PR first with `capy pr <id> --json`, then review.
|
|
89
|
+
|
|
90
|
+
4. **Never retry infinitely.** Cap retries at 3 attempts. After 3 failures, stop the task with `capy stop <id>` and tell the user. Each retry costs LLM and VM credits.
|
|
91
|
+
|
|
92
|
+
5. **If Greptile says "Review still processing", wait and re-check.** Do NOT retry the task. The code is fine, the review just hasn't finished. Wait 60 seconds, then run `capy review <id> --json` again.
|
|
93
|
+
|
|
94
|
+
6. **Captain threads can spawn multiple tasks.** After `capy wait` on a thread, check ALL tasks in the response, not just `tasks[0]`. Review and approve each one.
|
|
95
|
+
|
|
96
|
+
7. **The Capy API reports merged PRs as "closed".** The CLI cross-references with GitHub to show the real state. Trust the CLI output.
|
|
97
|
+
|
|
98
|
+
## Workflow: Start new work
|
|
99
|
+
|
|
21
100
|
```bash
|
|
22
|
-
|
|
23
|
-
|
|
101
|
+
# 1. Start a Captain thread
|
|
102
|
+
RESULT=$(capy captain "Implement feature X. Files: src/foo.ts, src/bar.ts. Include tests. Run tests before finishing." --json)
|
|
103
|
+
THREAD_ID=$(echo "$RESULT" | jq -r '.id')
|
|
104
|
+
|
|
105
|
+
# 2. Wait for completion (use 600s for Captain, 300s for Build)
|
|
106
|
+
WAIT_RESULT=$(capy wait "$THREAD_ID" --timeout=600 --json)
|
|
107
|
+
|
|
108
|
+
# 3. Check if wait timed out
|
|
109
|
+
if echo "$WAIT_RESULT" | jq -e '.error' > /dev/null 2>&1; then
|
|
110
|
+
echo "Timed out. Last status: $(echo "$WAIT_RESULT" | jq -r '.error.lastStatus')"
|
|
111
|
+
# Decide: wait longer, or stop the thread
|
|
112
|
+
exit 1
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# 4. Get task identifiers from the thread
|
|
116
|
+
TASKS=$(echo "$WAIT_RESULT" | jq -r '.tasks[].identifier')
|
|
117
|
+
|
|
118
|
+
# 5. For each task: create PR if needed, review, approve/retry
|
|
119
|
+
for TASK in $TASKS; do
|
|
120
|
+
# Check if PR exists
|
|
121
|
+
HAS_PR=$(capy get "$TASK" --json | jq -r '.pullRequest.number // empty')
|
|
122
|
+
if [ -z "$HAS_PR" ]; then
|
|
123
|
+
capy pr "$TASK" --json
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# Review quality
|
|
127
|
+
QUALITY=$(capy review "$TASK" --json)
|
|
128
|
+
PASS=$(echo "$QUALITY" | jq -r '.quality.pass')
|
|
129
|
+
|
|
130
|
+
# Retry loop (max 3)
|
|
131
|
+
ATTEMPTS=0
|
|
132
|
+
while [ "$PASS" != "true" ] && [ "$ATTEMPTS" -lt 3 ]; do
|
|
133
|
+
FAILING=$(echo "$QUALITY" | jq -r '.quality.gates[] | select(.pass == false) | .name + ": " + .detail')
|
|
134
|
+
RETRY=$(capy retry "$TASK" --fix="Fix these failures: $FAILING" --json)
|
|
135
|
+
NEW_THREAD=$(echo "$RETRY" | jq -r '.newThread')
|
|
136
|
+
|
|
137
|
+
capy wait "$NEW_THREAD" --timeout=600 --json
|
|
138
|
+
TASK=$(capy threads get "$NEW_THREAD" --json | jq -r '.tasks[0].identifier')
|
|
139
|
+
|
|
140
|
+
HAS_PR=$(capy get "$TASK" --json | jq -r '.pullRequest.number // empty')
|
|
141
|
+
[ -z "$HAS_PR" ] && capy pr "$TASK" --json
|
|
142
|
+
|
|
143
|
+
QUALITY=$(capy review "$TASK" --json)
|
|
144
|
+
PASS=$(echo "$QUALITY" | jq -r '.quality.pass')
|
|
145
|
+
ATTEMPTS=$((ATTEMPTS + 1))
|
|
146
|
+
done
|
|
147
|
+
|
|
148
|
+
if [ "$PASS" = "true" ]; then
|
|
149
|
+
capy approve "$TASK" --json
|
|
150
|
+
else
|
|
151
|
+
echo "Task $TASK failed after $ATTEMPTS retries"
|
|
152
|
+
fi
|
|
153
|
+
done
|
|
24
154
|
```
|
|
25
155
|
|
|
26
|
-
##
|
|
156
|
+
## Workflow: Triage existing work
|
|
27
157
|
|
|
28
|
-
|
|
158
|
+
When the user asks "what's the status" or you need to check on existing tasks:
|
|
29
159
|
|
|
30
160
|
```bash
|
|
31
|
-
#
|
|
32
|
-
capy
|
|
161
|
+
# Fast overview (no diff fetching, 2x faster, good enough for status checks)
|
|
162
|
+
capy triage --brief --json
|
|
33
163
|
|
|
34
|
-
#
|
|
35
|
-
capy
|
|
164
|
+
# Full detail with diff stats (use when you need to decide actions)
|
|
165
|
+
capy triage --json
|
|
36
166
|
|
|
37
|
-
#
|
|
38
|
-
capy
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
# 4a. If pass: approve
|
|
42
|
-
capy approve <task-id> --json
|
|
167
|
+
# Check specific tasks
|
|
168
|
+
capy triage SCO-15,SCO-24 --json
|
|
169
|
+
```
|
|
43
170
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
171
|
+
Triage returns:
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"summary": { "total": 26, "merged": 7, "ready": 2, "needs_pr": 11, "stuck": 3, "backlog": 3, "in_progress": 0 },
|
|
175
|
+
"tasks": [{ "identifier": "SCO-15", "category": "needs_pr", "title": "...", "pr": null, "diff": { "files": 5, "additions": 494 } }],
|
|
176
|
+
"recommendations": ["Create PRs: SCO-15, SCO-24", "Retry or stop: SCO-21, SCO-22"]
|
|
177
|
+
}
|
|
47
178
|
```
|
|
48
179
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
| `capy review <id>` | Run quality gates (pr_exists, ci, greptile, threads, tests) |
|
|
57
|
-
| `capy approve <id>` | Approve if all gates pass |
|
|
58
|
-
| `capy retry <id> --fix="..."` | Retry with failure context from previous attempt |
|
|
59
|
-
| `capy status` | Dashboard of all threads and tasks |
|
|
60
|
-
| `capy list [status]` | List tasks, optionally filtered |
|
|
61
|
-
| `capy get <id>` | Task or thread details |
|
|
62
|
-
| `capy diff <id>` | View diff from task |
|
|
63
|
-
| `capy pr <id>` | Create PR for task |
|
|
64
|
-
| `capy threads list` | List Captain threads |
|
|
65
|
-
| `capy threads get <id>` | Thread details |
|
|
66
|
-
| `capy threads msg <id> <text>` | Message a thread |
|
|
67
|
-
| `capy threads stop <id>` | Stop a thread |
|
|
68
|
-
| `capy threads messages <id>` | View thread messages |
|
|
69
|
-
|
|
70
|
-
All commands support `--json` for machine-readable output.
|
|
180
|
+
Map categories to actions:
|
|
181
|
+
- `in_progress` → wait
|
|
182
|
+
- `needs_pr` → `capy pr <id>` then review
|
|
183
|
+
- `ready` → `capy review <id>` then approve/retry
|
|
184
|
+
- `stuck` → `capy retry <id> --fix="..."` or `capy stop <id>`
|
|
185
|
+
- `backlog` → `capy start <id>` if the user wants it running
|
|
186
|
+
- `merged` → done, ignore
|
|
71
187
|
|
|
72
188
|
## Quality gates
|
|
73
189
|
|
|
74
|
-
`capy review`
|
|
190
|
+
`capy review <id> --json` returns `quality.pass` (boolean) and `quality.gates` (array of gate results).
|
|
191
|
+
|
|
192
|
+
| Gate | Checks | When it fails, do this |
|
|
193
|
+
|------|--------|----------------------|
|
|
194
|
+
| `pr_exists` | A PR was created | Run `capy pr <id> --json` first |
|
|
195
|
+
| `pr_open` | PR is open or merged | PR was closed. Check why. May need a new PR. |
|
|
196
|
+
| `ci` | CI checks are green | Retry: `capy retry <id> --fix="CI failing: <list failing checks>"` |
|
|
197
|
+
| `greptile` | No unaddressed code review issues | If "still processing": wait 60s, re-review. If issues listed: retry with issues in `--fix` |
|
|
198
|
+
| `threads` | No unresolved GitHub review threads | Retry with the unresolved comments in `--fix` |
|
|
199
|
+
| `tests` | Diff includes test files | Retry: `capy retry <id> --fix="Add tests for the changes"` |
|
|
200
|
+
|
|
201
|
+
## Commands reference
|
|
75
202
|
|
|
76
|
-
|
|
77
|
-
- **pr_open** — PR is OPEN or MERGED
|
|
78
|
-
- **ci** — CI checks passing
|
|
79
|
-
- **greptile** — No unaddressed Greptile issues
|
|
80
|
-
- **threads** — No unresolved GitHub review threads
|
|
81
|
-
- **tests** — Diff includes test files
|
|
203
|
+
All commands support `--json` for structured output. All errors return `{ "error": { "code": "...", "message": "..." } }`.
|
|
82
204
|
|
|
83
|
-
|
|
205
|
+
### Start work
|
|
84
206
|
|
|
85
|
-
|
|
207
|
+
```bash
|
|
208
|
+
capy captain "<prompt>" --json # → { id, url }
|
|
209
|
+
capy build "<prompt>" --json # → { id, identifier, status }
|
|
210
|
+
```
|
|
86
211
|
|
|
87
|
-
|
|
212
|
+
Model shortcuts: `--opus`, `--sonnet`, `--mini`, `--fast`, `--kimi`, `--gemini`, `--grok`, `--qwen`, or `--model=<id>`.
|
|
88
213
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
214
|
+
### Wait and monitor
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
capy wait <id> --timeout=600 --json # → full object on success, { error: { code: "timeout", lastStatus } } on timeout
|
|
218
|
+
capy triage [ids] [--brief] --json # → { summary, tasks, recommendations }
|
|
219
|
+
capy get <id> --json # → full task or thread object
|
|
220
|
+
capy list [status] [--limit=N] --json # → { items, nextCursor, hasMore }
|
|
221
|
+
capy diff <id> --json # → { stats: { files, additions, deletions }, files: [...] }
|
|
222
|
+
capy status --json # → { threads, tasks, watches }
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Take action
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
capy pr <id> [--draft] [--description="..."] --json # → { url, number, title }
|
|
229
|
+
capy review <id> --json # → { task, quality: { pass, gates }, diff }
|
|
230
|
+
capy approve <id> [--force] --json # → { task, quality, approved }
|
|
231
|
+
capy retry <id> --fix="..." --json # → { originalTask, newThread, model }
|
|
232
|
+
capy start <id> --json # → task object
|
|
233
|
+
capy stop <id> --json # → task/thread object
|
|
234
|
+
capy msg <id> "<text>" --json # → { id, sent: true }
|
|
235
|
+
capy re-review <id> --json # → triggers fresh Greptile review
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Threads
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
capy threads list [--limit=N] --json # → { items, nextCursor, hasMore }
|
|
242
|
+
capy threads get <id> --json # → thread with tasks[] and pullRequests[]
|
|
243
|
+
capy threads msg <id> "<text>" --json # → message result
|
|
244
|
+
capy threads stop <id> --json # → stop result
|
|
245
|
+
capy threads messages <id> --json # → conversation history
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Admin
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
capy projects --json # → list of projects
|
|
252
|
+
capy projects get [id] --json # → project details (defaults to current)
|
|
253
|
+
capy models --json # → available models
|
|
254
|
+
capy config [key] [value] # → get/set config
|
|
255
|
+
capy pool [status|set|test|instances|instance|clear] # → warm pool management
|
|
256
|
+
```
|
|
93
257
|
|
|
94
258
|
## Prompting tips
|
|
95
259
|
|
|
96
|
-
|
|
97
|
-
|
|
260
|
+
When writing prompts for `capy captain` or `capy build`, be specific. The Capy agent is another AI. Vague prompts produce vague results.
|
|
261
|
+
|
|
262
|
+
Bad: `"Fix the CI issue"`
|
|
263
|
+
|
|
264
|
+
Good: `"Fix CI for crypto-trading pack. The changeset file is missing. Add a changeset entry for @veto/crypto-trading with patch bump. Run 'npx changeset status' to validate. Files: packages/crypto-trading/. Reference: PLW-201."`
|
|
98
265
|
|
|
99
|
-
|
|
266
|
+
Always include:
|
|
267
|
+
- Which files to touch
|
|
268
|
+
- What the acceptance criteria are
|
|
269
|
+
- What commands to run to verify
|
|
270
|
+
- References to related issues or tasks
|
|
100
271
|
|
|
101
272
|
## Triggers
|
|
102
273
|
|
|
103
|
-
Keywords: capy, captain, build agent, quality gates, delegate coding, orchestrate agents, send to capy, approve task, retry task
|
|
274
|
+
Keywords: capy, captain, build agent, quality gates, delegate coding, orchestrate agents, send to capy, approve task, retry task, triage tasks, check capy status, review PR, what's the status
|
package/src/api.ts
CHANGED
|
@@ -14,36 +14,58 @@ function fail(code: string, message: string): never {
|
|
|
14
14
|
throw new CapyError(code, message);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
const RETRYABLE_STATUS = new Set([429, 500, 502, 503, 504]);
|
|
18
|
+
const MAX_RETRIES = 3;
|
|
19
|
+
|
|
17
20
|
async function rawRequest(apiKey: string, server: string, method: string, path: string, body?: unknown): Promise<any> {
|
|
18
21
|
const url = `${server}${path}`;
|
|
19
22
|
const headers: Record<string, string> = {
|
|
20
23
|
"Authorization": `Bearer ${apiKey}`,
|
|
21
24
|
"Accept": "application/json",
|
|
22
25
|
};
|
|
23
|
-
const init: RequestInit = { method, headers };
|
|
24
26
|
if (body) {
|
|
25
27
|
headers["Content-Type"] = "application/json";
|
|
26
|
-
init.body = JSON.stringify(body);
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
let
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
let lastError: Error | null = null;
|
|
31
|
+
|
|
32
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
33
|
+
if (attempt > 0) {
|
|
34
|
+
const delay = Math.min(1000 * 2 ** (attempt - 1), 8000);
|
|
35
|
+
await new Promise(r => setTimeout(r, delay));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let res: Response;
|
|
39
|
+
try {
|
|
40
|
+
res = await fetch(url, {
|
|
41
|
+
method,
|
|
42
|
+
headers,
|
|
43
|
+
...(body ? { body: JSON.stringify(body) } : {}),
|
|
44
|
+
});
|
|
45
|
+
} catch (e: unknown) {
|
|
46
|
+
lastError = e as Error;
|
|
47
|
+
if (attempt < MAX_RETRIES) continue;
|
|
48
|
+
fail("network_error", `request failed after ${MAX_RETRIES + 1} attempts — ${lastError.message}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (RETRYABLE_STATUS.has(res.status) && attempt < MAX_RETRIES) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const text = await res.text();
|
|
56
|
+
let data: any;
|
|
57
|
+
try {
|
|
58
|
+
data = JSON.parse(text);
|
|
59
|
+
} catch {
|
|
60
|
+
fail("bad_response", `bad API response: ${text.slice(0, 200)}`);
|
|
61
|
+
}
|
|
62
|
+
if (data.error) {
|
|
63
|
+
fail(data.error.code || "api_error", data.error.message || "unknown API error");
|
|
64
|
+
}
|
|
65
|
+
return data;
|
|
34
66
|
}
|
|
35
67
|
|
|
36
|
-
|
|
37
|
-
let data: any;
|
|
38
|
-
try {
|
|
39
|
-
data = JSON.parse(text);
|
|
40
|
-
} catch {
|
|
41
|
-
fail("bad_response", `bad API response: ${text.slice(0, 200)}`);
|
|
42
|
-
}
|
|
43
|
-
if (data.error) {
|
|
44
|
-
fail(data.error.code || "api_error", data.error.message || "unknown API error");
|
|
45
|
-
}
|
|
46
|
-
return data;
|
|
68
|
+
fail("network_error", `request failed after ${MAX_RETRIES + 1} attempts — ${lastError?.message || "unknown"}`);
|
|
47
69
|
}
|
|
48
70
|
|
|
49
71
|
async function request(method: string, path: string, body?: unknown): Promise<any> {
|
|
@@ -73,6 +95,11 @@ export async function listModelsWithKey(apiKey: string, server = "https://capy.a
|
|
|
73
95
|
return data.models || [];
|
|
74
96
|
}
|
|
75
97
|
|
|
98
|
+
export async function listProjectsAuth(): Promise<Project[]> {
|
|
99
|
+
const data = await request("GET", "/projects");
|
|
100
|
+
return data.items || [];
|
|
101
|
+
}
|
|
102
|
+
|
|
76
103
|
export async function getProject(id?: string): Promise<Project & { createdAt?: string; updatedAt?: string }> {
|
|
77
104
|
const pid = id || config.load().projectId;
|
|
78
105
|
return request("GET", `/projects/${pid}`);
|
package/src/commands/_shared.ts
CHANGED
|
@@ -17,6 +17,10 @@ export const jsonArg = {
|
|
|
17
17
|
json: { type: "boolean", description: "Machine-readable JSON output", default: false },
|
|
18
18
|
} as const satisfies ArgsDef;
|
|
19
19
|
|
|
20
|
+
export function isThreadId(id: string): boolean {
|
|
21
|
+
return id.length > 20 || (id.length > 10 && !id.match(/^[A-Z]+-\d+$/));
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
export function resolveModel(args: Record<string, unknown>): string | null {
|
|
21
25
|
if (args.model) return String(args.model);
|
|
22
26
|
if (args.opus) return "claude-opus-4-6";
|
package/src/commands/diff-pr.ts
CHANGED
|
@@ -30,6 +30,8 @@ export const pr = defineCommand({
|
|
|
30
30
|
args: {
|
|
31
31
|
id: { type: "positional", description: "Task ID", required: true },
|
|
32
32
|
title: { type: "positional", required: false, description: "PR title" },
|
|
33
|
+
description: { type: "string", description: "PR body/description" },
|
|
34
|
+
draft: { type: "boolean", description: "Create as draft PR", default: false },
|
|
33
35
|
...jsonArg,
|
|
34
36
|
},
|
|
35
37
|
async run({ args }) {
|
|
@@ -37,7 +39,10 @@ export const pr = defineCommand({
|
|
|
37
39
|
const fmt = await import("../output.js");
|
|
38
40
|
const { log } = await import("@clack/prompts");
|
|
39
41
|
|
|
40
|
-
const body
|
|
42
|
+
const body: Record<string, unknown> = {};
|
|
43
|
+
if (args.title) body.title = args.title;
|
|
44
|
+
if (args.description) body.description = args.description;
|
|
45
|
+
if (args.draft) body.draft = true;
|
|
41
46
|
const data = await api.createPR(args.id, body);
|
|
42
47
|
if (args.json) { fmt.out(data); return; }
|
|
43
48
|
log.success(`PR: ${data.url}`);
|