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 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) {
@@ -33,24 +33,10 @@ export function parseIssueContent(content) {
33
33
  if (!trimmedSection) {
34
34
  continue;
35
35
  }
36
- // Check if this section starts with @user: or @claude:
37
- const userMatch = trimmedSection.match(/^@user:\s*([\s\S]*)$/);
38
- const claudeMatch = trimmedSection.match(/^@claude:\s*([\s\S]*)$/);
39
- if (userMatch) {
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
@@ -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
- let abbreviated = fullContent;
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 (fullContent.length > maxLength) {
81
- abbreviated = fullContent.substring(0, maxLength).trimEnd() + '…';
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
- const content = msg.isAbbreviated ? condenseText(msg.content) : msg.content;
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.1",
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.55"
67
+ "@anthropic-ai/claude-agent-sdk": "^0.1.76"
68
68
  }
69
69
  }