@link-assistant/hive-mind 0.46.1 → 0.47.1

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +20 -15
  2. package/README.md +42 -8
  3. package/package.json +16 -3
  4. package/src/agent.lib.mjs +49 -70
  5. package/src/agent.prompts.lib.mjs +6 -20
  6. package/src/buildUserMention.lib.mjs +4 -17
  7. package/src/claude-limits.lib.mjs +15 -15
  8. package/src/claude.lib.mjs +617 -626
  9. package/src/claude.prompts.lib.mjs +7 -22
  10. package/src/codex.lib.mjs +39 -71
  11. package/src/codex.prompts.lib.mjs +6 -20
  12. package/src/config.lib.mjs +3 -16
  13. package/src/contributing-guidelines.lib.mjs +5 -18
  14. package/src/exit-handler.lib.mjs +4 -4
  15. package/src/git.lib.mjs +7 -7
  16. package/src/github-issue-creator.lib.mjs +17 -17
  17. package/src/github-linking.lib.mjs +8 -33
  18. package/src/github.batch.lib.mjs +20 -16
  19. package/src/github.graphql.lib.mjs +18 -18
  20. package/src/github.lib.mjs +89 -91
  21. package/src/hive.config.lib.mjs +50 -50
  22. package/src/hive.mjs +1293 -1296
  23. package/src/instrument.mjs +7 -11
  24. package/src/interactive-mode.lib.mjs +112 -138
  25. package/src/lenv-reader.lib.mjs +1 -6
  26. package/src/lib.mjs +36 -45
  27. package/src/lino.lib.mjs +2 -2
  28. package/src/local-ci-checks.lib.mjs +15 -14
  29. package/src/memory-check.mjs +52 -60
  30. package/src/model-mapping.lib.mjs +25 -32
  31. package/src/model-validation.lib.mjs +31 -31
  32. package/src/opencode.lib.mjs +37 -62
  33. package/src/opencode.prompts.lib.mjs +7 -21
  34. package/src/protect-branch.mjs +14 -15
  35. package/src/review.mjs +28 -27
  36. package/src/reviewers-hive.mjs +64 -69
  37. package/src/sentry.lib.mjs +13 -10
  38. package/src/solve.auto-continue.lib.mjs +48 -38
  39. package/src/solve.auto-pr.lib.mjs +111 -69
  40. package/src/solve.branch-errors.lib.mjs +17 -46
  41. package/src/solve.branch.lib.mjs +16 -23
  42. package/src/solve.config.lib.mjs +263 -261
  43. package/src/solve.error-handlers.lib.mjs +21 -79
  44. package/src/solve.execution.lib.mjs +10 -18
  45. package/src/solve.feedback.lib.mjs +25 -46
  46. package/src/solve.mjs +59 -60
  47. package/src/solve.preparation.lib.mjs +10 -36
  48. package/src/solve.repo-setup.lib.mjs +4 -19
  49. package/src/solve.repository.lib.mjs +37 -37
  50. package/src/solve.results.lib.mjs +32 -46
  51. package/src/solve.session.lib.mjs +7 -22
  52. package/src/solve.validation.lib.mjs +19 -17
  53. package/src/solve.watch.lib.mjs +20 -33
  54. package/src/start-screen.mjs +24 -24
  55. package/src/task.mjs +38 -44
  56. package/src/telegram-bot.mjs +125 -121
  57. package/src/telegram-top-command.lib.mjs +32 -48
  58. package/src/usage-limit.lib.mjs +9 -13
  59. package/src/version-info.lib.mjs +1 -1
  60. package/src/version.lib.mjs +1 -1
  61. package/src/youtrack/solve.youtrack.lib.mjs +3 -8
  62. package/src/youtrack/youtrack-sync.mjs +8 -14
  63. package/src/youtrack/youtrack.lib.mjs +26 -28
@@ -29,10 +29,10 @@ const { log: libLog, setLogFile } = lib;
29
29
  // Function to check available disk space
