bueller-wheel 0.3.1 → 0.4.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/dist/index.js +32 -1
- package/dist/issue-reader.js +4 -38
- package/dist/issue-summarize.js +19 -19
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
3
|
import * as fs from 'node:fs/promises';
|
|
4
|
+
import * as os from 'node:os';
|
|
4
5
|
import * as path from 'node:path';
|
|
5
6
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
6
7
|
import { expandMessages, formatIssueSummary, resolveIssueReference, summarizeIssue, } from './issue-summarize.js';
|
|
@@ -33,6 +34,7 @@ OPTIONS:
|
|
|
33
34
|
--git Enable automatic git commits (on by default, run command only)
|
|
34
35
|
--no-git Disable automatic git commits (run command only)
|
|
35
36
|
--max N Maximum number of iterations to run (default: 25, run command only)
|
|
37
|
+
--model MODEL Model to use (e.g., opus, sonnet, haiku, or full model ID)
|
|
36
38
|
--continue [PROMPT] Continue from previous session (default prompt: "continue", run command only)
|
|
37
39
|
--index N Expand message at index N (issue command only)
|
|
38
40
|
--index M,N Expand message range from M to N (issue command only)
|
|
@@ -88,6 +90,7 @@ function parseArgs() {
|
|
|
88
90
|
'--issues-dir',
|
|
89
91
|
'--faq-dir',
|
|
90
92
|
'--max',
|
|
93
|
+
'--model',
|
|
91
94
|
'--git',
|
|
92
95
|
'--no-git',
|
|
93
96
|
'--prompt',
|
|
@@ -116,6 +119,7 @@ function parseArgs() {
|
|
|
116
119
|
let continuePrompt = 'continue';
|
|
117
120
|
const issueReferences = [];
|
|
118
121
|
let issueIndex;
|
|
122
|
+
let model;
|
|
119
123
|
// Parse arguments starting from index 1 (skip the command)
|
|
120
124
|
for (let i = 1; i < args.length; i++) {
|
|
121
125
|
if (args[i] === '--issues-dir' && i + 1 < args.length) {
|
|
@@ -131,6 +135,9 @@ function parseArgs() {
|
|
|
131
135
|
else if (args[i] === '--max' && i + 1 < args.length) {
|
|
132
136
|
maxIterations = parseInt(args[++i], 10);
|
|
133
137
|
}
|
|
138
|
+
else if (args[i] === '--model' && i + 1 < args.length) {
|
|
139
|
+
model = args[++i];
|
|
140
|
+
}
|
|
134
141
|
else if (args[i] === '--git') {
|
|
135
142
|
gitCommit = true;
|
|
136
143
|
}
|
|
@@ -174,8 +181,21 @@ function parseArgs() {
|
|
|
174
181
|
command: command,
|
|
175
182
|
issueReferences,
|
|
176
183
|
issueIndex,
|
|
184
|
+
model,
|
|
177
185
|
};
|
|
178
186
|
}
|
|
187
|
+
async function getModelFromUserSettings() {
|
|
188
|
+
try {
|
|
189
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
190
|
+
const content = await fs.readFile(settingsPath, 'utf-8');
|
|
191
|
+
const settings = JSON.parse(content);
|
|
192
|
+
return settings.model;
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
// File doesn't exist or is invalid JSON - fail gracefully
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
179
199
|
async function ensureDirectories(issuesDir, faqDir) {
|
|
180
200
|
const dirs = [
|
|
181
201
|
issuesDir,
|
|
@@ -434,6 +454,12 @@ function logToolUse(block) {
|
|
|
434
454
|
}
|
|
435
455
|
function logSDKMessage(item) {
|
|
436
456
|
switch (item.type) {
|
|
457
|
+
case 'system':
|
|
458
|
+
if (item.subtype === 'init') {
|
|
459
|
+
process.stdout.write(`Model: ${item.model}\n`);
|
|
460
|
+
process.stdout.write(`Agents: ${item.agents?.join(',')}\n`);
|
|
461
|
+
}
|
|
462
|
+
break;
|
|
437
463
|
case 'assistant':
|
|
438
464
|
case 'user':
|
|
439
465
|
for (const chunk of item.message.content) {
|
|
@@ -462,7 +488,7 @@ function logSDKMessage(item) {
|
|
|
462
488
|
}
|
|
463
489
|
}
|
|
464
490
|
async function runAgent(options) {
|
|
465
|
-
const { template, issuesDir, faqDir, issueFile, continueMode, continuePrompt } = options;
|
|
491
|
+
const { template, issuesDir, faqDir, issueFile, continueMode, continuePrompt, model } = options;
|
|
466
492
|
const systemPrompt = buildSystemPrompt(template, issuesDir, faqDir, issueFile);
|
|
467
493
|
console.log(`${colors.blue}\n--- Starting agent ---${colors.reset}`);
|
|
468
494
|
const stream = query({
|
|
@@ -471,6 +497,7 @@ async function runAgent(options) {
|
|
|
471
497
|
settingSources: ['local', 'project', 'user'],
|
|
472
498
|
permissionMode: 'acceptEdits',
|
|
473
499
|
continue: continueMode,
|
|
500
|
+
...(model && { model }),
|
|
474
501
|
canUseTool: async (toolName, input) => {
|
|
475
502
|
console.log(`${colors.red}Auto-denied tool:${colors.reset} ${toolName} ${String(input?.['command'] ?? input?.['file_path'] ?? '')}`);
|
|
476
503
|
return {
|
|
@@ -518,12 +545,15 @@ async function main() {
|
|
|
518
545
|
await runIssue(config);
|
|
519
546
|
return;
|
|
520
547
|
}
|
|
548
|
+
// If no model specified via CLI, try to load from user settings
|
|
549
|
+
const model = config.model ?? (await getModelFromUserSettings());
|
|
521
550
|
console.log(`${colors.cyan}Bueller? Bueller?${colors.reset}`);
|
|
522
551
|
console.log(`${colors.cyan}-----------------${colors.reset}`);
|
|
523
552
|
console.log(`Issues directory: ${config.issuesDir}`);
|
|
524
553
|
console.log(`FAQ directory: ${config.faqDir}`);
|
|
525
554
|
console.log(`Max iterations: ${config.maxIterations}`);
|
|
526
555
|
console.log(`Git auto-commit: ${config.gitCommit ? 'enabled' : 'disabled'}`);
|
|
556
|
+
console.log(`Model: ${model ?? '(default)'}`);
|
|
527
557
|
console.log(`Prompt file: ${config.promptFile}`);
|
|
528
558
|
if (config.continueMode) {
|
|
529
559
|
console.log(`Continue mode: enabled (prompt: "${config.continuePrompt}")`);
|
|
@@ -552,6 +582,7 @@ async function main() {
|
|
|
552
582
|
issueFile: currentIssue,
|
|
553
583
|
continueMode: config.continueMode && isFirstIteration,
|
|
554
584
|
continuePrompt: config.continuePrompt,
|
|
585
|
+
model,
|
|
555
586
|
});
|
|
556
587
|
// Auto-commit if enabled and there's a current issue
|
|
557
588
|
if (config.gitCommit && currentIssue) {
|
package/dist/issue-reader.js
CHANGED
|
@@ -33,24 +33,10 @@ export function parseIssueContent(content) {
|
|
|
33
33
|
if (!trimmedSection) {
|
|
34
34
|
continue;
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
messages.push({
|
|
41
|
-
index: messageIndex++,
|
|
42
|
-
author: 'user',
|
|
43
|
-
content: userMatch[1].trim(),
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
else if (claudeMatch) {
|
|
47
|
-
messages.push({
|
|
48
|
-
index: messageIndex++,
|
|
49
|
-
author: 'claude',
|
|
50
|
-
content: claudeMatch[1].trim(),
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
// If no match, skip this section (handles malformed sections)
|
|
36
|
+
messages.push({
|
|
37
|
+
index: messageIndex++,
|
|
38
|
+
content: trimmedSection,
|
|
39
|
+
});
|
|
54
40
|
}
|
|
55
41
|
return {
|
|
56
42
|
messages,
|
|
@@ -69,24 +55,4 @@ export function getLatestMessage(issue) {
|
|
|
69
55
|
}
|
|
70
56
|
return issue.messages[issue.messages.length - 1];
|
|
71
57
|
}
|
|
72
|
-
/**
|
|
73
|
-
* Gets all messages from a specific author
|
|
74
|
-
*
|
|
75
|
-
* @param issue - Parsed issue object
|
|
76
|
-
* @param author - Author to filter by ('user' or 'claude')
|
|
77
|
-
* @returns Array of messages from the specified author
|
|
78
|
-
*/
|
|
79
|
-
export function getMessagesByAuthor(issue, author) {
|
|
80
|
-
return issue.messages.filter((msg) => msg.author === author);
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Formats a message for appending to an issue file
|
|
84
|
-
*
|
|
85
|
-
* @param author - Author of the message ('user' or 'claude')
|
|
86
|
-
* @param content - Content of the message
|
|
87
|
-
* @returns Formatted message string ready to append to an issue file
|
|
88
|
-
*/
|
|
89
|
-
export function formatMessage(author, content) {
|
|
90
|
-
return `---\n\n@${author}: ${content}`;
|
|
91
|
-
}
|
|
92
58
|
//# sourceMappingURL=issue-reader.js.map
|
package/dist/issue-summarize.js
CHANGED
|
@@ -65,6 +65,19 @@ export async function resolveIssueReference(reference, issuesDir) {
|
|
|
65
65
|
// Otherwise, treat it as a filename and search for it
|
|
66
66
|
return locateIssueFile(reference, issuesDir);
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Condenses text by trimming lines and replacing newlines with single spaces
|
|
70
|
+
*
|
|
71
|
+
* @param text - Text to condense
|
|
72
|
+
* @returns Condensed text
|
|
73
|
+
*/
|
|
74
|
+
function condenseText(text) {
|
|
75
|
+
return text
|
|
76
|
+
.split('\n')
|
|
77
|
+
.map((line) => line.trim())
|
|
78
|
+
.filter((line) => line.length > 0)
|
|
79
|
+
.join(' ');
|
|
80
|
+
}
|
|
68
81
|
/**
|
|
69
82
|
* Abbreviates a message based on its position in the conversation
|
|
70
83
|
*
|
|
@@ -75,15 +88,16 @@ export async function resolveIssueReference(reference, issuesDir) {
|
|
|
75
88
|
*/
|
|
76
89
|
function abbreviateMessage(message, _position, maxLength) {
|
|
77
90
|
const fullContent = message.content;
|
|
78
|
-
|
|
91
|
+
// Condense the content for abbreviation (replace newlines with spaces)
|
|
92
|
+
const condensed = condenseText(fullContent);
|
|
93
|
+
let abbreviated = condensed;
|
|
79
94
|
let isAbbreviated = false;
|
|
80
|
-
if (
|
|
81
|
-
abbreviated =
|
|
95
|
+
if (condensed.length > maxLength) {
|
|
96
|
+
abbreviated = condensed.substring(0, maxLength).trimEnd() + '…';
|
|
82
97
|
isAbbreviated = true;
|
|
83
98
|
}
|
|
84
99
|
return {
|
|
85
100
|
index: message.index,
|
|
86
|
-
author: message.author,
|
|
87
101
|
content: abbreviated,
|
|
88
102
|
isAbbreviated,
|
|
89
103
|
fullContent,
|
|
@@ -191,19 +205,6 @@ export function expandMessages(summary, indexSpec) {
|
|
|
191
205
|
isSingleIndex,
|
|
192
206
|
};
|
|
193
207
|
}
|
|
194
|
-
/**
|
|
195
|
-
* Condenses text by trimming lines and replacing newlines with single spaces
|
|
196
|
-
*
|
|
197
|
-
* @param text - Text to condense
|
|
198
|
-
* @returns Condensed text
|
|
199
|
-
*/
|
|
200
|
-
function condenseText(text) {
|
|
201
|
-
return text
|
|
202
|
-
.split('\n')
|
|
203
|
-
.map((line) => line.trim())
|
|
204
|
-
.filter((line) => line.length > 0)
|
|
205
|
-
.join(' ');
|
|
206
|
-
}
|
|
207
208
|
/**
|
|
208
209
|
* Formats an issue summary for console output
|
|
209
210
|
*
|
|
@@ -223,8 +224,7 @@ export function formatIssueSummary(summary, indexSpec) {
|
|
|
223
224
|
: summary.abbreviatedMessages;
|
|
224
225
|
// Messages
|
|
225
226
|
for (const msg of messagesToShow) {
|
|
226
|
-
|
|
227
|
-
lines.push(`[${msg.index}] @${msg.author}: ${content}`);
|
|
227
|
+
lines.push(`[${msg.index}] ${msg.content}`);
|
|
228
228
|
}
|
|
229
229
|
// Add follow-up action hint if not showing specific indices
|
|
230
230
|
if (!indexSpec || !summary.isSingleIndex) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bueller-wheel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Headless Claude Code issue processor - A wrapper that runs Claude Code in a loop to process issues from a directory queue",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -64,6 +64,6 @@
|
|
|
64
64
|
"typescript-eslint": "^8.47.0"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@anthropic-ai/claude-agent-sdk": "^0.1.
|
|
67
|
+
"@anthropic-ai/claude-agent-sdk": "^0.1.76"
|
|
68
68
|
}
|
|
69
69
|
}
|