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 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
- 21 MCP tools with full API parity:
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
- ## How to use capyai
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
- capy wait <threadId> --timeout=600 --json
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
- `{text}` is replaced with a summary when the task completes. Examples:
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
- 21 tools with full API parity, including warm pool management.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capyai",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "description": "Unofficial Capy.ai CLI for agent orchestration with quality gates",
6
6
  "bin": {
@@ -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.4.0"
6
+ version: "0.5.0"
7
7
  ---
8
8
 
9
9
  # capy
10
10
 
11
- Orchestrate Capy.ai coding agents. Start tasks, wait for them, enforce quality gates, approve or retry. Works with any AI agent (Claude Code, Codex, OpenClaw, Poke).
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
- ## Install
13
+ ## When to use this skill
14
14
 
15
- ```bash
16
- npm i -g capyai
17
- capy init
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
- Or set env vars directly:
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
- export CAPY_API_KEY=capy_...
23
- export CAPY_PROJECT_ID=...
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
- ## Agent workflow
156
+ ## Workflow: Triage existing work
27
157
 
28
- The core loop for any agent:
158
+ When the user asks "what's the status" or you need to check on existing tasks:
29
159
 
30
160
  ```bash
31
- # 1. Start work
32
- capy captain "Implement feature X. Files: src/foo.ts. Tests required." --json
161
+ # Fast overview (no diff fetching, 2x faster, good enough for status checks)
162
+ capy triage --brief --json
33
163
 
34
- # 2. Wait for completion (blocks until done)
35
- capy wait <thread-id> --timeout=600 --json
164
+ # Full detail with diff stats (use when you need to decide actions)
165
+ capy triage --json
36
166
 
37
- # 3. Check quality gates
38
- capy review <task-id> --json
39
- # Parse quality.pass boolean
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
- # 4b. If fail: retry with context
45
- capy retry <task-id> --fix="fix the failing CI check" --json
46
- # Go back to step 2
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
- ## Commands
50
-
51
- | Command | What it does |
52
- |---------|-------------|
53
- | `capy captain "<prompt>"` | Start Captain thread (primary agent) |
54
- | `capy build "<prompt>"` | Start Build agent (isolated, small tasks) |
55
- | `capy wait <id>` | Block until task/thread reaches terminal state |
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` checks pass/fail gates:
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
- - **pr_exists** PR was created
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
- Configure which run via `capy config quality.reviewProvider greptile|capy|both|none`.
205
+ ### Start work
84
206
 
85
- ## JSON output
207
+ ```bash
208
+ capy captain "<prompt>" --json # → { id, url }
209
+ capy build "<prompt>" --json # → { id, identifier, status }
210
+ ```
86
211
 
87
- Every command returns structured JSON with `--json`. Errors return `{ "error": { "code": "...", "message": "..." } }`.
212
+ Model shortcuts: `--opus`, `--sonnet`, `--mini`, `--fast`, `--kimi`, `--gemini`, `--grok`, `--qwen`, or `--model=<id>`.
88
213
 
89
- Key fields for agents:
90
- - `capy review --json` → `quality.pass` (boolean), `quality.gates` (array)
91
- - `capy wait --json` → full task/thread object on completion, `error.code: "timeout"` on timeout
92
- - `capy retry --json``newThread` (thread ID to wait on)
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
- Bad: "Fix the CI issue"
97
- 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."
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
- Specific files, specific functions, specific acceptance criteria.
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 res: Response;
30
- try {
31
- res = await fetch(url, init);
32
- } catch (e: unknown) {
33
- fail("network_error", `request failed ${(e as Error).message}`);
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
- const text = await res.text();
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}`);
@@ -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";
@@ -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 = args.title ? { title: args.title } : {};
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}`);