@link-assistant/hive-mind 1.34.1 → 1.34.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.34.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 22a8868: Fail fast when API signals x-should-retry: false and retries make no progress (Issue #1437). Increase minimum retry delay to 2 minutes.
8
+
9
+ When the Anthropic API returns HTTP 500 with `x-should-retry: false` AND subsequent retries immediately fail with `num_turns <= 1`, the outer retry loop now exits early instead of waiting through up to 10 retries with exponential backoff. This prevents stuck sessions where recovery is impossible.
10
+
11
+ Two new signals are tracked: (1) `apiMarkedNotRetryable` — set when `ANTHROPIC_LOG=debug` stderr contains `"error; not retryable"` or `x-should-retry: false`; (2) `resultNumTurns` — captured from the result event to detect sessions that failed immediately on resume. If both conditions are met after `HIVE_MIND_MAX_NOT_RETRYABLE_ATTEMPTS` (default: 5) retry attempts, the loop fails fast with a clear error message instead of continuing indefinitely.
12
+
13
+ The minimum retry delay for transient API errors (Overloaded, 503, Internal Server Error) is increased from 1 minute to 2 minutes (`HIVE_MIND_INITIAL_TRANSIENT_ERROR_DELAY_MS`), giving the API more time to recover between retries.
14
+
15
+ ## 1.34.2
16
+
17
+ ### Patch Changes
18
+
19
+ - dc92237: Set `opus` alias to target Opus 4.6 instead of Opus 4.5 (Issue #1433). Opus 4.6 offers a 1M token context window and comparable cost efficiency. The `isOpus46OrLater` function is updated to recognise the `opus` alias directly so Opus 4.6 features (128K output tokens, effort-level thinking) are applied automatically when using the default alias.
20
+
3
21
  ## 1.34.1
4
22
 
5
23
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.34.1",
3
+ "version": "1.34.3",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -841,6 +841,8 @@ export const executeClaudeCommand = async params => {
841
841
  let is503Error = false;
842
842
  let isInternalServerError = false; // Issue #1331: Track 500 Internal server error
843
843
  let isRequestTimeout = false; // Issue #1353: Track "Request timed out" from Claude CLI
844
+ let apiMarkedNotRetryable = false; // Issue #1437: Track when API explicitly signals x-should-retry: false
845
+ let resultNumTurns = 0; // Issue #1437: Track num_turns from result event to detect stuck retries
844
846
  let stderrErrors = [];
845
847
  let resultSuccessReceived = false; // Issue #1354: Track if result success event was received
846
848
  let anthropicTotalCostUSD = null; // Capture Anthropic's official total_cost_usd from result
@@ -881,14 +883,10 @@ export const executeClaudeCommand = async params => {
881
883
  try {
882
884
  // Resolve thinking settings (see issue #1146)
883
885
  const { thinkingBudget: resolvedThinkingBudget, thinkLevel, isNewVersion, maxBudget } = await resolveThinkingSettings(argv, log);
884
- // Set CLAUDE_CODE_MAX_OUTPUT_TOKENS (see issue #1076), MAX_THINKING_TOKENS (see issue #1146),
885
- // MCP timeout configurations (see issue #1066), and CLAUDE_CODE_EFFORT_LEVEL for Opus 4.6 (Issue #1238)
886
- // Pass model for model-specific max output tokens (Issue #1221)
887
- // Pass thinkLevel and maxBudget for Opus 4.6 effort level conversion (Issue #1238)
886
+ // Set CLAUDE_CODE_MAX_OUTPUT_TOKENS (#1076), MAX_THINKING_TOKENS (#1146), MCP timeout (#1066),
887
+ // CLAUDE_CODE_EFFORT_LEVEL (#1238), model/thinkLevel/maxBudget for effort conversion (#1221, #1238)
888
888
  const claudeEnv = getClaudeEnv({ thinkingBudget: resolvedThinkingBudget, model: mappedModel, thinkLevel, maxBudget });
889
- // Issue #1337: Enable ANTHROPIC_LOG=debug in --verbose mode to diagnose slow API requests.
890
- // The BashTool pre-flight check suggests "Run with ANTHROPIC_LOG=debug to check for failed or slow API requests."
891
- // When --verbose is enabled, we propagate ANTHROPIC_LOG=debug so users can see detailed API request info.
889
+ // Issue #1337: Enable ANTHROPIC_LOG=debug in --verbose mode for detailed API request diagnostics.
892
890
  if (argv.verbose) {
893
891
  claudeEnv.ANTHROPIC_LOG = 'debug';
894
892
  }
@@ -923,14 +921,9 @@ export const executeClaudeCommand = async params => {
923
921
  // Issue #1183: Line buffer for NDJSON stream parsing - accumulate incomplete lines across chunks
924
922
  // Long JSON messages (e.g., result with total_cost_usd) may be split across multiple stdout chunks
925
923
  let stdoutLineBuffer = '';
926
- // Issue #1280: Track result event and timeout for hung processes
927
- // Root cause: command-stream's stream() async iterator waits for BOTH process exit AND
928
- // stdout/stderr pipe close before emitting 'end'. If the CLI process keeps stdout open after
929
- // sending the result event, pumpReadable() hangs → finish() never fires → stream never ends.
930
- // Additionally, command-stream v0.9.4 does NOT yield {type:'exit'} chunks from stream(),
931
- // so the exit code detection via chunk.type==='exit' below is dead code.
932
- // Workaround: after receiving the result event, start a timeout to force-kill the process.
933
- // See: https://github.com/link-foundation/command-stream/issues/155
924
+ // Issue #1280: Track result event and timeout for hung processes.
925
+ // command-stream's stream() waits for BOTH process exit AND stdout pipe close; if stdout stays open
926
+ // the stream hangs. Workaround: force-kill after result event. See command-stream/issues/155
934
927
  let resultEventReceived = false;
935
928
  let resultTimeoutId = null;
936
929
  let forceExitTriggered = false;
@@ -1025,12 +1018,16 @@ export const executeClaudeCommand = async params => {
1025
1018
  } else if (data.total_cost_usd !== undefined && data.total_cost_usd !== null) {
1026
1019
  await log(`💰 Anthropic cost from ${data.subtype || 'unknown'} result ignored: $${data.total_cost_usd.toFixed(6)}`, { verbose: true });
1027
1020
  }
1028
- // Issue #1263: Extract result summary for --attach-solution-summary and --auto-attach-solution-summary
1029
- // The result field contains the AI's summary of the work done
1021
+ // Issue #1263: Extract result summary (AI's summary of work done) for --attach-solution-summary
1030
1022
  if (data.subtype === 'success' && data.result && typeof data.result === 'string') {
1031
1023
  resultSummary = data.result;
1032
1024
  await log('📝 Captured result summary from Claude output', { verbose: true });
1033
1025
  }
1026
+ // Issue #1437: Capture num_turns to detect stuck retries (degrading turn count signals non-recovery)
1027
+ if (data.num_turns !== undefined) {
1028
+ resultNumTurns = data.num_turns;
1029
+ await log(`📊 Session num_turns: ${resultNumTurns}`, { verbose: true });
1030
+ }
1034
1031
  if (data.is_error === true) {
1035
1032
  lastMessage = data.result || JSON.stringify(data);
1036
1033
  const subtype = data.subtype || 'unknown';
@@ -1111,10 +1108,7 @@ export const executeClaudeCommand = async params => {
1111
1108
  await log(line, { stream: 'raw' });
1112
1109
  lastMessage = line;
1113
1110
 
1114
- // Detect Claude Code terms acceptance message (Issue #1015)
1115
- // When Claude CLI requires terms acceptance, it outputs a non-JSON message like:
1116
- // "[ACTION REQUIRED] An update to our Consumer Terms and Privacy Policy has taken effect..."
1117
- // This should be treated as an error requiring human intervention, not success
1111
+ // Issue #1015: Detect terms acceptance prompt (non-JSON "[ACTION REQUIRED]..." message)
1118
1112
  const termsAcceptancePattern = /\[ACTION REQUIRED\].*terms|must run.*claude.*review.*terms/i;
1119
1113
  if (termsAcceptancePattern.test(line)) {
1120
1114
  commandFailed = true;
@@ -1129,11 +1123,16 @@ export const executeClaudeCommand = async params => {
1129
1123
  // Log stderr immediately
1130
1124
  if (errorOutput) {
1131
1125
  await log(errorOutput, { stream: 'stderr' });
1132
- // Issue #1354: Split multi-line stderr chunks and check each line individually.
1133
- // A single chunk may contain multiple newline-separated JSON messages (e.g. two
1134
- // consecutive {"level":"warn",...} lines). Passing the whole chunk to isStderrError()
1135
- // causes JSON.parse() to fail (multi-object is not valid JSON), falling through to
1136
- // keyword matching and producing false positives on words like "failed".
1126
+ // Issue #1437: Detect x-should-retry: false in ANTHROPIC_LOG=debug output signals
1127
+ // a non-transient error; fail fast instead of blindly retrying.
1128
+ if (errorOutput.includes('not retryable') || errorOutput.includes("'x-should-retry': 'false'") || errorOutput.includes('"x-should-retry": "false"')) {
1129
+ if (!apiMarkedNotRetryable) {
1130
+ apiMarkedNotRetryable = true;
1131
+ await log('⚠️ API signaled error is not retryable (x-should-retry: false)', { verbose: true });
1132
+ }
1133
+ }
1134
+ // Issue #1354: Split multi-line chunks — a chunk may contain multiple JSON messages;
1135
+ // passing the whole chunk to isStderrError() causes JSON.parse() to fail.
1137
1136
  for (const line of errorOutput.split('\n')) {
1138
1137
  if (isStderrError(line)) {
1139
1138
  stderrErrors.push(line.trim());
@@ -1141,9 +1140,7 @@ export const executeClaudeCommand = async params => {
1141
1140
  }
1142
1141
  }
1143
1142
  } else if (chunk.type === 'exit') {
1144
- // Note: command-stream v0.9.4 stream() does NOT yield exit chunks (Issue #1280).
1145
- // Exit code is obtained from execCommand.result.code after the loop.
1146
- // This branch is kept for forward-compatibility if command-stream adds exit chunks.
1143
+ // Note: command-stream v0.9.4 stream() does NOT yield exit chunks (Issue #1280) — kept for forward-compat.
1147
1144
  exitCode = chunk.code;
1148
1145
  if (chunk.code !== 0) {
1149
1146
  commandFailed = true;
@@ -1172,9 +1169,7 @@ export const executeClaudeCommand = async params => {
1172
1169
  await log('✅ Stream closed normally after result event', { verbose: true });
1173
1170
  }
1174
1171
  }
1175
- // Issue #1165: Check actual exit code from command result for more reliable detection
1176
- // The .stream() method may not emit 'exit' chunks, but the command object still tracks the exit code
1177
- // Exit code 127 is the standard Unix convention for "command not found"
1172
+ // Issue #1165: Check actual exit code from command result (stream() may not emit 'exit' chunks)
1178
1173
  if (execCommand.result && typeof execCommand.result.code === 'number') {
1179
1174
  const resultExitCode = execCommand.result.code;
1180
1175
  if (exitCode === 0 && resultExitCode !== 0) {
@@ -1197,20 +1192,39 @@ export const executeClaudeCommand = async params => {
1197
1192
  }
1198
1193
  }
1199
1194
 
1200
- // Issue #1331: Unified handler for all transient API errors (Overloaded, 503, Internal Server Error)
1201
- // Issue #1353: Also handle "Request timed out" Claude CLI times out after exhausting its own retries
1202
- // All use exponential backoff with session preservation via --resume
1195
+ // Issues #1331, #1353: Unified handler for transient API errors (Overloaded, 503, Internal Server Error,
1196
+ // Request timed out). All use exponential backoff with session preservation via --resume.
1203
1197
  const isTransientError = isOverloadError || isInternalServerError || is503Error || isRequestTimeout || (lastMessage.includes('API Error: 500') && (lastMessage.includes('Overloaded') || lastMessage.includes('Internal server error'))) || (lastMessage.includes('api_error') && lastMessage.includes('Overloaded')) || lastMessage.includes('API Error: 503') || (lastMessage.includes('503') && (lastMessage.includes('upstream connect error') || lastMessage.includes('remote connection failure'))) || lastMessage === 'Request timed out' || lastMessage.includes('Request timed out');
1204
1198
  if ((commandFailed || isTransientError) && isTransientError) {
1205
- // Issue #1353: Use timeout-specific backoff params (5min–1hr) vs general transient params (1min–30min)
1206
- // Timeouts indicate network instability — Claude CLI already exhausted its own retries, so we need longer waits
1199
+ // Issue #1353: Timeouts use longer backoff (5min–1hr) vs general transient (2min–30min)
1207
1200
  const maxRetries = isRequestTimeout ? retryLimits.maxRequestTimeoutRetries : retryLimits.maxTransientErrorRetries;
1208
1201
  const initialDelay = isRequestTimeout ? retryLimits.initialRequestTimeoutDelayMs : retryLimits.initialTransientErrorDelayMs;
1209
1202
  const maxDelay = isRequestTimeout ? retryLimits.maxRequestTimeoutDelayMs : retryLimits.maxTransientErrorDelayMs;
1203
+ // Issue #1437: Fail fast when API signals x-should-retry: false AND session made no progress
1204
+ // (num_turns <= 1). Allow maxNotRetryableAttempts before giving up (signal can be wrong sometimes).
1205
+ const isStuckRetry = apiMarkedNotRetryable && retryCount >= retryLimits.maxNotRetryableAttempts && resultNumTurns <= 1;
1206
+ if (isStuckRetry) {
1207
+ await log(`\n\n❌ API explicitly marked error as not retryable (x-should-retry: false) and session made no progress (num_turns=${resultNumTurns}) after ${retryCount} attempt(s)`, { level: 'error' });
1208
+ await log(` This error is not recoverable. Failing fast to avoid a stuck retry loop (Issue #1437).`, { level: 'error' });
1209
+ await log(` Check https://status.anthropic.com/ for API status.`, { level: 'error' });
1210
+ return {
1211
+ success: false,
1212
+ sessionId,
1213
+ limitReached: false,
1214
+ limitResetTime: null,
1215
+ limitTimezone: null,
1216
+ messageCount,
1217
+ toolUseCount,
1218
+ is503Error,
1219
+ anthropicTotalCostUSD,
1220
+ resultSummary,
1221
+ };
1222
+ }
1210
1223
  if (retryCount < maxRetries) {
1211
1224
  const delay = Math.min(initialDelay * Math.pow(retryLimits.retryBackoffMultiplier, retryCount), maxDelay);
1212
1225
  const errorLabel = isRequestTimeout ? 'Request timeout' : isOverloadError || (lastMessage.includes('API Error: 500') && lastMessage.includes('Overloaded')) ? 'API overload (500)' : isInternalServerError || lastMessage.includes('Internal server error') ? 'Internal server error (500)' : '503 network error';
1213
- await log(`\n⚠️ ${errorLabel} detected. Retry ${retryCount + 1}/${maxRetries} in ${Math.round(delay / 60000)} min (session preserved)...`, { level: 'warning' });
1226
+ const notRetryableHint = apiMarkedNotRetryable ? ' (API says not retryable will stop early if no progress)' : '';
1227
+ await log(`\n⚠️ ${errorLabel} detected. Retry ${retryCount + 1}/${maxRetries} in ${Math.round(delay / 60000)} min (session preserved)${notRetryableHint}...`, { level: 'warning' });
1214
1228
  await log(` Error: ${lastMessage.substring(0, 200)}`, { verbose: true });
1215
1229
  if (sessionId && !argv.resume) argv.resume = sessionId; // preserve session for resume
1216
1230
  await waitWithCountdown(delay, log);
@@ -1263,9 +1277,8 @@ export const executeClaudeCommand = async params => {
1263
1277
  }
1264
1278
  }
1265
1279
  }
1266
- // Additional failure detection: silent failures (no messages + stderr errors).
1267
- // E.g., sudo timeout causing "kill EPERM" stderr error but exit code 0.
1268
- // Issue #1354: Skip if result event confirmed success (definitive proof regardless of messageCount).
1280
+ // Issue #1354: Detect silent failures (no messages + stderr errors, e.g. "kill EPERM" with exit 0).
1281
+ // Skip if result event confirmed success (definitive proof regardless of messageCount).
1269
1282
  if (!commandFailed && !resultSuccessReceived && stderrErrors.length > 0 && messageCount === 0 && toolUseCount === 0) {
1270
1283
  commandFailed = true;
1271
1284
  const errorsPreview = stderrErrors
@@ -95,7 +95,7 @@ export const systemLimits = {
95
95
 
96
96
  // Retry configurations
97
97
  // Issue #1331: All API error types use unified retry parameters:
98
- // 10 max retries, 1 minute initial delay, 30 minute max delay (exponential backoff), session preserved
98
+ // 10 max retries, 2 minute initial delay, 30 minute max delay (exponential backoff), session preserved
99
99
  export const retryLimits = {
100
100
  maxForkRetries: parseIntWithDefault('HIVE_MIND_MAX_FORK_RETRIES', 5),
101
101
  maxVerifyRetries: parseIntWithDefault('HIVE_MIND_MAX_VERIFY_RETRIES', 5),
@@ -103,19 +103,25 @@ export const retryLimits = {
103
103
  retryBackoffMultiplier: parseFloatWithDefault('HIVE_MIND_RETRY_BACKOFF_MULTIPLIER', 2),
104
104
  // Unified retry config for all transient API errors (Overloaded, 503, Internal Server Error)
105
105
  maxTransientErrorRetries: parseIntWithDefault('HIVE_MIND_MAX_TRANSIENT_ERROR_RETRIES', 10),
106
- initialTransientErrorDelayMs: parseIntWithDefault('HIVE_MIND_INITIAL_TRANSIENT_ERROR_DELAY_MS', 60 * 1000), // 1 minute
106
+ initialTransientErrorDelayMs: parseIntWithDefault('HIVE_MIND_INITIAL_TRANSIENT_ERROR_DELAY_MS', 2 * 60 * 1000), // 2 minutes
107
107
  maxTransientErrorDelayMs: parseIntWithDefault('HIVE_MIND_MAX_TRANSIENT_ERROR_DELAY_MS', 30 * 60 * 1000), // 30 minutes
108
108
  // Request timeout retry configuration (Issue #1353)
109
109
  // Network timeouts need longer waits than API errors — Claude CLI already exhausted its own retries
110
110
  maxRequestTimeoutRetries: parseIntWithDefault('HIVE_MIND_MAX_REQUEST_TIMEOUT_RETRIES', 10),
111
111
  initialRequestTimeoutDelayMs: parseIntWithDefault('HIVE_MIND_INITIAL_REQUEST_TIMEOUT_DELAY_MS', 5 * 60 * 1000), // 5 minutes
112
112
  maxRequestTimeoutDelayMs: parseIntWithDefault('HIVE_MIND_MAX_REQUEST_TIMEOUT_DELAY_MS', 60 * 60 * 1000), // 1 hour
113
+ // Not-retryable error fail-fast configuration (Issue #1437)
114
+ // When the API sends x-should-retry: false AND retries make no progress (num_turns <= 1),
115
+ // stop retrying after this many attempts to avoid a stuck loop with no recovery prospects.
116
+ // Default: 5 — retry generously even when API signals not retryable, since the signal can be wrong
117
+ // for transient backend glitches (e.g. overloaded errors observed as non-retryable 500s).
118
+ maxNotRetryableAttempts: parseIntWithDefault('HIVE_MIND_MAX_NOT_RETRYABLE_ATTEMPTS', 5),
113
119
  };
114
120
 
115
121
  // Claude Code CLI configurations
116
122
  // See: https://github.com/link-assistant/hive-mind/issues/1076
117
123
  // Claude models support different max output tokens:
118
- // - Opus 4.6: 128K tokens (Issue #1221)
124
+ // - Opus 4.6 (default 'opus' alias): 128K tokens (Issue #1221, Issue #1433)
119
125
  // - Sonnet 4.5, Opus 4.5, Haiku 4.5: 64K tokens
120
126
  // Setting a higher limit allows Claude to generate longer responses without hitting the limit
121
127
  export const claudeCode = {
@@ -158,8 +164,8 @@ export const isOpus46OrLater = model => {
158
164
  if (!model) return false;
159
165
  const normalizedModel = model.toLowerCase();
160
166
  // Check for explicit opus-4-6 or later versions
161
- // Note: The 'opus' alias now maps to Opus 4.5 (Issue #1238), so we only check explicit version identifiers
162
- return normalizedModel.includes('opus-4-6') || normalizedModel.includes('opus-4-7') || normalizedModel.includes('opus-5');
167
+ // Note: The 'opus' alias now maps to Opus 4.6 (Issue #1433), so we also check for the alias directly
168
+ return normalizedModel === 'opus' || normalizedModel.includes('opus-4-6') || normalizedModel.includes('opus-4-7') || normalizedModel.includes('opus-5');
163
169
  };
164
170
 
165
171
  /**
@@ -259,7 +259,7 @@ export const resolveModelId = (requestedModel, tool) => {
259
259
  const modelMaps = {
260
260
  claude: {
261
261
  sonnet: 'claude-sonnet-4-6',
262
- opus: 'claude-opus-4-5-20251101',
262
+ opus: 'claude-opus-4-6',
263
263
  haiku: 'claude-haiku-4-5-20251001',
264
264
  'opus-4-6': 'claude-opus-4-6',
265
265
  'opus-4-5': 'claude-opus-4-5-20251101',
@@ -6,10 +6,10 @@
6
6
  */
7
7
 
8
8
  // Claude models (Anthropic API)
9
- // Updated for Opus 4.5/4.6 and Sonnet 4.6 support (Issue #1221, Issue #1238, Issue #1329)
9
+ // Updated for Opus 4.5/4.6 and Sonnet 4.6 support (Issue #1221, Issue #1238, Issue #1329, Issue #1433)
10
10
  export const claudeModels = {
11
11
  sonnet: 'claude-sonnet-4-6', // Sonnet 4.6 (default, Issue #1329)
12
- opus: 'claude-opus-4-5-20251101', // Opus 4.5 (Issue #1238)
12
+ opus: 'claude-opus-4-6', // Opus 4.6 (Issue #1433)
13
13
  haiku: 'claude-haiku-4-5-20251001', // Haiku 4.5
14
14
  'haiku-3-5': 'claude-3-5-haiku-20241022', // Haiku 3.5
15
15
  'haiku-3': 'claude-3-haiku-20240307', // Haiku 3
@@ -15,7 +15,7 @@ import { log } from './lib.mjs';
15
15
  export const CLAUDE_MODELS = {
16
16
  // Short aliases (single word)
17
17
  sonnet: 'claude-sonnet-4-6', // Sonnet 4.6 (default, Issue #1329)
18
- opus: 'claude-opus-4-5-20251101', // Opus 4.5 (Issue #1238)
18
+ opus: 'claude-opus-4-6', // Opus 4.6 (Issue #1433)
19
19
  haiku: 'claude-haiku-4-5-20251001',
20
20
  'haiku-3-5': 'claude-3-5-haiku-20241022',
21
21
  'haiku-3': 'claude-3-haiku-20240307',
package/src/review.mjs CHANGED
@@ -73,7 +73,7 @@ const argv = yargs()
73
73
  description: 'Model to use (opus, sonnet, or full model ID like claude-sonnet-4-5-20250929)',
74
74
  alias: 'm',
75
75
  default: 'opus',
76
- choices: ['opus', 'sonnet', 'claude-sonnet-4-5-20250929', 'claude-opus-4-5-20251101'],
76
+ choices: ['opus', 'sonnet', 'claude-sonnet-4-5-20250929', 'claude-opus-4-6', 'claude-opus-4-5-20251101'],
77
77
  })
78
78
  .option('focus', {
79
79
  type: 'string',
package/src/task.mjs CHANGED
@@ -109,7 +109,7 @@ const argv = yargs()
109
109
  description: 'Model to use (opus, sonnet, or full model ID like claude-sonnet-4-5-20250929)',
110
110
  alias: 'm',
111
111
  default: 'sonnet',
112
- choices: ['opus', 'sonnet', 'claude-sonnet-4-5-20250929', 'claude-opus-4-5-20251101'],
112
+ choices: ['opus', 'sonnet', 'claude-sonnet-4-5-20250929', 'claude-opus-4-6', 'claude-opus-4-5-20251101'],
113
113
  })
114
114
  .option('verbose', {
115
115
  type: 'boolean',