@link-assistant/hive-mind 0.39.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/CHANGELOG.md +20 -0
- package/LICENSE +24 -0
- package/README.md +769 -0
- package/package.json +58 -0
- package/src/agent.lib.mjs +705 -0
- package/src/agent.prompts.lib.mjs +196 -0
- package/src/buildUserMention.lib.mjs +71 -0
- package/src/claude-limits.lib.mjs +389 -0
- package/src/claude.lib.mjs +1445 -0
- package/src/claude.prompts.lib.mjs +203 -0
- package/src/codex.lib.mjs +552 -0
- package/src/codex.prompts.lib.mjs +194 -0
- package/src/config.lib.mjs +207 -0
- package/src/contributing-guidelines.lib.mjs +268 -0
- package/src/exit-handler.lib.mjs +205 -0
- package/src/git.lib.mjs +145 -0
- package/src/github-issue-creator.lib.mjs +246 -0
- package/src/github-linking.lib.mjs +152 -0
- package/src/github.batch.lib.mjs +272 -0
- package/src/github.graphql.lib.mjs +258 -0
- package/src/github.lib.mjs +1479 -0
- package/src/hive.config.lib.mjs +254 -0
- package/src/hive.mjs +1500 -0
- package/src/instrument.mjs +191 -0
- package/src/interactive-mode.lib.mjs +1000 -0
- package/src/lenv-reader.lib.mjs +206 -0
- package/src/lib.mjs +490 -0
- package/src/lino.lib.mjs +176 -0
- package/src/local-ci-checks.lib.mjs +324 -0
- package/src/memory-check.mjs +419 -0
- package/src/model-mapping.lib.mjs +145 -0
- package/src/model-validation.lib.mjs +278 -0
- package/src/opencode.lib.mjs +479 -0
- package/src/opencode.prompts.lib.mjs +194 -0
- package/src/protect-branch.mjs +159 -0
- package/src/review.mjs +433 -0
- package/src/reviewers-hive.mjs +643 -0
- package/src/sentry.lib.mjs +284 -0
- package/src/solve.auto-continue.lib.mjs +568 -0
- package/src/solve.auto-pr.lib.mjs +1374 -0
- package/src/solve.branch-errors.lib.mjs +341 -0
- package/src/solve.branch.lib.mjs +230 -0
- package/src/solve.config.lib.mjs +342 -0
- package/src/solve.error-handlers.lib.mjs +256 -0
- package/src/solve.execution.lib.mjs +291 -0
- package/src/solve.feedback.lib.mjs +436 -0
- package/src/solve.mjs +1128 -0
- package/src/solve.preparation.lib.mjs +210 -0
- package/src/solve.repo-setup.lib.mjs +114 -0
- package/src/solve.repository.lib.mjs +961 -0
- package/src/solve.results.lib.mjs +558 -0
- package/src/solve.session.lib.mjs +135 -0
- package/src/solve.validation.lib.mjs +325 -0
- package/src/solve.watch.lib.mjs +572 -0
- package/src/start-screen.mjs +324 -0
- package/src/task.mjs +308 -0
- package/src/telegram-bot.mjs +1481 -0
- package/src/telegram-markdown.lib.mjs +64 -0
- package/src/usage-limit.lib.mjs +218 -0
- package/src/version.lib.mjs +41 -0
- package/src/youtrack/solve.youtrack.lib.mjs +116 -0
- package/src/youtrack/youtrack-sync.mjs +219 -0
- package/src/youtrack/youtrack.lib.mjs +425 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Check if use is already defined (when imported from solve.mjs)
|
|
4
|
+
// If not, fetch it (when running standalone)
|
|
5
|
+
if (typeof globalThis.use === 'undefined') {
|
|
6
|
+
globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
|
|
7
|
+
}
|
|
8
|
+
const use = globalThis.use;
|
|
9
|
+
|
|
10
|
+
// Temporarily unset CI to avoid command-stream trace logs
|
|
11
|
+
const originalCI = process.env.CI;
|
|
12
|
+
delete process.env.CI;
|
|
13
|
+
|
|
14
|
+
const { $ } = await use('command-stream');
|
|
15
|
+
// Create a silent version of $ that doesn't mirror output to stdout
|
|
16
|
+
// Note: command-stream may still emit trace logs to stderr in some environments
|
|
17
|
+
// These are filtered out by consuming code when parsing JSON
|
|
18
|
+
const $silent = $({ mirror: false, capture: true });
|
|
19
|
+
|
|
20
|
+
const yargsModule = await use('yargs@17.7.2');
|
|
21
|
+
const yargs = yargsModule.default || yargsModule;
|
|
22
|
+
const { hideBin } = await use('yargs@17.7.2/helpers');
|
|
23
|
+
const fs = (await use('fs')).promises;
|
|
24
|
+
|
|
25
|
+
// Import log function from lib.mjs
|
|
26
|
+
const lib = await import('./lib.mjs');
|
|
27
|
+
const { log: libLog, setLogFile } = lib;
|
|
28
|
+
|
|
29
|
+
// Function to check available disk space
|
|
30
|
+
export const checkDiskSpace = async (minSpaceMB = 500, options = {}) => {
|
|
31
|
+
const log = options.log || libLog;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
let availableMB;
|
|
35
|
+
|
|
36
|
+
if (process.platform === 'darwin') {
|
|
37
|
+
// macOS: use df -m (megabytes) and get the 4th column
|
|
38
|
+
const { stdout } = await $silent`df -m . 2>/dev/null | tail -1 | awk '{print $4}'`;
|
|
39
|
+
availableMB = parseInt(stdout.toString().trim());
|
|
40
|
+
} else if (process.platform === 'win32') {
|
|
41
|
+
// Windows: use PowerShell to get free space
|
|
42
|
+
const { stdout } = await $silent`powershell -Command "(Get-PSDrive -Name (Get-Location).Drive.Name).Free / 1MB"`;
|
|
43
|
+
availableMB = Math.floor(parseFloat(stdout.toString().trim()));
|
|
44
|
+
} else {
|
|
45
|
+
// Linux: use df -BM and get the 4th column
|
|
46
|
+
const { stdout } = await $silent`df -BM . 2>/dev/null | tail -1 | awk '{print $4}'`;
|
|
47
|
+
availableMB = parseInt(stdout.toString().replace('M', ''));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (isNaN(availableMB)) {
|
|
51
|
+
await log('❌ Failed to parse disk space information');
|
|
52
|
+
return { success: false, availableMB: 0, error: 'Failed to parse disk space' };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (availableMB < minSpaceMB) {
|
|
56
|
+
await log(`❌ Insufficient disk space: ${availableMB}MB available, ${minSpaceMB}MB required`);
|
|
57
|
+
await log(' This may prevent successful operations.');
|
|
58
|
+
await log(' Please free up disk space and try again.');
|
|
59
|
+
return { success: false, availableMB, required: minSpaceMB };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await log(`💾 Disk space check: ${availableMB}MB available (${minSpaceMB}MB required) ✅`);
|
|
63
|
+
return { success: true, availableMB, required: minSpaceMB };
|
|
64
|
+
} catch (error) {
|
|
65
|
+
await log(`❌ Could not check disk space: ${error.message}`);
|
|
66
|
+
return { success: false, availableMB: 0, error: error.message };
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Function to check available RAM (volatile memory)
|
|
71
|
+
export const checkRAM = async (minMemoryMB = 256, options = {}) => {
|
|
72
|
+
const log = options.log || libLog;
|
|
73
|
+
|
|
74
|
+
// Check platform first
|
|
75
|
+
if (process.platform === 'darwin') {
|
|
76
|
+
// macOS RAM check using vm_stat
|
|
77
|
+
try {
|
|
78
|
+
const { stdout: vmStatOutput } = await $silent`vm_stat 2>/dev/null`;
|
|
79
|
+
|
|
80
|
+
// Parse page size
|
|
81
|
+
const pageSizeMatch = vmStatOutput.toString().match(/page size of (\d+) bytes/);
|
|
82
|
+
const pageSize = pageSizeMatch ? parseInt(pageSizeMatch[1]) : 16384; // Default to 16KB
|
|
83
|
+
|
|
84
|
+
// Parse free pages (handle dots in numbers like "6009.")
|
|
85
|
+
const freeMatch = vmStatOutput.toString().match(/Pages free:\s+([\d.]+)/);
|
|
86
|
+
const freePages = freeMatch ? parseInt(freeMatch[1].replace(/\./g, '')) : 0;
|
|
87
|
+
|
|
88
|
+
// Parse inactive pages (can be reclaimed)
|
|
89
|
+
const inactiveMatch = vmStatOutput.toString().match(/Pages inactive:\s+([\d.]+)/);
|
|
90
|
+
const inactivePages = inactiveMatch ? parseInt(inactiveMatch[1].replace(/\./g, '')) : 0;
|
|
91
|
+
|
|
92
|
+
// Parse purgeable pages (can be freed)
|
|
93
|
+
const purgeableMatch = vmStatOutput.toString().match(/Pages purgeable:\s+([\d.]+)/);
|
|
94
|
+
const purgeablePages = purgeableMatch ? parseInt(purgeableMatch[1].replace(/\./g, '')) : 0;
|
|
95
|
+
|
|
96
|
+
// Calculate available memory (free + inactive + purgeable)
|
|
97
|
+
const availablePages = freePages + inactivePages + purgeablePages;
|
|
98
|
+
const availableBytes = availablePages * pageSize;
|
|
99
|
+
const availableMB = Math.floor(availableBytes / (1024 * 1024));
|
|
100
|
+
|
|
101
|
+
// Check swap status
|
|
102
|
+
const { stdout: swapEnabledOutput } = await $silent`sysctl vm.swap_enabled 2>/dev/null`;
|
|
103
|
+
const swapEnabled = swapEnabledOutput.toString().includes('1');
|
|
104
|
+
|
|
105
|
+
// Get swap usage details
|
|
106
|
+
const { stdout: swapUsageOutput } = await $silent`sysctl vm.swapusage 2>/dev/null`;
|
|
107
|
+
|
|
108
|
+
// Parse swap info
|
|
109
|
+
const swapMatch = swapUsageOutput.toString().match(/total = ([\d.]+)M\s+used = ([\d.]+)M/);
|
|
110
|
+
const swapTotal = swapMatch ? parseFloat(swapMatch[1]) : 0;
|
|
111
|
+
const swapUsed = swapMatch ? parseFloat(swapMatch[2]) : 0;
|
|
112
|
+
const swapAvailable = swapTotal - swapUsed;
|
|
113
|
+
|
|
114
|
+
let swapInfo;
|
|
115
|
+
if (swapEnabled) {
|
|
116
|
+
if (swapTotal > 0) {
|
|
117
|
+
swapInfo = `${Math.round(swapTotal)}MB (${Math.round(swapUsed)}MB used)`;
|
|
118
|
+
} else {
|
|
119
|
+
swapInfo = 'enabled (dynamic allocation)';
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
swapInfo = 'disabled';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Calculate total available memory (RAM + swap)
|
|
126
|
+
const totalAvailable = availableMB + (swapEnabled && swapTotal > 0 ? Math.round(swapAvailable) : 0);
|
|
127
|
+
|
|
128
|
+
// Check only total memory against the requirement
|
|
129
|
+
const success = totalAvailable >= minMemoryMB;
|
|
130
|
+
|
|
131
|
+
if (!success) {
|
|
132
|
+
await log(`❌ Insufficient memory: ${availableMB}MB available, swap: ${swapInfo}, total: ${totalAvailable}MB (${minMemoryMB}MB required)`);
|
|
133
|
+
|
|
134
|
+
if (!swapEnabled) {
|
|
135
|
+
await log(' Swap is disabled. Consider enabling swap:');
|
|
136
|
+
await log(' sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.dynamic_pager.plist');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { success: false, availableMB, required: minMemoryMB, swap: swapInfo, totalAvailable };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
await log(`🧠 Memory check: ${availableMB}MB available, swap: ${swapInfo}, total: ${totalAvailable}MB (${minMemoryMB}MB required) ✅`);
|
|
143
|
+
return { success: true, availableMB, required: minMemoryMB, swap: swapInfo, totalAvailable };
|
|
144
|
+
|
|
145
|
+
} catch (error) {
|
|
146
|
+
await log(`❌ macOS memory check failed: ${error.message}`);
|
|
147
|
+
return { success: false, availableMB: 0, error: error.message };
|
|
148
|
+
}
|
|
149
|
+
} else if (process.platform === 'win32') {
|
|
150
|
+
// Windows memory check using PowerShell
|
|
151
|
+
try {
|
|
152
|
+
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
|
+
|
|
154
|
+
const memInfo = JSON.parse(memOutput.toString());
|
|
155
|
+
const availableMB = memInfo.AvailableMB;
|
|
156
|
+
const pageFileTotalMB = memInfo.TotalPageFileMB || 0;
|
|
157
|
+
const pageFileFreeMB = memInfo.FreePageFileMB || 0;
|
|
158
|
+
const pageFileUsedMB = pageFileTotalMB - pageFileFreeMB;
|
|
159
|
+
|
|
160
|
+
let swapInfo;
|
|
161
|
+
if (pageFileTotalMB > 0) {
|
|
162
|
+
swapInfo = `${pageFileTotalMB}MB (${pageFileUsedMB}MB used)`;
|
|
163
|
+
} else {
|
|
164
|
+
swapInfo = 'none';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Calculate total available memory (RAM + page file)
|
|
168
|
+
const totalAvailable = availableMB + (pageFileFreeMB || 0);
|
|
169
|
+
|
|
170
|
+
// Check only total memory against the requirement
|
|
171
|
+
const success = totalAvailable >= minMemoryMB;
|
|
172
|
+
|
|
173
|
+
if (!success) {
|
|
174
|
+
await log(`❌ Insufficient memory: ${availableMB}MB available, page file: ${swapInfo}, total: ${totalAvailable}MB (${minMemoryMB}MB required)`);
|
|
175
|
+
await log(' Consider closing some applications or increasing virtual memory.');
|
|
176
|
+
return { success: false, availableMB, required: minMemoryMB, swap: swapInfo, totalAvailable };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await log(`🧠 Memory check: ${availableMB}MB available, page file: ${swapInfo}, total: ${totalAvailable}MB (${minMemoryMB}MB required) ✅`);
|
|
180
|
+
return { success: true, availableMB, required: minMemoryMB, swap: swapInfo, totalAvailable };
|
|
181
|
+
|
|
182
|
+
} catch (error) {
|
|
183
|
+
await log(`❌ Windows memory check failed: ${error.message}`);
|
|
184
|
+
return { success: false, availableMB: 0, error: error.message };
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
// Linux memory check using /proc/meminfo
|
|
188
|
+
try {
|
|
189
|
+
const meminfoContent = await fs.readFile('/proc/meminfo', 'utf8');
|
|
190
|
+
const lines = meminfoContent.split('\n');
|
|
191
|
+
|
|
192
|
+
const getValue = (key) => {
|
|
193
|
+
const line = lines.find(l => l.startsWith(key));
|
|
194
|
+
if (!line) return 0;
|
|
195
|
+
const match = line.match(/(\d+)/);
|
|
196
|
+
return match ? parseInt(match[1]) : 0;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// Get memory values in KB
|
|
200
|
+
const memFree = getValue('MemFree:');
|
|
201
|
+
const buffers = getValue('Buffers:');
|
|
202
|
+
const cached = getValue('Cached:');
|
|
203
|
+
const sReclaimable = getValue('SReclaimable:');
|
|
204
|
+
const swapTotal = getValue('SwapTotal:');
|
|
205
|
+
const swapFree = getValue('SwapFree:');
|
|
206
|
+
|
|
207
|
+
// Calculate available memory (similar to 'free' command)
|
|
208
|
+
const availableKB = memFree + buffers + cached + sReclaimable;
|
|
209
|
+
const availableMB = Math.floor(availableKB / 1024);
|
|
210
|
+
|
|
211
|
+
// Calculate swap info
|
|
212
|
+
const swapUsedKB = swapTotal - swapFree;
|
|
213
|
+
const swapMB = Math.floor(swapTotal / 1024);
|
|
214
|
+
const swapUsedMB = Math.floor(swapUsedKB / 1024);
|
|
215
|
+
const swapAvailableMB = Math.floor(swapFree / 1024);
|
|
216
|
+
|
|
217
|
+
let swapInfo;
|
|
218
|
+
if (swapTotal > 0) {
|
|
219
|
+
swapInfo = `${swapMB}MB (${swapUsedMB}MB used)`;
|
|
220
|
+
} else {
|
|
221
|
+
swapInfo = 'none';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Calculate total available memory (RAM + swap)
|
|
225
|
+
const totalAvailable = availableMB + swapAvailableMB;
|
|
226
|
+
|
|
227
|
+
// Check only total memory against the requirement
|
|
228
|
+
const success = totalAvailable >= minMemoryMB;
|
|
229
|
+
|
|
230
|
+
if (!success) {
|
|
231
|
+
await log(`❌ Insufficient memory: ${availableMB}MB available, swap: ${swapInfo}, total: ${totalAvailable}MB (${minMemoryMB}MB required)`);
|
|
232
|
+
|
|
233
|
+
if (swapTotal === 0) {
|
|
234
|
+
await log(' No swap configured. Consider adding swap:');
|
|
235
|
+
await log(' sudo fallocate -l 2G /swapfile');
|
|
236
|
+
await log(' sudo chmod 600 /swapfile');
|
|
237
|
+
await log(' sudo mkswap /swapfile');
|
|
238
|
+
await log(' sudo swapon /swapfile');
|
|
239
|
+
await log(' echo "/swapfile none swap sw 0 0" | sudo tee -a /etc/fstab');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return { success: false, availableMB, required: minMemoryMB, swap: swapInfo, totalAvailable };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
await log(`🧠 Memory check: ${availableMB}MB available, swap: ${swapInfo}, total: ${totalAvailable}MB (${minMemoryMB}MB required) ✅`);
|
|
246
|
+
return { success: true, availableMB, required: minMemoryMB, swap: swapInfo, totalAvailable };
|
|
247
|
+
|
|
248
|
+
} catch (error) {
|
|
249
|
+
await log(`❌ Linux memory check failed: ${error.message}`);
|
|
250
|
+
return { success: false, availableMB: 0, error: error.message };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// Keep checkMemory as an alias for checkRAM for backward compatibility
|
|
256
|
+
export const checkMemory = checkRAM;
|
|
257
|
+
|
|
258
|
+
// Function to get system resource snapshot
|
|
259
|
+
export const getResourceSnapshot = async () => {
|
|
260
|
+
try {
|
|
261
|
+
if (process.platform === 'darwin') {
|
|
262
|
+
// macOS resource snapshot
|
|
263
|
+
const vmStat = await $silent`vm_stat 2>/dev/null | head -10`;
|
|
264
|
+
const uptime = await $silent`uptime 2>/dev/null`;
|
|
265
|
+
const swap = await $silent`sysctl vm.swapusage 2>/dev/null`;
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
timestamp: new Date().toISOString(),
|
|
269
|
+
memory: vmStat.stdout.toString().trim(),
|
|
270
|
+
swap: swap.stdout.toString().trim(),
|
|
271
|
+
uptime: uptime.stdout.toString().trim()
|
|
272
|
+
};
|
|
273
|
+
} else {
|
|
274
|
+
// Linux resource snapshot
|
|
275
|
+
const memInfo = await $silent`grep -E "MemTotal|MemAvailable|MemFree|SwapTotal|SwapFree" /proc/meminfo 2>/dev/null`;
|
|
276
|
+
const loadAvg = await $silent`cat /proc/loadavg`;
|
|
277
|
+
const uptime = await $silent`uptime`;
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
timestamp: new Date().toISOString(),
|
|
281
|
+
memory: memInfo.stdout.toString().trim(),
|
|
282
|
+
load: loadAvg.stdout.toString().trim(),
|
|
283
|
+
uptime: uptime.stdout.toString().trim()
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
} catch (error) {
|
|
287
|
+
return {
|
|
288
|
+
timestamp: new Date().toISOString(),
|
|
289
|
+
error: `Failed to get resource snapshot: ${error.message}`
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// Combined system check function
|
|
295
|
+
export const checkSystem = async (requirements = {}, options = {}) => {
|
|
296
|
+
const {
|
|
297
|
+
minMemoryMB = 256,
|
|
298
|
+
minDiskSpaceMB = 500,
|
|
299
|
+
exitOnFailure = false
|
|
300
|
+
} = requirements;
|
|
301
|
+
|
|
302
|
+
// Note: log is passed through options to checkDiskSpace and checkRAM
|
|
303
|
+
const results = {
|
|
304
|
+
ram: null,
|
|
305
|
+
disk: null,
|
|
306
|
+
success: true
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// Check disk space (persistent memory)
|
|
310
|
+
results.disk = await checkDiskSpace(minDiskSpaceMB, options);
|
|
311
|
+
if (!results.disk.success) {
|
|
312
|
+
results.success = false;
|
|
313
|
+
if (exitOnFailure) {
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Check RAM (volatile memory)
|
|
319
|
+
results.ram = await checkRAM(minMemoryMB, options);
|
|
320
|
+
if (!results.ram.success) {
|
|
321
|
+
results.success = false;
|
|
322
|
+
if (exitOnFailure) {
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return results;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// CLI interface when run directly
|
|
331
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
332
|
+
|
|
333
|
+
// Create yargs instance with all options
|
|
334
|
+
const yargsInstance = yargs(hideBin(process.argv))
|
|
335
|
+
.scriptName('memory-check.mjs')
|
|
336
|
+
.usage('Usage: $0 [options]')
|
|
337
|
+
.option('min-memory', {
|
|
338
|
+
alias: 'm',
|
|
339
|
+
type: 'number',
|
|
340
|
+
description: 'Minimum required memory in MB',
|
|
341
|
+
default: 256
|
|
342
|
+
})
|
|
343
|
+
.option('min-disk-space', {
|
|
344
|
+
alias: 'd',
|
|
345
|
+
type: 'number',
|
|
346
|
+
description: 'Minimum required disk space in MB',
|
|
347
|
+
default: 500
|
|
348
|
+
})
|
|
349
|
+
.option('exit-on-failure', {
|
|
350
|
+
alias: 'e',
|
|
351
|
+
type: 'boolean',
|
|
352
|
+
description: 'Exit with code 1 if any check fails',
|
|
353
|
+
default: false
|
|
354
|
+
})
|
|
355
|
+
.option('json', {
|
|
356
|
+
alias: 'j',
|
|
357
|
+
type: 'boolean',
|
|
358
|
+
description: 'Output results as JSON',
|
|
359
|
+
default: false
|
|
360
|
+
})
|
|
361
|
+
.option('quiet', {
|
|
362
|
+
alias: 'q',
|
|
363
|
+
type: 'boolean',
|
|
364
|
+
description: 'Suppress detailed output (only show final status)',
|
|
365
|
+
default: false
|
|
366
|
+
})
|
|
367
|
+
.option('log-file', {
|
|
368
|
+
alias: 'l',
|
|
369
|
+
type: 'string',
|
|
370
|
+
description: 'Path to log file for output'
|
|
371
|
+
})
|
|
372
|
+
.help('h')
|
|
373
|
+
.alias('h', 'help');
|
|
374
|
+
|
|
375
|
+
// Check for help before parsing
|
|
376
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
377
|
+
yargsInstance.showHelp();
|
|
378
|
+
process.exit(0);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const argv = await yargsInstance.parseAsync();
|
|
382
|
+
|
|
383
|
+
// If we get here, help wasn't requested or yargs didn't handle it
|
|
384
|
+
// Set up logging based on options
|
|
385
|
+
if (argv.logFile) {
|
|
386
|
+
setLogFile(argv.logFile);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Create appropriate log function based on quiet mode
|
|
390
|
+
const log = argv.quiet ? async () => {} : libLog;
|
|
391
|
+
|
|
392
|
+
const results = await checkSystem(
|
|
393
|
+
{
|
|
394
|
+
minMemoryMB: argv.minMemory,
|
|
395
|
+
minDiskSpaceMB: argv.minDiskSpace,
|
|
396
|
+
exitOnFailure: argv.exitOnFailure
|
|
397
|
+
},
|
|
398
|
+
{ log }
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
if (argv.json) {
|
|
402
|
+
console.log(JSON.stringify(results, null, 2));
|
|
403
|
+
} else if (!argv.quiet) {
|
|
404
|
+
console.log('\n📊 System Check Summary:');
|
|
405
|
+
console.log('─'.repeat(40));
|
|
406
|
+
console.log(`RAM: ${results.ram.success ? '✅' : '❌'} ${results.ram.availableMB}MB available (${results.ram.required}MB required)`);
|
|
407
|
+
console.log(`Disk: ${results.disk.success ? '✅' : '❌'} ${results.disk.availableMB}MB available (${results.disk.required}MB required)`);
|
|
408
|
+
console.log(`Overall: ${results.success ? '✅ All checks passed' : '❌ Some checks failed'}`);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (!results.success && argv.exitOnFailure) {
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Restore CI if it was set (at the very end, after yargs has processed everything)
|
|
417
|
+
if (originalCI !== undefined) {
|
|
418
|
+
process.env.CI = originalCI;
|
|
419
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Unified model mapping module
|
|
5
|
+
* Provides a single source of truth for model name mapping across all tools
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Claude models (Anthropic API)
|
|
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
|
|
13
|
+
'haiku-3-5': 'claude-3-5-haiku-20241022', // Haiku 3.5
|
|
14
|
+
'haiku-3': 'claude-3-haiku-20240307', // Haiku 3
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Agent models (OpenCode API via agent CLI)
|
|
18
|
+
export const agentModels = {
|
|
19
|
+
'grok': 'opencode/grok-code',
|
|
20
|
+
'grok-code': 'opencode/grok-code',
|
|
21
|
+
'grok-code-fast-1': 'opencode/grok-code',
|
|
22
|
+
'big-pickle': 'opencode/big-pickle',
|
|
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',
|
|
27
|
+
'gemini-3-pro': 'google/gemini-3-pro',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// OpenCode models (OpenCode API)
|
|
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',
|
|
39
|
+
'grok-code': 'opencode/grok-code',
|
|
40
|
+
'grok-code-fast-1': 'opencode/grok-code',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Codex models (OpenAI API)
|
|
44
|
+
export const codexModels = {
|
|
45
|
+
'gpt5': 'gpt-5',
|
|
46
|
+
'gpt5-codex': 'gpt-5-codex',
|
|
47
|
+
'o3': 'o3',
|
|
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',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Map model name to full model ID for a specific tool
|
|
58
|
+
* @param {string} tool - The tool name (claude, agent, opencode, codex)
|
|
59
|
+
* @param {string} model - The model name or alias
|
|
60
|
+
* @returns {string} The full model ID
|
|
61
|
+
*/
|
|
62
|
+
export const mapModelForTool = (tool, model) => {
|
|
63
|
+
switch (tool) {
|
|
64
|
+
case 'claude':
|
|
65
|
+
return claudeModels[model] || model;
|
|
66
|
+
case 'agent':
|
|
67
|
+
return agentModels[model] || model;
|
|
68
|
+
case 'opencode':
|
|
69
|
+
return opencodeModels[model] || model;
|
|
70
|
+
case 'codex':
|
|
71
|
+
return codexModels[model] || model;
|
|
72
|
+
default:
|
|
73
|
+
return model;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Validate if a model is compatible with a tool
|
|
79
|
+
* @param {string} tool - The tool name (claude, agent, opencode, codex)
|
|
80
|
+
* @param {string} model - The model name or alias
|
|
81
|
+
* @returns {boolean} True if the model is compatible with the tool
|
|
82
|
+
*/
|
|
83
|
+
export const isModelCompatibleWithTool = (tool, model) => {
|
|
84
|
+
const mappedModel = mapModelForTool(tool, model);
|
|
85
|
+
|
|
86
|
+
switch (tool) {
|
|
87
|
+
case 'claude':
|
|
88
|
+
// Claude only accepts models in the claude- namespace
|
|
89
|
+
return mappedModel.startsWith('claude-');
|
|
90
|
+
case 'agent':
|
|
91
|
+
// Agent accepts any model with provider prefix (opencode/, anthropic/, etc.)
|
|
92
|
+
// or models in the agentModels list
|
|
93
|
+
return mappedModel.includes('/') || Object.keys(agentModels).includes(model);
|
|
94
|
+
case 'opencode':
|
|
95
|
+
// OpenCode accepts models with provider prefix
|
|
96
|
+
return mappedModel.includes('/') || Object.keys(opencodeModels).includes(model);
|
|
97
|
+
case 'codex':
|
|
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-');
|
|
103
|
+
default:
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get a list of valid model names for a tool
|
|
110
|
+
* @param {string} tool - The tool name
|
|
111
|
+
* @returns {string[]} Array of valid model names
|
|
112
|
+
*/
|
|
113
|
+
export const getValidModelsForTool = (tool) => {
|
|
114
|
+
switch (tool) {
|
|
115
|
+
case 'claude':
|
|
116
|
+
return Object.keys(claudeModels);
|
|
117
|
+
case 'agent':
|
|
118
|
+
return Object.keys(agentModels);
|
|
119
|
+
case 'opencode':
|
|
120
|
+
return Object.keys(opencodeModels);
|
|
121
|
+
case 'codex':
|
|
122
|
+
return Object.keys(codexModels);
|
|
123
|
+
default:
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Validate tool-model compatibility and throw descriptive error if invalid
|
|
130
|
+
* @param {string} tool - The tool name
|
|
131
|
+
* @param {string} model - The model name
|
|
132
|
+
* @throws {Error} If the model is not compatible with the tool
|
|
133
|
+
*/
|
|
134
|
+
export const validateToolModelCompatibility = (tool, model) => {
|
|
135
|
+
if (!isModelCompatibleWithTool(tool, model)) {
|
|
136
|
+
const validModels = getValidModelsForTool(tool);
|
|
137
|
+
const mappedModel = mapModelForTool(tool, model);
|
|
138
|
+
|
|
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
|
+
);
|
|
144
|
+
}
|
|
145
|
+
};
|