claude-queue 1.3.3 → 1.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/Makefile +37 -0
- package/README.md +198 -0
- package/claude-queue.sh +954 -0
- package/package.json +13 -49
- package/dist/cli.js +0 -686
- package/dist/mcp/index.d.ts +0 -1
- package/dist/mcp/index.js +0 -577
- package/dist/mcp.js +0 -8
- package/dist/server/index.js +0 -1379
- package/dist/skills/queue/SKILL.md +0 -153
- package/dist/ui/assets/index-CGuz4rUk.css +0 -1
- package/dist/ui/assets/index-yyLY0y4s.js +0 -90
- package/dist/ui/index.html +0 -15
- package/dist/ui/sounds/complete.mp3 +0 -0
- package/dist/ui/sounds/question.mp3 +0 -0
- package/dist/ui/sounds/start.mp3 +0 -0
package/claude-queue.sh
ADDED
|
@@ -0,0 +1,954 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# claude-queue - Automated GitHub issue solver & creator
|
|
4
|
+
#
|
|
5
|
+
# Commands:
|
|
6
|
+
# claude-queue [options] Solve open issues (default)
|
|
7
|
+
# claude-queue create [options] Create issues from text or interactively
|
|
8
|
+
#
|
|
9
|
+
# Solve options:
|
|
10
|
+
# --max-retries N Max retries per issue (default: 3)
|
|
11
|
+
# --max-turns N Max Claude turns per attempt (default: 50)
|
|
12
|
+
# --label LABEL Only process issues with this label
|
|
13
|
+
# --model MODEL Claude model to use
|
|
14
|
+
# -v, --version Show version
|
|
15
|
+
# -h, --help Show this help message
|
|
16
|
+
#
|
|
17
|
+
# Create options:
|
|
18
|
+
# -i, --interactive Interview mode (Claude asks questions)
|
|
19
|
+
# --label LABEL Add this label to all created issues
|
|
20
|
+
# --model MODEL Claude model to use
|
|
21
|
+
# -h, --help Show help for create
|
|
22
|
+
|
|
23
|
+
set -euo pipefail
|
|
24
|
+
|
|
25
|
+
VERSION=$(node -p "require('$(dirname "$0")/package.json').version" 2>/dev/null || echo "unknown")
|
|
26
|
+
|
|
27
|
+
MAX_RETRIES=3
|
|
28
|
+
MAX_TURNS=50
|
|
29
|
+
ISSUE_FILTER=""
|
|
30
|
+
MODEL_FLAG=""
|
|
31
|
+
DATE=$(date +%Y-%m-%d)
|
|
32
|
+
TIMESTAMP=$(date +%H%M%S)
|
|
33
|
+
BRANCH="claude-queue/${DATE}"
|
|
34
|
+
LOG_DIR="/tmp/claude-queue-${DATE}-${TIMESTAMP}"
|
|
35
|
+
|
|
36
|
+
LABEL_PROGRESS="claude-queue:in-progress"
|
|
37
|
+
LABEL_SOLVED="claude-queue:solved"
|
|
38
|
+
LABEL_FAILED="claude-queue:failed"
|
|
39
|
+
|
|
40
|
+
RED='\033[0;31m'
|
|
41
|
+
GREEN='\033[0;32m'
|
|
42
|
+
YELLOW='\033[1;33m'
|
|
43
|
+
BLUE='\033[0;34m'
|
|
44
|
+
DIM='\033[2m'
|
|
45
|
+
BOLD='\033[1m'
|
|
46
|
+
NC='\033[0m'
|
|
47
|
+
|
|
48
|
+
declare -a SOLVED_ISSUES=()
|
|
49
|
+
declare -a FAILED_ISSUES=()
|
|
50
|
+
declare -a SKIPPED_ISSUES=()
|
|
51
|
+
CURRENT_ISSUE=""
|
|
52
|
+
START_TIME=$(date +%s)
|
|
53
|
+
|
|
54
|
+
show_help() {
|
|
55
|
+
echo "claude-queue v${VERSION} — Automated GitHub issue solver & creator"
|
|
56
|
+
echo ""
|
|
57
|
+
echo "Usage:"
|
|
58
|
+
echo " claude-queue [options] Solve open issues (default)"
|
|
59
|
+
echo " claude-queue create [options] [text] Create issues from text or interactively"
|
|
60
|
+
echo ""
|
|
61
|
+
echo "Solve options:"
|
|
62
|
+
echo " --max-retries N Max retries per issue (default: 3)"
|
|
63
|
+
echo " --max-turns N Max Claude turns per attempt (default: 50)"
|
|
64
|
+
echo " --label LABEL Only process issues with this label"
|
|
65
|
+
echo " --model MODEL Claude model to use"
|
|
66
|
+
echo " -v, --version Show version"
|
|
67
|
+
echo " -h, --help Show this help message"
|
|
68
|
+
echo ""
|
|
69
|
+
echo "Run 'claude-queue create --help' for create options."
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
show_create_help() {
|
|
73
|
+
echo "claude-queue create — Generate GitHub issues from text or an interactive interview"
|
|
74
|
+
echo ""
|
|
75
|
+
echo "Usage:"
|
|
76
|
+
echo " claude-queue create \"description\" Create issues from inline text"
|
|
77
|
+
echo " claude-queue create Prompt for text input (Ctrl+D to finish)"
|
|
78
|
+
echo " claude-queue create -i Interactive interview mode"
|
|
79
|
+
echo ""
|
|
80
|
+
echo "Options:"
|
|
81
|
+
echo " -i, --interactive Interview mode (Claude asks clarifying questions first)"
|
|
82
|
+
echo " --label LABEL Add this label to every created issue"
|
|
83
|
+
echo " --model MODEL Claude model to use"
|
|
84
|
+
echo " -h, --help Show this help message"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
log() { echo -e "${DIM}$(date +%H:%M:%S)${NC} ${BLUE}[claude-queue]${NC} $1"; }
|
|
88
|
+
log_success() { echo -e "${DIM}$(date +%H:%M:%S)${NC} ${GREEN}[claude-queue]${NC} $1"; }
|
|
89
|
+
log_warn() { echo -e "${DIM}$(date +%H:%M:%S)${NC} ${YELLOW}[claude-queue]${NC} $1"; }
|
|
90
|
+
log_error() { echo -e "${DIM}$(date +%H:%M:%S)${NC} ${RED}[claude-queue]${NC} $1"; }
|
|
91
|
+
log_header() { echo -e "\n${BOLD}═══ $1 ═══${NC}\n"; }
|
|
92
|
+
|
|
93
|
+
cleanup() {
|
|
94
|
+
local exit_code=$?
|
|
95
|
+
|
|
96
|
+
if [ -n "$CURRENT_ISSUE" ]; then
|
|
97
|
+
log_warn "Interrupted while working on issue #${CURRENT_ISSUE}"
|
|
98
|
+
gh issue edit "$CURRENT_ISSUE" --remove-label "$LABEL_PROGRESS" 2>/dev/null || true
|
|
99
|
+
gh issue edit "$CURRENT_ISSUE" --add-label "$LABEL_FAILED" 2>/dev/null || true
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
if [ $exit_code -ne 0 ] && [ ${#SOLVED_ISSUES[@]} -gt 0 ]; then
|
|
103
|
+
log_warn "Script interrupted but ${#SOLVED_ISSUES[@]} issue(s) were solved."
|
|
104
|
+
log_warn "Branch '${BRANCH}' has your commits. Push manually if needed."
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
if [ -d "$LOG_DIR" ]; then
|
|
108
|
+
log "Logs saved to: ${LOG_DIR}"
|
|
109
|
+
fi
|
|
110
|
+
}
|
|
111
|
+
trap cleanup EXIT
|
|
112
|
+
|
|
113
|
+
preflight() {
|
|
114
|
+
log_header "Preflight Checks"
|
|
115
|
+
|
|
116
|
+
local failed=false
|
|
117
|
+
|
|
118
|
+
for cmd in gh claude git jq; do
|
|
119
|
+
if command -v "$cmd" &>/dev/null; then
|
|
120
|
+
log " $cmd ... found"
|
|
121
|
+
else
|
|
122
|
+
log_error " $cmd ... NOT FOUND"
|
|
123
|
+
failed=true
|
|
124
|
+
fi
|
|
125
|
+
done
|
|
126
|
+
|
|
127
|
+
if ! gh auth status &>/dev/null; then
|
|
128
|
+
log_error " gh auth ... not authenticated"
|
|
129
|
+
failed=true
|
|
130
|
+
else
|
|
131
|
+
log " gh auth ... ok"
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
|
|
135
|
+
log_error " git repo ... not inside a git repository"
|
|
136
|
+
failed=true
|
|
137
|
+
else
|
|
138
|
+
log " git repo ... ok"
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
|
|
142
|
+
log_error " working tree ... dirty (commit or stash changes first)"
|
|
143
|
+
failed=true
|
|
144
|
+
else
|
|
145
|
+
log " working tree ... clean"
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
if [ "$failed" = true ]; then
|
|
149
|
+
log_error "Preflight failed. Aborting."
|
|
150
|
+
exit 1
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
mkdir -p "$LOG_DIR"
|
|
154
|
+
log " log dir ... ${LOG_DIR}"
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
ensure_labels() {
|
|
158
|
+
log "Creating labels (if missing)..."
|
|
159
|
+
|
|
160
|
+
gh label create "$LABEL_PROGRESS" --color "fbca04" --description "claude-queue is working on this" --force 2>/dev/null || true
|
|
161
|
+
gh label create "$LABEL_SOLVED" --color "0e8a16" --description "Solved by claude-queue" --force 2>/dev/null || true
|
|
162
|
+
gh label create "$LABEL_FAILED" --color "d93f0b" --description "claude-queue could not solve this" --force 2>/dev/null || true
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
setup_branch() {
|
|
166
|
+
log_header "Branch Setup"
|
|
167
|
+
|
|
168
|
+
local default_branch
|
|
169
|
+
default_branch=$(gh repo view --json defaultBranchRef -q '.defaultBranchRef.name')
|
|
170
|
+
log "Default branch: ${default_branch}"
|
|
171
|
+
|
|
172
|
+
git fetch origin "$default_branch" --quiet
|
|
173
|
+
|
|
174
|
+
if git show-ref --verify --quiet "refs/heads/${BRANCH}"; then
|
|
175
|
+
log_warn "Branch ${BRANCH} already exists, adding timestamp suffix"
|
|
176
|
+
BRANCH="${BRANCH}-${TIMESTAMP}"
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
git checkout -b "$BRANCH" "origin/${default_branch}" --quiet
|
|
180
|
+
log_success "Created branch: ${BRANCH}"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fetch_issues() {
|
|
184
|
+
local args=(--state open --json "number,title,body,labels" --limit 200)
|
|
185
|
+
|
|
186
|
+
if [ -n "$ISSUE_FILTER" ]; then
|
|
187
|
+
args+=(--label "$ISSUE_FILTER")
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
gh issue list "${args[@]}"
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
process_issue() {
|
|
194
|
+
local issue_number=$1
|
|
195
|
+
local issue_title="$2"
|
|
196
|
+
local attempt=0
|
|
197
|
+
local solved=false
|
|
198
|
+
local issue_log="${LOG_DIR}/issue-${issue_number}.md"
|
|
199
|
+
local checkpoint
|
|
200
|
+
checkpoint=$(git rev-parse HEAD)
|
|
201
|
+
|
|
202
|
+
CURRENT_ISSUE="$issue_number"
|
|
203
|
+
|
|
204
|
+
log_header "Issue #${issue_number}: ${issue_title}"
|
|
205
|
+
|
|
206
|
+
gh issue edit "$issue_number" \
|
|
207
|
+
--remove-label "$LABEL_SOLVED" \
|
|
208
|
+
--remove-label "$LABEL_FAILED" \
|
|
209
|
+
2>/dev/null || true
|
|
210
|
+
gh issue edit "$issue_number" --add-label "$LABEL_PROGRESS"
|
|
211
|
+
|
|
212
|
+
{
|
|
213
|
+
echo "# Issue #${issue_number}: ${issue_title}"
|
|
214
|
+
echo ""
|
|
215
|
+
echo "**Started:** $(date)"
|
|
216
|
+
echo ""
|
|
217
|
+
} > "$issue_log"
|
|
218
|
+
|
|
219
|
+
while [ "$attempt" -lt "$MAX_RETRIES" ] && [ "$solved" = false ]; do
|
|
220
|
+
attempt=$((attempt + 1))
|
|
221
|
+
log "Attempt ${attempt}/${MAX_RETRIES}"
|
|
222
|
+
|
|
223
|
+
git reset --hard "$checkpoint" --quiet 2>/dev/null || true
|
|
224
|
+
git clean -fd --quiet 2>/dev/null || true
|
|
225
|
+
|
|
226
|
+
echo "## Attempt ${attempt}" >> "$issue_log"
|
|
227
|
+
echo "" >> "$issue_log"
|
|
228
|
+
|
|
229
|
+
local custom_instructions=""
|
|
230
|
+
if [ -f ".claude-queue" ]; then
|
|
231
|
+
custom_instructions="
|
|
232
|
+
|
|
233
|
+
Additional project-specific instructions:
|
|
234
|
+
$(cat .claude-queue)"
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
local prompt
|
|
238
|
+
prompt="You are an automated assistant solving a GitHub issue in this repository.
|
|
239
|
+
|
|
240
|
+
First, read the full issue details by running:
|
|
241
|
+
gh issue view ${issue_number}
|
|
242
|
+
|
|
243
|
+
Then:
|
|
244
|
+
1. Explore the codebase to understand the project structure and conventions
|
|
245
|
+
2. Implement a complete, correct fix for the issue
|
|
246
|
+
3. Run any existing tests to verify your fix doesn't break anything
|
|
247
|
+
4. If tests fail because of your changes, fix them
|
|
248
|
+
|
|
249
|
+
Rules:
|
|
250
|
+
- Do NOT create any git commits
|
|
251
|
+
- Do NOT push anything
|
|
252
|
+
- Match the existing code style exactly
|
|
253
|
+
- Only change what is necessary to solve the issue
|
|
254
|
+
${custom_instructions}
|
|
255
|
+
If this issue does NOT require code changes (e.g. it's a question, a request for external action,
|
|
256
|
+
a finding, or something that can't be solved with code), output a line that says CLAUDE_QUEUE_NO_CODE
|
|
257
|
+
followed by an explanation of what needs to be done instead.
|
|
258
|
+
|
|
259
|
+
Otherwise, when you are done, output a line that says CLAUDE_QUEUE_SUMMARY followed by a 2-3 sentence
|
|
260
|
+
description of what you changed and why."
|
|
261
|
+
|
|
262
|
+
local attempt_log="${LOG_DIR}/issue-${issue_number}-attempt-${attempt}.log"
|
|
263
|
+
local claude_exit=0
|
|
264
|
+
|
|
265
|
+
# shellcheck disable=SC2086
|
|
266
|
+
claude -p "$prompt" \
|
|
267
|
+
--dangerously-skip-permissions \
|
|
268
|
+
--max-turns "$MAX_TURNS" \
|
|
269
|
+
$MODEL_FLAG \
|
|
270
|
+
> "$attempt_log" 2>&1 || claude_exit=$?
|
|
271
|
+
|
|
272
|
+
if [ "$claude_exit" -ne 0 ]; then
|
|
273
|
+
log_warn "Claude exited with code ${claude_exit}"
|
|
274
|
+
echo "**Claude exited with code ${claude_exit}**" >> "$issue_log"
|
|
275
|
+
echo "" >> "$issue_log"
|
|
276
|
+
continue
|
|
277
|
+
fi
|
|
278
|
+
|
|
279
|
+
local no_code_reason
|
|
280
|
+
no_code_reason=$(grep -A 20 "CLAUDE_QUEUE_NO_CODE" "$attempt_log" 2>/dev/null | tail -n +2 | head -10 || echo "")
|
|
281
|
+
|
|
282
|
+
if [ -n "$no_code_reason" ]; then
|
|
283
|
+
log "Issue does not require code changes"
|
|
284
|
+
{
|
|
285
|
+
echo "### No Code Changes Required"
|
|
286
|
+
echo "$no_code_reason"
|
|
287
|
+
echo ""
|
|
288
|
+
} >> "$issue_log"
|
|
289
|
+
solved=true
|
|
290
|
+
log_success "Issue #${issue_number} handled (no code changes needed)"
|
|
291
|
+
break
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
local changed_files
|
|
295
|
+
changed_files=$(git diff --name-only 2>/dev/null; git ls-files --others --exclude-standard 2>/dev/null)
|
|
296
|
+
|
|
297
|
+
if [ -z "$changed_files" ]; then
|
|
298
|
+
log_warn "No file changes detected"
|
|
299
|
+
echo "**No file changes detected**" >> "$issue_log"
|
|
300
|
+
echo "" >> "$issue_log"
|
|
301
|
+
continue
|
|
302
|
+
fi
|
|
303
|
+
|
|
304
|
+
log_success "Changes detected in:"
|
|
305
|
+
echo "$changed_files" | while IFS= read -r f; do
|
|
306
|
+
log " ${f}"
|
|
307
|
+
done
|
|
308
|
+
|
|
309
|
+
local summary
|
|
310
|
+
summary=$(grep -A 20 "CLAUDE_QUEUE_SUMMARY" "$attempt_log" 2>/dev/null | tail -n +2 | head -10 || echo "No summary provided.")
|
|
311
|
+
|
|
312
|
+
{
|
|
313
|
+
echo "### Summary"
|
|
314
|
+
echo "$summary"
|
|
315
|
+
echo ""
|
|
316
|
+
echo "### Changed Files"
|
|
317
|
+
echo "$changed_files" | while IFS= read -r f; do echo "- \`${f}\`"; done
|
|
318
|
+
echo ""
|
|
319
|
+
} >> "$issue_log"
|
|
320
|
+
|
|
321
|
+
git add -A
|
|
322
|
+
git commit -m "fix: resolve #${issue_number} - ${issue_title}
|
|
323
|
+
|
|
324
|
+
Automated fix by claude-queue.
|
|
325
|
+
Closes #${issue_number}" --quiet
|
|
326
|
+
|
|
327
|
+
solved=true
|
|
328
|
+
|
|
329
|
+
log_success "Solved issue #${issue_number} on attempt ${attempt}"
|
|
330
|
+
done
|
|
331
|
+
|
|
332
|
+
gh issue edit "$issue_number" --remove-label "$LABEL_PROGRESS" 2>/dev/null || true
|
|
333
|
+
|
|
334
|
+
{
|
|
335
|
+
echo "**Finished:** $(date)"
|
|
336
|
+
echo "**Status:** $([ "$solved" = true ] && echo "SOLVED" || echo "FAILED after ${MAX_RETRIES} attempts")"
|
|
337
|
+
} >> "$issue_log"
|
|
338
|
+
|
|
339
|
+
if [ "$solved" = true ]; then
|
|
340
|
+
gh issue edit "$issue_number" --add-label "$LABEL_SOLVED"
|
|
341
|
+
gh issue comment "$issue_number" --body-file "$issue_log" 2>/dev/null || true
|
|
342
|
+
SOLVED_ISSUES+=("${issue_number}|${issue_title}")
|
|
343
|
+
else
|
|
344
|
+
gh issue edit "$issue_number" --add-label "$LABEL_FAILED"
|
|
345
|
+
gh issue comment "$issue_number" --body "claude-queue failed to solve this issue after ${MAX_RETRIES} attempts." 2>/dev/null || true
|
|
346
|
+
FAILED_ISSUES+=("${issue_number}|${issue_title}")
|
|
347
|
+
git reset --hard "$checkpoint" --quiet 2>/dev/null || true
|
|
348
|
+
git clean -fd --quiet 2>/dev/null || true
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
CURRENT_ISSUE=""
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
review_and_fix() {
|
|
355
|
+
log_header "Final Review & Fix Pass"
|
|
356
|
+
|
|
357
|
+
local review_log="${LOG_DIR}/review.md"
|
|
358
|
+
|
|
359
|
+
local prompt
|
|
360
|
+
prompt="You are doing a final review pass on automated code changes in this repository.
|
|
361
|
+
|
|
362
|
+
Look at all uncommitted and recently committed changes on this branch. For each file that was modified:
|
|
363
|
+
1. Read the full file
|
|
364
|
+
2. Check for bugs, incomplete implementations, lazy code, missed edge cases, or style inconsistencies
|
|
365
|
+
3. Fix anything you find
|
|
366
|
+
|
|
367
|
+
Rules:
|
|
368
|
+
- Do NOT create any git commits
|
|
369
|
+
- Do NOT push anything
|
|
370
|
+
- Only fix real problems, don't refactor for style preferences
|
|
371
|
+
- Match the existing code style exactly
|
|
372
|
+
|
|
373
|
+
When you are done, output a line that says CLAUDE_QUEUE_REVIEW followed by a brief summary of what you fixed. If nothing needed fixing, say so."
|
|
374
|
+
|
|
375
|
+
# shellcheck disable=SC2086
|
|
376
|
+
claude -p "$prompt" \
|
|
377
|
+
--dangerously-skip-permissions \
|
|
378
|
+
--max-turns "$MAX_TURNS" \
|
|
379
|
+
$MODEL_FLAG \
|
|
380
|
+
> "$review_log" 2>&1 || true
|
|
381
|
+
|
|
382
|
+
local changed_files
|
|
383
|
+
changed_files=$(git diff --name-only 2>/dev/null; git ls-files --others --exclude-standard 2>/dev/null)
|
|
384
|
+
|
|
385
|
+
if [ -n "$changed_files" ]; then
|
|
386
|
+
log_success "Review pass made fixes:"
|
|
387
|
+
echo "$changed_files" | while IFS= read -r f; do
|
|
388
|
+
log " ${f}"
|
|
389
|
+
done
|
|
390
|
+
|
|
391
|
+
git add -A
|
|
392
|
+
git commit -m "chore: final review pass
|
|
393
|
+
|
|
394
|
+
Automated review and fixes by claude-queue." --quiet
|
|
395
|
+
else
|
|
396
|
+
log "Review pass found nothing to fix"
|
|
397
|
+
fi
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
create_pr() {
|
|
401
|
+
log_header "Creating Pull Request"
|
|
402
|
+
|
|
403
|
+
local default_branch
|
|
404
|
+
default_branch=$(gh repo view --json defaultBranchRef -q '.defaultBranchRef.name')
|
|
405
|
+
local elapsed=$(( $(date +%s) - START_TIME ))
|
|
406
|
+
local duration
|
|
407
|
+
duration="$(( elapsed / 3600 ))h $(( (elapsed % 3600) / 60 ))m $(( elapsed % 60 ))s"
|
|
408
|
+
local pr_body="${LOG_DIR}/pr-body.md"
|
|
409
|
+
local total_processed=$(( ${#SOLVED_ISSUES[@]} + ${#FAILED_ISSUES[@]} ))
|
|
410
|
+
|
|
411
|
+
{
|
|
412
|
+
echo "## claude-queue Run Summary"
|
|
413
|
+
echo ""
|
|
414
|
+
echo "| Metric | Value |"
|
|
415
|
+
echo "|--------|-------|"
|
|
416
|
+
echo "| Date | ${DATE} |"
|
|
417
|
+
echo "| Duration | ${duration} |"
|
|
418
|
+
echo "| Issues processed | ${total_processed} |"
|
|
419
|
+
echo "| Solved | ${#SOLVED_ISSUES[@]} |"
|
|
420
|
+
echo "| Failed | ${#FAILED_ISSUES[@]} |"
|
|
421
|
+
echo "| Skipped | ${#SKIPPED_ISSUES[@]} |"
|
|
422
|
+
echo ""
|
|
423
|
+
|
|
424
|
+
if [ ${#SOLVED_ISSUES[@]} -gt 0 ]; then
|
|
425
|
+
echo "### Solved Issues"
|
|
426
|
+
echo ""
|
|
427
|
+
echo "| Issue | Title |"
|
|
428
|
+
echo "|-------|-------|"
|
|
429
|
+
for entry in "${SOLVED_ISSUES[@]}"; do
|
|
430
|
+
local num="${entry%%|*}"
|
|
431
|
+
local title="${entry#*|}"
|
|
432
|
+
echo "| #${num} | ${title} |"
|
|
433
|
+
done
|
|
434
|
+
echo ""
|
|
435
|
+
fi
|
|
436
|
+
|
|
437
|
+
if [ ${#FAILED_ISSUES[@]} -gt 0 ]; then
|
|
438
|
+
echo "### Failed Issues"
|
|
439
|
+
echo ""
|
|
440
|
+
echo "| Issue | Title |"
|
|
441
|
+
echo "|-------|-------|"
|
|
442
|
+
for entry in "${FAILED_ISSUES[@]}"; do
|
|
443
|
+
local num="${entry%%|*}"
|
|
444
|
+
local title="${entry#*|}"
|
|
445
|
+
echo "| #${num} | ${title} |"
|
|
446
|
+
done
|
|
447
|
+
echo ""
|
|
448
|
+
fi
|
|
449
|
+
|
|
450
|
+
echo "---"
|
|
451
|
+
echo ""
|
|
452
|
+
echo "### Chain Logs"
|
|
453
|
+
echo ""
|
|
454
|
+
|
|
455
|
+
for log_file in "${LOG_DIR}"/issue-*.md; do
|
|
456
|
+
if [ ! -f "$log_file" ]; then
|
|
457
|
+
continue
|
|
458
|
+
fi
|
|
459
|
+
|
|
460
|
+
local issue_num
|
|
461
|
+
issue_num=$(basename "$log_file" | grep -oE '[0-9]+')
|
|
462
|
+
|
|
463
|
+
echo "<details>"
|
|
464
|
+
echo "<summary>Issue #${issue_num} Log</summary>"
|
|
465
|
+
echo ""
|
|
466
|
+
head -c 40000 "$log_file"
|
|
467
|
+
echo ""
|
|
468
|
+
echo "</details>"
|
|
469
|
+
echo ""
|
|
470
|
+
done
|
|
471
|
+
} > "$pr_body"
|
|
472
|
+
|
|
473
|
+
local body_size
|
|
474
|
+
body_size=$(wc -c < "$pr_body")
|
|
475
|
+
if [ "$body_size" -gt 60000 ]; then
|
|
476
|
+
log_warn "PR body is ${body_size} bytes, truncating to fit GitHub limits"
|
|
477
|
+
head -c 59000 "$pr_body" > "${pr_body}.tmp"
|
|
478
|
+
{
|
|
479
|
+
echo ""
|
|
480
|
+
echo ""
|
|
481
|
+
echo "---"
|
|
482
|
+
echo "*Log truncated. Full logs available at: ${LOG_DIR}*"
|
|
483
|
+
} >> "${pr_body}.tmp"
|
|
484
|
+
mv "${pr_body}.tmp" "$pr_body"
|
|
485
|
+
fi
|
|
486
|
+
|
|
487
|
+
git push origin "$BRANCH" --quiet
|
|
488
|
+
log_success "Pushed branch to origin"
|
|
489
|
+
|
|
490
|
+
local pr_url
|
|
491
|
+
pr_url=$(gh pr create \
|
|
492
|
+
--base "$default_branch" \
|
|
493
|
+
--head "$BRANCH" \
|
|
494
|
+
--title "claude-queue: Automated fixes (${DATE})" \
|
|
495
|
+
--body-file "$pr_body")
|
|
496
|
+
|
|
497
|
+
log_success "Pull request created: ${pr_url}"
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
main() {
|
|
501
|
+
echo -e "${BOLD}"
|
|
502
|
+
echo ' _ _ '
|
|
503
|
+
echo ' ___| | __ _ _ _ __| | ___ __ _ _ _ ___ _ _ ___'
|
|
504
|
+
echo ' / __| |/ _` | | | |/ _` |/ _ \_____ / _` | | | |/ _ \ | | |/ _ \'
|
|
505
|
+
echo ' | (__| | (_| | |_| | (_| | __/_____| (_| | |_| | __/ |_| | __/'
|
|
506
|
+
echo ' \___|_|\__,_|\__,_|\__,_|\___| \__, |\__,_|\___|\__,_|\___|'
|
|
507
|
+
echo ' |_| '
|
|
508
|
+
echo -e "${NC}"
|
|
509
|
+
echo -e " ${DIM}Automated GitHub issue solver${NC}"
|
|
510
|
+
echo ""
|
|
511
|
+
|
|
512
|
+
preflight
|
|
513
|
+
ensure_labels
|
|
514
|
+
setup_branch
|
|
515
|
+
|
|
516
|
+
log_header "Fetching Issues"
|
|
517
|
+
|
|
518
|
+
local issues
|
|
519
|
+
issues=$(fetch_issues)
|
|
520
|
+
local total
|
|
521
|
+
total=$(echo "$issues" | jq length)
|
|
522
|
+
|
|
523
|
+
if [ "$total" -eq 0 ]; then
|
|
524
|
+
log "No open issues found. Going back to sleep."
|
|
525
|
+
exit 0
|
|
526
|
+
fi
|
|
527
|
+
|
|
528
|
+
log "Found ${total} open issue(s)"
|
|
529
|
+
|
|
530
|
+
for i in $(seq 0 $((total - 1))); do
|
|
531
|
+
local number title labels
|
|
532
|
+
number=$(echo "$issues" | jq -r ".[$i].number")
|
|
533
|
+
title=$(echo "$issues" | jq -r ".[$i].title")
|
|
534
|
+
labels=$(echo "$issues" | jq -r "[.[$i].labels[].name] | join(\",\")" 2>/dev/null || echo "")
|
|
535
|
+
|
|
536
|
+
if echo "$labels" | grep -q "claude-queue:"; then
|
|
537
|
+
log "Skipping #${number} (already has a claude-queue label)"
|
|
538
|
+
SKIPPED_ISSUES+=("${number}|${title}")
|
|
539
|
+
continue
|
|
540
|
+
fi
|
|
541
|
+
|
|
542
|
+
process_issue "$number" "$title" || true
|
|
543
|
+
done
|
|
544
|
+
|
|
545
|
+
if [ ${#SOLVED_ISSUES[@]} -gt 0 ]; then
|
|
546
|
+
review_and_fix
|
|
547
|
+
create_pr
|
|
548
|
+
else
|
|
549
|
+
log_warn "No issues were solved. No PR created."
|
|
550
|
+
fi
|
|
551
|
+
|
|
552
|
+
log_header "claude-queue Complete"
|
|
553
|
+
|
|
554
|
+
local elapsed=$(( $(date +%s) - START_TIME ))
|
|
555
|
+
log "Duration: $(( elapsed / 3600 ))h $(( (elapsed % 3600) / 60 ))m $(( elapsed % 60 ))s"
|
|
556
|
+
log_success "Solved: ${#SOLVED_ISSUES[@]}"
|
|
557
|
+
if [ ${#FAILED_ISSUES[@]} -gt 0 ]; then
|
|
558
|
+
log_error "Failed: ${#FAILED_ISSUES[@]}"
|
|
559
|
+
fi
|
|
560
|
+
if [ ${#SKIPPED_ISSUES[@]} -gt 0 ]; then
|
|
561
|
+
log_warn "Skipped: ${#SKIPPED_ISSUES[@]}"
|
|
562
|
+
fi
|
|
563
|
+
log "Logs: ${LOG_DIR}"
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
create_preflight() {
|
|
567
|
+
log_header "Preflight Checks"
|
|
568
|
+
|
|
569
|
+
local failed=false
|
|
570
|
+
|
|
571
|
+
for cmd in gh claude jq; do
|
|
572
|
+
if command -v "$cmd" &>/dev/null; then
|
|
573
|
+
log " $cmd ... found"
|
|
574
|
+
else
|
|
575
|
+
log_error " $cmd ... NOT FOUND"
|
|
576
|
+
failed=true
|
|
577
|
+
fi
|
|
578
|
+
done
|
|
579
|
+
|
|
580
|
+
if ! gh auth status &>/dev/null; then
|
|
581
|
+
log_error " gh auth ... not authenticated"
|
|
582
|
+
failed=true
|
|
583
|
+
else
|
|
584
|
+
log " gh auth ... ok"
|
|
585
|
+
fi
|
|
586
|
+
|
|
587
|
+
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
|
|
588
|
+
log_error " git repo ... not inside a git repository"
|
|
589
|
+
failed=true
|
|
590
|
+
else
|
|
591
|
+
log " git repo ... ok"
|
|
592
|
+
fi
|
|
593
|
+
|
|
594
|
+
if [ "$failed" = true ]; then
|
|
595
|
+
log_error "Preflight failed. Aborting."
|
|
596
|
+
exit 1
|
|
597
|
+
fi
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
get_repo_labels() {
|
|
601
|
+
gh label list --json name -q '.[].name' 2>/dev/null | paste -sd ',' -
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
extract_json() {
|
|
605
|
+
local input="$1"
|
|
606
|
+
local json
|
|
607
|
+
|
|
608
|
+
json=$(echo "$input" | sed -n '/^```\(json\)\?$/,/^```$/{ /^```/d; p; }')
|
|
609
|
+
if [ -z "$json" ]; then
|
|
610
|
+
json="$input"
|
|
611
|
+
fi
|
|
612
|
+
|
|
613
|
+
if echo "$json" | jq empty 2>/dev/null; then
|
|
614
|
+
echo "$json"
|
|
615
|
+
return 0
|
|
616
|
+
fi
|
|
617
|
+
|
|
618
|
+
json=$(echo "$input" | grep -o '\[.*\]' | head -1)
|
|
619
|
+
if [ -n "$json" ] && echo "$json" | jq empty 2>/dev/null; then
|
|
620
|
+
echo "$json"
|
|
621
|
+
return 0
|
|
622
|
+
fi
|
|
623
|
+
|
|
624
|
+
return 1
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
create_from_text() {
|
|
628
|
+
local user_text="$1"
|
|
629
|
+
local repo_labels
|
|
630
|
+
repo_labels=$(get_repo_labels)
|
|
631
|
+
|
|
632
|
+
log "Analyzing text and generating issues..."
|
|
633
|
+
|
|
634
|
+
local prompt
|
|
635
|
+
prompt="You are a GitHub issue planner. The user wants to create issues for a repository.
|
|
636
|
+
|
|
637
|
+
Existing labels in the repo: ${repo_labels}
|
|
638
|
+
|
|
639
|
+
The user's description:
|
|
640
|
+
${user_text}
|
|
641
|
+
|
|
642
|
+
Decompose this into a JSON array of well-structured GitHub issues. Each issue should have:
|
|
643
|
+
- \"title\": a clear, concise issue title
|
|
644
|
+
- \"body\": a detailed issue body in markdown (include acceptance criteria where appropriate)
|
|
645
|
+
- \"labels\": an array of label strings (reuse existing repo labels when they fit, or suggest new ones)
|
|
646
|
+
|
|
647
|
+
Rules:
|
|
648
|
+
- Create separate issues for logically distinct tasks
|
|
649
|
+
- Each issue should be independently actionable
|
|
650
|
+
- Use clear, imperative titles (e.g. \"Add dark mode toggle to settings page\")
|
|
651
|
+
- If the description is vague, make reasonable assumptions and note them in the body
|
|
652
|
+
|
|
653
|
+
Output ONLY the JSON array, no other text."
|
|
654
|
+
|
|
655
|
+
local output
|
|
656
|
+
# shellcheck disable=SC2086
|
|
657
|
+
output=$(claude -p "$prompt" $MODEL_FLAG 2>/dev/null)
|
|
658
|
+
|
|
659
|
+
local json
|
|
660
|
+
if ! json=$(extract_json "$output"); then
|
|
661
|
+
log_error "Failed to parse Claude's response as JSON"
|
|
662
|
+
log_error "Raw output:"
|
|
663
|
+
echo "$output"
|
|
664
|
+
exit 1
|
|
665
|
+
fi
|
|
666
|
+
|
|
667
|
+
local count
|
|
668
|
+
count=$(echo "$json" | jq length)
|
|
669
|
+
if [ "$count" -eq 0 ]; then
|
|
670
|
+
log_error "No issues were generated"
|
|
671
|
+
exit 1
|
|
672
|
+
fi
|
|
673
|
+
|
|
674
|
+
echo "$json"
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
create_interactive() {
|
|
678
|
+
local repo_labels
|
|
679
|
+
repo_labels=$(get_repo_labels)
|
|
680
|
+
local conversation=""
|
|
681
|
+
local max_turns=10
|
|
682
|
+
local turn=0
|
|
683
|
+
|
|
684
|
+
local system_prompt="You are a GitHub issue planner conducting an interview to understand what issues to create for a repository.
|
|
685
|
+
|
|
686
|
+
Existing labels in the repo: ${repo_labels}
|
|
687
|
+
|
|
688
|
+
Your job:
|
|
689
|
+
1. Ask focused questions to understand what the user wants to build or fix
|
|
690
|
+
2. Ask about priorities, scope, and acceptance criteria
|
|
691
|
+
3. When you have enough information, output the marker CLAUDE_QUEUE_READY on its own line, followed by a JSON array of issues
|
|
692
|
+
|
|
693
|
+
Each issue in the JSON array should have:
|
|
694
|
+
- \"title\": a clear, concise issue title
|
|
695
|
+
- \"body\": a detailed issue body in markdown
|
|
696
|
+
- \"labels\": an array of label strings (reuse existing repo labels when they fit)
|
|
697
|
+
|
|
698
|
+
Rules:
|
|
699
|
+
- Ask one question at a time
|
|
700
|
+
- Keep questions short and specific
|
|
701
|
+
- After 2-3 questions you should have enough context — don't over-interview
|
|
702
|
+
- If the user says \"done\", immediately generate the issues with what you know
|
|
703
|
+
- Output ONLY your question text (no JSON) until you're ready to generate issues
|
|
704
|
+
- When ready, output CLAUDE_QUEUE_READY on its own line followed by ONLY the JSON array"
|
|
705
|
+
|
|
706
|
+
echo -e "${BOLD}Interactive issue creation${NC}"
|
|
707
|
+
echo -e "${DIM}Answer Claude's questions. Type 'done' to generate issues at any time.${NC}"
|
|
708
|
+
echo ""
|
|
709
|
+
|
|
710
|
+
while [ "$turn" -lt "$max_turns" ]; do
|
|
711
|
+
turn=$((turn + 1))
|
|
712
|
+
|
|
713
|
+
local prompt
|
|
714
|
+
if [ -z "$conversation" ]; then
|
|
715
|
+
prompt="${system_prompt}
|
|
716
|
+
|
|
717
|
+
Start by asking your first question."
|
|
718
|
+
else
|
|
719
|
+
prompt="${system_prompt}
|
|
720
|
+
|
|
721
|
+
Conversation so far:
|
|
722
|
+
${conversation}
|
|
723
|
+
|
|
724
|
+
Continue the interview or, if you have enough information, output CLAUDE_QUEUE_READY followed by the JSON array."
|
|
725
|
+
fi
|
|
726
|
+
|
|
727
|
+
local output
|
|
728
|
+
# shellcheck disable=SC2086
|
|
729
|
+
output=$(claude -p "$prompt" $MODEL_FLAG 2>/dev/null)
|
|
730
|
+
|
|
731
|
+
if echo "$output" | grep -q "CLAUDE_QUEUE_READY"; then
|
|
732
|
+
local json_part
|
|
733
|
+
json_part=$(echo "$output" | sed -n '/CLAUDE_QUEUE_READY/,$ p' | tail -n +2)
|
|
734
|
+
|
|
735
|
+
local json
|
|
736
|
+
if ! json=$(extract_json "$json_part"); then
|
|
737
|
+
log_error "Failed to parse generated issues as JSON"
|
|
738
|
+
exit 1
|
|
739
|
+
fi
|
|
740
|
+
|
|
741
|
+
echo "$json"
|
|
742
|
+
return 0
|
|
743
|
+
fi
|
|
744
|
+
|
|
745
|
+
echo -e "${BLUE}Claude:${NC} ${output}"
|
|
746
|
+
echo ""
|
|
747
|
+
|
|
748
|
+
local user_input
|
|
749
|
+
read -r -p "You: " user_input
|
|
750
|
+
|
|
751
|
+
if [ "$user_input" = "done" ]; then
|
|
752
|
+
conversation="${conversation}
|
|
753
|
+
Claude: ${output}
|
|
754
|
+
User: Please generate the issues now with what you know."
|
|
755
|
+
|
|
756
|
+
local final_prompt="${system_prompt}
|
|
757
|
+
|
|
758
|
+
Conversation so far:
|
|
759
|
+
${conversation}
|
|
760
|
+
|
|
761
|
+
The user wants you to generate the issues now. Output CLAUDE_QUEUE_READY followed by the JSON array."
|
|
762
|
+
|
|
763
|
+
local final_output
|
|
764
|
+
# shellcheck disable=SC2086
|
|
765
|
+
final_output=$(claude -p "$final_prompt" $MODEL_FLAG 2>/dev/null)
|
|
766
|
+
|
|
767
|
+
local final_json_part
|
|
768
|
+
final_json_part=$(echo "$final_output" | sed -n '/CLAUDE_QUEUE_READY/,$ p' | tail -n +2)
|
|
769
|
+
if [ -z "$final_json_part" ]; then
|
|
770
|
+
final_json_part="$final_output"
|
|
771
|
+
fi
|
|
772
|
+
|
|
773
|
+
local json
|
|
774
|
+
if ! json=$(extract_json "$final_json_part"); then
|
|
775
|
+
log_error "Failed to parse generated issues as JSON"
|
|
776
|
+
exit 1
|
|
777
|
+
fi
|
|
778
|
+
|
|
779
|
+
echo "$json"
|
|
780
|
+
return 0
|
|
781
|
+
fi
|
|
782
|
+
|
|
783
|
+
conversation="${conversation}
|
|
784
|
+
Claude: ${output}
|
|
785
|
+
User: ${user_input}"
|
|
786
|
+
done
|
|
787
|
+
|
|
788
|
+
log_warn "Reached maximum interview turns, generating issues with current information..."
|
|
789
|
+
|
|
790
|
+
local final_prompt="${system_prompt}
|
|
791
|
+
|
|
792
|
+
Conversation so far:
|
|
793
|
+
${conversation}
|
|
794
|
+
|
|
795
|
+
You've reached the maximum number of questions. Output CLAUDE_QUEUE_READY followed by the JSON array now."
|
|
796
|
+
|
|
797
|
+
local final_output
|
|
798
|
+
# shellcheck disable=SC2086
|
|
799
|
+
final_output=$(claude -p "$final_prompt" $MODEL_FLAG 2>/dev/null)
|
|
800
|
+
|
|
801
|
+
local final_json_part
|
|
802
|
+
final_json_part=$(echo "$final_output" | sed -n '/CLAUDE_QUEUE_READY/,$ p' | tail -n +2)
|
|
803
|
+
if [ -z "$final_json_part" ]; then
|
|
804
|
+
final_json_part="$final_output"
|
|
805
|
+
fi
|
|
806
|
+
|
|
807
|
+
local json
|
|
808
|
+
if ! json=$(extract_json "$final_json_part"); then
|
|
809
|
+
log_error "Failed to parse generated issues as JSON"
|
|
810
|
+
exit 1
|
|
811
|
+
fi
|
|
812
|
+
|
|
813
|
+
echo "$json"
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
preview_issues() {
|
|
817
|
+
local json="$1"
|
|
818
|
+
local count
|
|
819
|
+
count=$(echo "$json" | jq length)
|
|
820
|
+
|
|
821
|
+
echo ""
|
|
822
|
+
echo -e "${BOLD}═══ Issue Preview ═══${NC}"
|
|
823
|
+
echo ""
|
|
824
|
+
|
|
825
|
+
for i in $(seq 0 $((count - 1))); do
|
|
826
|
+
local title labels body
|
|
827
|
+
title=$(echo "$json" | jq -r ".[$i].title")
|
|
828
|
+
labels=$(echo "$json" | jq -r ".[$i].labels // [] | join(\", \")")
|
|
829
|
+
body=$(echo "$json" | jq -r ".[$i].body" | head -3)
|
|
830
|
+
|
|
831
|
+
echo -e " ${BOLD}$((i + 1)). ${title}${NC}"
|
|
832
|
+
if [ -n "$labels" ]; then
|
|
833
|
+
echo -e " ${DIM}Labels: ${labels}${NC}"
|
|
834
|
+
fi
|
|
835
|
+
echo -e " ${DIM}$(echo "$body" | head -1)${NC}"
|
|
836
|
+
echo ""
|
|
837
|
+
done
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
confirm_and_create() {
|
|
841
|
+
local json="$1"
|
|
842
|
+
local extra_label="$2"
|
|
843
|
+
local count
|
|
844
|
+
count=$(echo "$json" | jq length)
|
|
845
|
+
|
|
846
|
+
local prompt_text="Create ${count} issue(s)? [y/N] "
|
|
847
|
+
read -r -p "$prompt_text" confirm
|
|
848
|
+
|
|
849
|
+
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
|
850
|
+
log "Cancelled."
|
|
851
|
+
exit 0
|
|
852
|
+
fi
|
|
853
|
+
|
|
854
|
+
echo ""
|
|
855
|
+
|
|
856
|
+
for i in $(seq 0 $((count - 1))); do
|
|
857
|
+
local title body
|
|
858
|
+
title=$(echo "$json" | jq -r ".[$i].title")
|
|
859
|
+
body=$(echo "$json" | jq -r ".[$i].body")
|
|
860
|
+
|
|
861
|
+
local label_args=()
|
|
862
|
+
local issue_labels
|
|
863
|
+
issue_labels=$(echo "$json" | jq -r ".[$i].labels // [] | .[]")
|
|
864
|
+
while IFS= read -r lbl; do
|
|
865
|
+
if [ -n "$lbl" ]; then
|
|
866
|
+
label_args+=(--label "$lbl")
|
|
867
|
+
fi
|
|
868
|
+
done <<< "$issue_labels"
|
|
869
|
+
|
|
870
|
+
if [ -n "$extra_label" ]; then
|
|
871
|
+
label_args+=(--label "$extra_label")
|
|
872
|
+
fi
|
|
873
|
+
|
|
874
|
+
local issue_url
|
|
875
|
+
issue_url=$(gh issue create --title "$title" --body "$body" "${label_args[@]}" 2>&1)
|
|
876
|
+
log_success "Created: ${issue_url}"
|
|
877
|
+
done
|
|
878
|
+
|
|
879
|
+
echo ""
|
|
880
|
+
log_success "Created ${count} issue(s)"
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
cmd_create() {
|
|
884
|
+
local interactive=false
|
|
885
|
+
local extra_label=""
|
|
886
|
+
local user_text=""
|
|
887
|
+
|
|
888
|
+
while [[ $# -gt 0 ]]; do
|
|
889
|
+
case $1 in
|
|
890
|
+
-i|--interactive) interactive=true; shift ;;
|
|
891
|
+
--label) extra_label="$2"; shift 2 ;;
|
|
892
|
+
--model) MODEL_FLAG="--model $2"; shift 2 ;;
|
|
893
|
+
-h|--help) show_create_help; exit 0 ;;
|
|
894
|
+
-*) echo "Unknown option: $1"; echo ""; show_create_help; exit 1 ;;
|
|
895
|
+
*) user_text="$1"; shift ;;
|
|
896
|
+
esac
|
|
897
|
+
done
|
|
898
|
+
|
|
899
|
+
create_preflight
|
|
900
|
+
|
|
901
|
+
local json
|
|
902
|
+
|
|
903
|
+
if [ "$interactive" = true ]; then
|
|
904
|
+
json=$(create_interactive)
|
|
905
|
+
elif [ -n "$user_text" ]; then
|
|
906
|
+
json=$(create_from_text "$user_text")
|
|
907
|
+
else
|
|
908
|
+
echo -e "${BOLD}Describe what issues you want to create.${NC}"
|
|
909
|
+
echo -e "${DIM}Type or paste your text, then press Ctrl+D when done.${NC}"
|
|
910
|
+
echo ""
|
|
911
|
+
user_text=$(cat)
|
|
912
|
+
if [ -z "$user_text" ]; then
|
|
913
|
+
log_error "No input provided"
|
|
914
|
+
exit 1
|
|
915
|
+
fi
|
|
916
|
+
json=$(create_from_text "$user_text")
|
|
917
|
+
fi
|
|
918
|
+
|
|
919
|
+
preview_issues "$json"
|
|
920
|
+
confirm_and_create "$json" "$extra_label"
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
# --- Subcommand routing ---
|
|
924
|
+
|
|
925
|
+
SUBCOMMAND=""
|
|
926
|
+
if [[ $# -gt 0 ]] && [[ "$1" != -* ]]; then
|
|
927
|
+
SUBCOMMAND="$1"; shift
|
|
928
|
+
fi
|
|
929
|
+
|
|
930
|
+
case "$SUBCOMMAND" in
|
|
931
|
+
"")
|
|
932
|
+
while [[ $# -gt 0 ]]; do
|
|
933
|
+
case $1 in
|
|
934
|
+
--max-retries) MAX_RETRIES="$2"; shift 2 ;;
|
|
935
|
+
--max-turns) MAX_TURNS="$2"; shift 2 ;;
|
|
936
|
+
--label) ISSUE_FILTER="$2"; shift 2 ;;
|
|
937
|
+
--model) MODEL_FLAG="--model $2"; shift 2 ;;
|
|
938
|
+
-v|--version) echo "claude-queue v${VERSION}"; exit 0 ;;
|
|
939
|
+
-h|--help) show_help; exit 0 ;;
|
|
940
|
+
*) echo "Unknown option: $1"; exit 1 ;;
|
|
941
|
+
esac
|
|
942
|
+
done
|
|
943
|
+
main
|
|
944
|
+
;;
|
|
945
|
+
create)
|
|
946
|
+
cmd_create "$@"
|
|
947
|
+
;;
|
|
948
|
+
*)
|
|
949
|
+
echo "Unknown command: $SUBCOMMAND"
|
|
950
|
+
echo ""
|
|
951
|
+
show_help
|
|
952
|
+
exit 1
|
|
953
|
+
;;
|
|
954
|
+
esac
|