30
30
  export const checkDiskSpace = async (minSpaceMB = 500, options = {}) => {
31
31
  const log = options.log || libLog;
32
-
32
+
33
33
  try {
34
34
  let availableMB;
35
-
35
+
36
36
  if (process.platform === 'darwin') {
37
37
  // macOS: use df -m (megabytes) and get the 4th column
38
38
  const { stdout } = await $silent`df -m . 2>/dev/null | tail -1 | awk '{print $4}'`;
@@ -42,23 +42,23 @@ export const checkDiskSpace = async (minSpaceMB = 500, options = {}) => {
42
42
  const { stdout } = await $silent`powershell -Command "(Get-PSDrive -Name (Get-Location).Drive.Name).Free / 1MB"`;
43
43
  availableMB = Math.floor(parseFloat(stdout.toString().trim()));
44
44
  } else {
45
- // Linux: use df -BM and get the 4th column
45
+ // Linux: use df -BM and get the 4th column
46
46
  const { stdout } = await $silent`df -BM . 2>/dev/null | tail -1 | awk '{print $4}'`;
47
47
  availableMB = parseInt(stdout.toString().replace('M', ''));
48
48
  }
49
-
49
+
50
50
  if (isNaN(availableMB)) {
51
51
  await log('❌ Failed to parse disk space information');
52
52
  return { success: false, availableMB: 0, error: 'Failed to parse disk space' };
53
53
  }
54
-
54
+
55
55
  if (availableMB < minSpaceMB) {
56
56
  await log(`❌ Insufficient disk space: ${availableMB}MB available, ${minSpaceMB}MB required`);
57
57
  await log(' This may prevent successful operations.');
58
58
  await log(' Please free up disk space and try again.');
59
59
  return { success: false, availableMB, required: minSpaceMB };
60
60
  }
61
-
61
+
62
62
  await log(`💾 Disk space check: ${availableMB}MB available (${minSpaceMB}MB required) ✅`);
63
63
  return { success: true, availableMB, required: minSpaceMB };
64
64
  } catch (error) {
@@ -70,47 +70,47 @@ export const checkDiskSpace = async (minSpaceMB = 500, options = {}) => {
70
70
  // Function to check available RAM (volatile memory)
71
71
  export const checkRAM = async (minMemoryMB = 256, options = {}) => {
72
72
  const log = options.log || libLog;
73
-
73
+
74
74
  // Check platform first
75
75
  if (process.platform === 'darwin') {
76
76
  // macOS RAM check using vm_stat
77
77
  try {
78
78
  const { stdout: vmStatOutput } = await $silent`vm_stat 2>/dev/null`;
79
-
79
+
80
80
  // Parse page size
81
81
  const pageSizeMatch = vmStatOutput.toString().match(/page size of (\d+) bytes/);
82
82
  const pageSize = pageSizeMatch ? parseInt(pageSizeMatch[1]) : 16384; // Default to 16KB
83
-
83
+
84
84
  // Parse free pages (handle dots in numbers like "6009.")
85
85
  const freeMatch = vmStatOutput.toString().match(/Pages free:\s+([\d.]+)/);
86
86
  const freePages = freeMatch ? parseInt(freeMatch[1].replace(/\./g, '')) : 0;
87
-
87
+
88
88
  // Parse inactive pages (can be reclaimed)
89
89
  const inactiveMatch = vmStatOutput.toString().match(/Pages inactive:\s+([\d.]+)/);
90
90
  const inactivePages = inactiveMatch ? parseInt(inactiveMatch[1].replace(/\./g, '')) : 0;
91
-
91
+
92
92
  // Parse purgeable pages (can be freed)
93
93
  const purgeableMatch = vmStatOutput.toString().match(/Pages purgeable:\s+([\d.]+)/);
94
94
  const purgeablePages = purgeableMatch ? parseInt(purgeableMatch[1].replace(/\./g, '')) : 0;
95
-
95
+
96
96
  // Calculate available memory (free + inactive + purgeable)
97
97
  const availablePages = freePages + inactivePages + purgeablePages;
98
98
  const availableBytes = availablePages * pageSize;
99
99
  const availableMB = Math.floor(availableBytes / (1024 * 1024));
100
-
100
+
101
101
  // Check swap status
102
102
  const { stdout: swapEnabledOutput } = await $silent`sysctl vm.swap_enabled 2>/dev/null`;
103
103
  const swapEnabled = swapEnabledOutput.toString().includes('1');
104
-
104
+
105
105
  // Get swap usage details
106
106
  const { stdout: swapUsageOutput } = await $silent`sysctl vm.swapusage 2>/dev/null`;
107
-
107
+
108
108
  // Parse swap info
109
109
  const swapMatch = swapUsageOutput.toString().match(/total = ([\d.]+)M\s+used = ([\d.]+)M/);
110
110
  const swapTotal = swapMatch ? parseFloat(swapMatch[1]) : 0;
111
111
  const swapUsed = swapMatch ? parseFloat(swapMatch[2]) : 0;
112
112
  const swapAvailable = swapTotal - swapUsed;
113
-
113
+
114
114
  let swapInfo;
115
115
  if (swapEnabled) {
116
116
  if (swapTotal > 0) {
@@ -121,7 +121,7 @@ export const checkRAM = async (minMemoryMB = 256, options = {}) => {
121
121
  } else {
122
122
  swapInfo = 'disabled';
123
123
  }
124
-
124
+
125
125
  // Calculate total available memory (RAM + swap)
126
126
  const totalAvailable = availableMB + (swapEnabled && swapTotal > 0 ? Math.round(swapAvailable) : 0);
127
127
 
@@ -141,7 +141,6 @@ export const checkRAM = async (minMemoryMB = 256, options = {}) => {
141
141
 
142
142
  await log(`🧠 Memory check: ${availableMB}MB available, swap: ${swapInfo}, total: ${totalAvailable}MB (${minMemoryMB}MB required) ✅`);
143
143
  return { success: true, availableMB, required: minMemoryMB, swap: swapInfo, totalAvailable };
144
-
145
144
  } catch (error) {
146
145
  await log(`❌ macOS memory check failed: ${error.message}`);
147
146
  return { success: false, availableMB: 0, error: error.message };
@@ -150,20 +149,20 @@ export const checkRAM = async (minMemoryMB = 256, options = {}) => {
150
149
  // Windows memory check using PowerShell
151
150
  try {
152
151
  const { stdout: memOutput } = await $silent`powershell -Command "Get-CimInstance Win32_OperatingSystem | Select-Object @{Name='AvailableMB';Expression={[math]::Round($_.FreePhysicalMemory/1024)}}, @{Name='TotalPageFileMB';Expression={[math]::Round($_.TotalVirtualMemorySize/1024)}}, @{Name='FreePageFileMB';Expression={[math]::Round($_.FreeVirtualMemory/1024)}} | ConvertTo-Json"`;
153
-
152
+
154
153
  const memInfo = JSON.parse(memOutput.toString());
155
154
  const availableMB = memInfo.AvailableMB;
156
155
  const pageFileTotalMB = memInfo.TotalPageFileMB || 0;
157
156
  const pageFileFreeMB = memInfo.FreePageFileMB || 0;
158
157
  const pageFileUsedMB = pageFileTotalMB - pageFileFreeMB;
159
-
158
+
160
159
  let swapInfo;
161
160
  if (pageFileTotalMB > 0) {
162
161
  swapInfo = `${pageFileTotalMB}MB (${pageFileUsedMB}MB used)`;
163
162
  } else {
164
163
  swapInfo = 'none';
165
164
  }
166
-
165
+
167
166
  // Calculate total available memory (RAM + page file)
168
167
  const totalAvailable = availableMB + (pageFileFreeMB || 0);
169
168
 
@@ -178,7 +177,6 @@ export const checkRAM = async (minMemoryMB = 256, options = {}) => {
178
177
 
179
178
  await log(`🧠 Memory check: ${availableMB}MB available, page file: ${swapInfo}, total: ${totalAvailable}MB (${minMemoryMB}MB required) ✅`);
180
179
  return { success: true, availableMB, required: minMemoryMB, swap: swapInfo, totalAvailable };
181
-
182
180
  } catch (error) {
183
181
  await log(`❌ Windows memory check failed: ${error.message}`);
184
182
  return { success: false, availableMB: 0, error: error.message };
@@ -188,14 +186,14 @@ export const checkRAM = async (minMemoryMB = 256, options = {}) => {
188
186
  try {
189
187
  const meminfoContent = await fs.readFile('/proc/meminfo', 'utf8');
190
188
  const lines = meminfoContent.split('\n');
191
-
192
- const getValue = (key) => {
189
+
190
+ const getValue = key => {
193
191
  const line = lines.find(l => l.startsWith(key));
194
192
  if (!line) return 0;
195
193
  const match = line.match(/(\d+)/);
196
194
  return match ? parseInt(match[1]) : 0;
197
195
  };
198
-
196
+
199
197
  // Get memory values in KB
200
198
  const memFree = getValue('MemFree:');
201
199
  const buffers = getValue('Buffers:');
@@ -203,24 +201,24 @@ export const checkRAM = async (minMemoryMB = 256, options = {}) => {
203
201
  const sReclaimable = getValue('SReclaimable:');
204
202
  const swapTotal = getValue('SwapTotal:');
205
203
  const swapFree = getValue('SwapFree:');
206
-
204
+
207
205
  // Calculate available memory (similar to 'free' command)
208
206
  const availableKB = memFree + buffers + cached + sReclaimable;
209
207
  const availableMB = Math.floor(availableKB / 1024);
210
-
208
+
211
209
  // Calculate swap info
212
210
  const swapUsedKB = swapTotal - swapFree;
213
211
  const swapMB = Math.floor(swapTotal / 1024);
214
212
  const swapUsedMB = Math.floor(swapUsedKB / 1024);
215
213
  const swapAvailableMB = Math.floor(swapFree / 1024);
216
-
214
+
217
215
  let swapInfo;
218
216
  if (swapTotal > 0) {
219
217
  swapInfo = `${swapMB}MB (${swapUsedMB}MB used)`;
220
218
  } else {
221
219
  swapInfo = 'none';
222
220
  }
223
-
221
+
224
222
  // Calculate total available memory (RAM + swap)
225
223
  const totalAvailable = availableMB + swapAvailableMB;
226
224
 
@@ -244,7 +242,6 @@ export const checkRAM = async (minMemoryMB = 256, options = {}) => {
244
242
 
245
243
  await log(`🧠 Memory check: ${availableMB}MB available, swap: ${swapInfo}, total: ${totalAvailable}MB (${minMemoryMB}MB required) ✅`);
246
244
  return { success: true, availableMB, required: minMemoryMB, swap: swapInfo, totalAvailable };
247
-
248
245
  } catch (error) {
249
246
  await log(`❌ Linux memory check failed: ${error.message}`);
250
247
  return { success: false, availableMB: 0, error: error.message };
@@ -263,49 +260,45 @@ export const getResourceSnapshot = async () => {
263
260
  const vmStat = await $silent`vm_stat 2>/dev/null | head -10`;
264
261
  const uptime = await $silent`uptime 2>/dev/null`;
265
262
  const swap = await $silent`sysctl vm.swapusage 2>/dev/null`;
266
-
263
+
267
264
  return {
268
265
  timestamp: new Date().toISOString(),
269
266
  memory: vmStat.stdout.toString().trim(),
270
267
  swap: swap.stdout.toString().trim(),
271
- uptime: uptime.stdout.toString().trim()
268
+ uptime: uptime.stdout.toString().trim(),
272
269
  };
273
270
  } else {
274
271
  // Linux resource snapshot
275
272
  const memInfo = await $silent`grep -E "MemTotal|MemAvailable|MemFree|SwapTotal|SwapFree" /proc/meminfo 2>/dev/null`;
276
273
  const loadAvg = await $silent`cat /proc/loadavg`;
277
274
  const uptime = await $silent`uptime`;
278
-
275
+
279
276
  return {
280
277
  timestamp: new Date().toISOString(),
281
278
  memory: memInfo.stdout.toString().trim(),
282
279
  load: loadAvg.stdout.toString().trim(),
283
- uptime: uptime.stdout.toString().trim()
280
+ uptime: uptime.stdout.toString().trim(),
284
281
  };
285
282
  }
286
283
  } catch (error) {
287
284
  return {
288
285
  timestamp: new Date().toISOString(),
289
- error: `Failed to get resource snapshot: ${error.message}`
286
+ error: `Failed to get resource snapshot: ${error.message}`,
290
287
  };
291
288
  }
292
289
  };
293
290
 
294
291
  // Combined system check function
295
292
  export const checkSystem = async (requirements = {}, options = {}) => {
296
- const {
297
- minMemoryMB = 256,
298
- minDiskSpaceMB = 500,
299
- exitOnFailure = false
300
- } = requirements;
293
+ const { minMemoryMB = 256, minDiskSpaceMB = 500, exitOnFailure = false } = requirements;
301
294
 
302
295
  // Note: log is passed through options to checkDiskSpace and checkRAM
303
296
  const results = {
304
297
  ram: null,
305
298
  disk: null,
306
- success: true
299
+ success: true,
307
300
  };
308
-
301
+
309
302
  // Check disk space (persistent memory)
310
303
  results.disk = await checkDiskSpace(minDiskSpaceMB, options);
311
304
  if (!results.disk.success) {
@@ -314,7 +307,7 @@ export const checkSystem = async (requirements = {}, options = {}) => {
314
307
  process.exit(1);
315
308
  }
316
309
  }
317
-
310
+
318
311
  // Check RAM (volatile memory)
319
312
  results.ram = await checkRAM(minMemoryMB, options);
320
313
  if (!results.ram.success) {
@@ -323,13 +316,12 @@ export const checkSystem = async (requirements = {}, options = {}) => {
323
316
  process.exit(1);
324
317
  }
325
318
  }
326
-
319
+
327
320
  return results;
328
321
  };
329
322
 
330
323
  // CLI interface when run directly
331
324
  if (import.meta.url === `file://${process.argv[1]}`) {
332
-
333
325
  // Create yargs instance with all options
334
326
  const yargsInstance = yargs(hideBin(process.argv))
335
327
  .scriptName('memory-check.mjs')
@@ -338,66 +330,66 @@ if (import.meta.url === `file://${process.argv[1]}`) {
338
330
  alias: 'm',
339
331
  type: 'number',
340
332
  description: 'Minimum required memory in MB',
341
- default: 256
333
+ default: 256,
342
334
  })
343
335
  .option('min-disk-space', {
344
336
  alias: 'd',
345
337
  type: 'number',
346
338
  description: 'Minimum required disk space in MB',
347
- default: 500
339
+ default: 500,
348
340
  })
349
341
  .option('exit-on-failure', {
350
342
  alias: 'e',
351
343
  type: 'boolean',
352
344
  description: 'Exit with code 1 if any check fails',
353
- default: false
345
+ default: false,
354
346
  })
355
347
  .option('json', {
356
348
  alias: 'j',
357
349
  type: 'boolean',
358
350
  description: 'Output results as JSON',
359
- default: false
351
+ default: false,
360
352
  })
361
353
  .option('quiet', {
362
354
  alias: 'q',
363
355
  type: 'boolean',
364
356
  description: 'Suppress detailed output (only show final status)',
365
- default: false
357
+ default: false,
366
358
  })
367
359
  .option('log-file', {
368
360
  alias: 'l',
369
361
  type: 'string',
370
- description: 'Path to log file for output'
362
+ description: 'Path to log file for output',
371
363
  })
372
364
  .help('h')
373
365
  .alias('h', 'help');
374
-
366
+
375
367
  // Check for help before parsing
376
368
  if (process.argv.includes('--help') || process.argv.includes('-h')) {
377
369
  yargsInstance.showHelp();
378
370
  process.exit(0);
379
371
  }
380
-
372
+
381
373
  const argv = await yargsInstance.parseAsync();
382
-
374
+
383
375
  // If we get here, help wasn't requested or yargs didn't handle it
384
376
  // Set up logging based on options
385
377
  if (argv.logFile) {
386
378
  setLogFile(argv.logFile);
387
379
  }
388
-
380
+
389
381
  // Create appropriate log function based on quiet mode
390
382
  const log = argv.quiet ? async () => {} : libLog;
391
-
383
+
392
384
  const results = await checkSystem(
393
385
  {
394
386
  minMemoryMB: argv.minMemory,
395
387
  minDiskSpaceMB: argv.minDiskSpace,
396
- exitOnFailure: argv.exitOnFailure
388
+ exitOnFailure: argv.exitOnFailure,
397
389
  },
398
390
  { log }
399
391
  );
400
-
392
+
401
393
  if (argv.json) {
402
394
  console.log(JSON.stringify(results, null, 2));
403
395
  } else if (!argv.quiet) {
@@ -407,7 +399,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {
407
399
  console.log(`Disk: ${results.disk.success ? '✅' : '❌'} ${results.disk.availableMB}MB available (${results.disk.required}MB required)`);
408
400
  console.log(`Overall: ${results.success ? '✅ All checks passed' : '❌ Some checks failed'}`);
409
401
  }
410
-
402
+
411
403
  if (!results.success && argv.exitOnFailure) {
412
404
  process.exit(1);
413
405
  }
@@ -416,4 +408,4 @@ if (import.meta.url === `file://${process.argv[1]}`) {
416
408
  // Restore CI if it was set (at the very end, after yargs has processed everything)
417
409
  if (originalCI !== undefined) {
418
410
  process.env.CI = originalCI;
419
- }
411
+ }
@@ -7,50 +7,50 @@
7
7
 
8
8
  // Claude models (Anthropic API)
9
9
  export const claudeModels = {
10
- 'sonnet': 'claude-sonnet-4-5-20250929', // Sonnet 4.5
11
- 'opus': 'claude-opus-4-5-20251101', // Opus 4.5
12
- 'haiku': 'claude-haiku-4-5-20251001', // Haiku 4.5
10
+ sonnet: 'claude-sonnet-4-5-20250929', // Sonnet 4.5
11
+ opus: 'claude-opus-4-5-20251101', // Opus 4.5
12
+ haiku: 'claude-haiku-4-5-20251001', // Haiku 4.5
13
13
  'haiku-3-5': 'claude-3-5-haiku-20241022', // Haiku 3.5
14
- 'haiku-3': 'claude-3-haiku-20240307', // Haiku 3
14
+ 'haiku-3': 'claude-3-haiku-20240307', // Haiku 3
15
15
  };
16
16
 
17
17
  // Agent models (OpenCode API via agent CLI)
18
18
  export const agentModels = {
19
- 'grok': 'opencode/grok-code',
19
+ grok: 'opencode/grok-code',
20
20
  'grok-code': 'opencode/grok-code',
21
21
  'grok-code-fast-1': 'opencode/grok-code',
22
22
  'big-pickle': 'opencode/big-pickle',
23
23
  'gpt-5-nano': 'openai/gpt-5-nano',
24
- 'sonnet': 'anthropic/claude-3-5-sonnet',
25
- 'haiku': 'anthropic/claude-3-5-haiku',
26
- 'opus': 'anthropic/claude-3-opus',
24
+ sonnet: 'anthropic/claude-3-5-sonnet',
25
+ haiku: 'anthropic/claude-3-5-haiku',
26
+ opus: 'anthropic/claude-3-opus',
27
27
  'gemini-3-pro': 'google/gemini-3-pro',
28
28
  };
29
29
 
30
30
  // OpenCode models (OpenCode API)
31
31
  export const opencodeModels = {
32
- 'gpt4': 'openai/gpt-4',
33
- 'gpt4o': 'openai/gpt-4o',
34
- 'claude': 'anthropic/claude-3-5-sonnet',
35
- 'sonnet': 'anthropic/claude-3-5-sonnet',
36
- 'opus': 'anthropic/claude-3-opus',
37
- 'gemini': 'google/gemini-pro',
38
- 'grok': 'opencode/grok-code',
32
+ gpt4: 'openai/gpt-4',
33
+ gpt4o: 'openai/gpt-4o',
34
+ claude: 'anthropic/claude-3-5-sonnet',
35
+ sonnet: 'anthropic/claude-3-5-sonnet',
36
+ opus: 'anthropic/claude-3-opus',
37
+ gemini: 'google/gemini-pro',
38
+ grok: 'opencode/grok-code',
39
39
  'grok-code': 'opencode/grok-code',
40
40
  'grok-code-fast-1': 'opencode/grok-code',
41
41
  };
42
42
 
43
43
  // Codex models (OpenAI API)
44
44
  export const codexModels = {
45
- 'gpt5': 'gpt-5',
45
+ gpt5: 'gpt-5',
46
46
  'gpt5-codex': 'gpt-5-codex',
47
- 'o3': 'o3',
47
+ o3: 'o3',
48
48
  'o3-mini': 'o3-mini',
49
- 'gpt4': 'gpt-4',
50
- 'gpt4o': 'gpt-4o',
51
- 'claude': 'claude-3-5-sonnet',
52
- 'sonnet': 'claude-3-5-sonnet',
53
- 'opus': 'claude-3-opus',
49
+ gpt4: 'gpt-4',
50
+ gpt4o: 'gpt-4o',
51
+ claude: 'claude-3-5-sonnet',
52
+ sonnet: 'claude-3-5-sonnet',
53
+ opus: 'claude-3-opus',
54
54
  };
55
55
 
56
56
  /**
@@ -96,10 +96,7 @@ export const isModelCompatibleWithTool = (tool, model) => {
96
96
  return mappedModel.includes('/') || Object.keys(opencodeModels).includes(model);
97
97
  case 'codex':
98
98
  // Codex accepts OpenAI and some Claude models
99
- return Object.keys(codexModels).includes(model) ||
100
- mappedModel.startsWith('gpt-') ||
101
- mappedModel.startsWith('o3') ||
102
- mappedModel.startsWith('claude-');
99
+ return Object.keys(codexModels).includes(model) || mappedModel.startsWith('gpt-') || mappedModel.startsWith('o3') || mappedModel.startsWith('claude-');
103
100
  default:
104
101
  return true;
105
102
  }
@@ -110,7 +107,7 @@ export const isModelCompatibleWithTool = (tool, model) => {
110
107
  * @param {string} tool - The tool name
111
108
  * @returns {string[]} Array of valid model names
112
109
  */
113
- export const getValidModelsForTool = (tool) => {
110
+ export const getValidModelsForTool = tool => {
114
111
  switch (tool) {
115
112
  case 'claude':
116
113
  return Object.keys(claudeModels);
@@ -136,10 +133,6 @@ export const validateToolModelCompatibility = (tool, model) => {
136
133
  const validModels = getValidModelsForTool(tool);
137
134
  const mappedModel = mapModelForTool(tool, model);
138
135
 
139
- throw new Error(
140
- `Model '${model}' (mapped to '${mappedModel}') is not compatible with --tool ${tool}.\n` +
141
- `Valid models for ${tool}: ${validModels.join(', ')}\n` +
142
- 'Hint: Different tools use different model APIs and naming conventions.'
143
- );
136
+ throw new Error(`Model '${model}' (mapped to '${mappedModel}') is not compatible with --tool ${tool}.\n` + `Valid models for ${tool}: ${validModels.join(', ')}\n` + 'Hint: Different tools use different model APIs and naming conventions.');
144
137
  }
145
138
  };
@@ -14,9 +14,9 @@ import { log } from './lib.mjs';
14
14
  // These are the "known good" model names that we accept
15
15
  export const CLAUDE_MODELS = {
16
16
  // Short aliases
17
- 'sonnet': 'claude-sonnet-4-5-20250929',
18
- 'opus': 'claude-opus-4-5-20251101',
19
- 'haiku': 'claude-haiku-4-5-20251001',
17
+ sonnet: 'claude-sonnet-4-5-20250929',
18
+ opus: 'claude-opus-4-5-20251101',
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',
22
22
  // Full model IDs (also valid inputs)
@@ -28,13 +28,13 @@ export const CLAUDE_MODELS = {
28
28
  };
29
29
 
30
30
  export const OPENCODE_MODELS = {
31
- 'gpt4': 'openai/gpt-4',
32
- 'gpt4o': 'openai/gpt-4o',
33
- 'claude': 'anthropic/claude-3-5-sonnet',
34
- 'sonnet': 'anthropic/claude-3-5-sonnet',
35
- 'opus': 'anthropic/claude-3-opus',
36
- 'gemini': 'google/gemini-pro',
37
- 'grok': 'opencode/grok-code',
31
+ gpt4: 'openai/gpt-4',
32
+ gpt4o: 'openai/gpt-4o',
33
+ claude: 'anthropic/claude-3-5-sonnet',
34
+ sonnet: 'anthropic/claude-3-5-sonnet',
35
+ opus: 'anthropic/claude-3-opus',
36
+ gemini: 'google/gemini-pro',
37
+ grok: 'opencode/grok-code',
38
38
  'grok-code': 'opencode/grok-code',
39
39
  'grok-code-fast-1': 'opencode/grok-code',
40
40
  // Full model IDs
@@ -47,17 +47,17 @@ export const OPENCODE_MODELS = {
47
47
  };
48
48
 
49
49
  export const CODEX_MODELS = {
50
- 'gpt5': 'gpt-5',
50
+ gpt5: 'gpt-5',
51
51
  'gpt-5': 'gpt-5',
52
52
  'gpt5-codex': 'gpt-5-codex',
53
53
  'gpt-5-codex': 'gpt-5-codex',
54
- 'o3': 'o3',
54
+ o3: 'o3',
55
55
  'o3-mini': 'o3-mini',
56
- 'gpt4': 'gpt-4',
57
- 'gpt4o': 'gpt-4o',
58
- 'claude': 'claude-3-5-sonnet',
59
- 'sonnet': 'claude-3-5-sonnet',
60
- 'opus': 'claude-3-opus',
56
+ gpt4: 'gpt-4',
57
+ gpt4o: 'gpt-4o',
58
+ claude: 'claude-3-5-sonnet',
59
+ sonnet: 'claude-3-5-sonnet',
60
+ opus: 'claude-3-opus',
61
61
  // Full model IDs
62
62
  'gpt-4': 'gpt-4',
63
63
  'gpt-4o': 'gpt-4o',
@@ -67,15 +67,15 @@ export const CODEX_MODELS = {
67
67
 
68
68
  export const AGENT_MODELS = {
69
69
  // Free models (via OpenCode)
70
- 'grok': 'opencode/grok-code',
70
+ grok: 'opencode/grok-code',
71
71
  'grok-code': 'opencode/grok-code',
72
72
  'grok-code-fast-1': 'opencode/grok-code',
73
73
  'big-pickle': 'opencode/big-pickle',
74
74
  'gpt-5-nano': 'openai/gpt-5-nano',
75
75
  // Premium models (requires OpenCode Zen subscription)
76
- 'sonnet': 'anthropic/claude-3-5-sonnet',
77
- 'haiku': 'anthropic/claude-3-5-haiku',
78
- 'opus': 'anthropic/claude-3-opus',
76
+ sonnet: 'anthropic/claude-3-5-sonnet',
77
+ haiku: 'anthropic/claude-3-5-haiku',
78
+ opus: 'anthropic/claude-3-opus',
79
79
  'gemini-3-pro': 'google/gemini-3-pro',
80
80
  // Full model IDs
81
81
  'opencode/grok-code': 'opencode/grok-code',
@@ -92,7 +92,7 @@ export const AGENT_MODELS = {
92
92
  * @param {string} tool - The tool name ('claude', 'opencode', 'codex', 'agent')
93
93
  * @returns {Object} The model mapping for the tool
94
94
  */
95
- export const getModelMapForTool = (tool) => {
95
+ export const getModelMapForTool = tool => {
96
96
  switch (tool) {
97
97
  case 'opencode':
98
98
  return OPENCODE_MODELS;
@@ -111,7 +111,7 @@ export const getModelMapForTool = (tool) => {
111
111
  * @param {string} tool - The tool name ('claude', 'opencode', 'codex', 'agent')
112
112
  * @returns {string[]} Array of available model short names
113
113
  */
114
- export const getAvailableModelNames = (tool) => {
114
+ export const getAvailableModelNames = tool => {
115
115
  const modelMap = getModelMapForTool(tool);
116
116
  // Get unique short names (aliases) - exclude full model IDs that contain '/' or long claude- prefixed IDs
117
117
  const aliases = Object.keys(modelMap).filter(key => {
@@ -121,8 +121,8 @@ export const getAvailableModelNames = (tool) => {
121
121
  // - Full gpt- prefixed IDs with version numbers (e.g., 'gpt-4', 'gpt-4o')
122
122
  // But keep short names like 'o3', 'o3-mini', 'gpt5', etc.
123
123
  if (key.includes('/')) return false;
124
- if (key.match(/^claude-.*-\d{8}$/)) return false; // Full claude model IDs with date
125
- if (key.match(/^gpt-\d+/)) return false; // Full gpt-N model IDs
124
+ if (key.match(/^claude-.*-\d{8}$/)) return false; // Full claude model IDs with date
125
+ if (key.match(/^gpt-\d+/)) return false; // Full gpt-N model IDs
126
126
  return true;
127
127
  });
128
128
  return [...new Set(aliases)];
@@ -162,8 +162,8 @@ export const levenshteinDistance = (a, b) => {
162
162
  } else {
163
163
  matrix[i][j] = Math.min(
164
164
  matrix[i - 1][j - 1] + 1, // substitution
165
- matrix[i][j - 1] + 1, // insertion
166
- matrix[i - 1][j] + 1 // deletion
165
+ matrix[i][j - 1] + 1, // insertion
166
+ matrix[i - 1][j] + 1 // deletion
167
167
  );
168
168
  }
169
169
  }
@@ -184,7 +184,7 @@ export const findSimilarModels = (input, validModels, maxSuggestions = 3, maxDis
184
184
  const suggestions = validModels
185
185
  .map(model => ({
186
186
  model,
187
- distance: levenshteinDistance(input, model)
187
+ distance: levenshteinDistance(input, model),
188
188
  }))
189
189
  .filter(({ distance }) => distance <= maxDistance)
190
190
  .sort((a, b) => a.distance - b.distance)
@@ -205,7 +205,7 @@ export const validateModelName = (model, tool = 'claude') => {
205
205
  return {
206
206
  valid: false,
207
207
  message: 'Model name is required',
208
- suggestions: []
208
+ suggestions: [],
209
209
  };
210
210
  }
211
211
 
@@ -219,7 +219,7 @@ export const validateModelName = (model, tool = 'claude') => {
219
219
  if (matchedKey) {
220
220
  return {
221
221
  valid: true,
222
- mappedModel: modelMap[matchedKey]
222
+ mappedModel: modelMap[matchedKey],
223
223
  };
224
224
  }
225
225
 
@@ -238,7 +238,7 @@ export const validateModelName = (model, tool = 'claude') => {
238
238
  return {
239
239
  valid: false,
240
240
  message,
241
- suggestions
241
+ suggestions,
242
242
  };
243
243
  };
244
244