@link-assistant/hive-mind 0.51.4 → 0.51.6
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 +29 -0
- package/package.json +1 -1
- package/src/claude-limits.lib.mjs +111 -1
- package/src/telegram-bot.mjs +7 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 0.51.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9ee79c8: fix(ci): Add timeout, verbose diagnostics, and pre-fetch caching for Docker ARM64 builds
|
|
8
|
+
|
|
9
|
+
Addresses issue #998 where Docker Publish (linux/arm64) was stuck for >1.5 hours due to slow Homebrew bottle downloads on GitHub's ARM64 runners.
|
|
10
|
+
|
|
11
|
+
Changes:
|
|
12
|
+
- Added 90-minute timeout to docker-publish jobs to prevent indefinite hangs
|
|
13
|
+
- Switched from ubuntu-24.04-arm to ubuntu-22.04-arm for better network performance
|
|
14
|
+
- Added documentation comments about known ARM64 runner issues
|
|
15
|
+
- Added Homebrew verbose mode (`HOMEBREW_VERBOSE=1`) for detailed diagnostics
|
|
16
|
+
- Added `brew fetch --deps --retry` to pre-download bottles before installation
|
|
17
|
+
- Added timing measurements for fetch and install steps
|
|
18
|
+
- Updated case study with diagnostic approach
|
|
19
|
+
|
|
20
|
+
Root cause: GitHub's ubuntu-24.04-arm runners have known network performance issues (actions/runner-images#11790, actions/partner-runner-images#101). The ARM64 build was stuck downloading Homebrew bottles for PHP dependencies at extremely slow speeds.
|
|
21
|
+
|
|
22
|
+
See docs/case-studies/issue-998/README.md for detailed analysis.
|
|
23
|
+
|
|
24
|
+
## 0.51.5
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- 1a17f74: feat: add disk space information to /limits command
|
|
29
|
+
|
|
30
|
+
Adds free disk space percentage and size information to the Telegram bot's /limits command output, allowing users to monitor disk usage alongside Claude API limits and plan issue execution accordingly.
|
|
31
|
+
|
|
3
32
|
## 0.51.4
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -4,9 +4,13 @@
|
|
|
4
4
|
* Provides functions to fetch and parse Claude usage limits via OAuth API
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { exec } from 'node:child_process';
|
|
7
8
|
import { readFile } from 'node:fs/promises';
|
|
8
9
|
import { homedir } from 'node:os';
|
|
9
10
|
import { join } from 'node:path';
|
|
11
|
+
import { promisify } from 'node:util';
|
|
12
|
+
|
|
13
|
+
const execAsync = promisify(exec);
|
|
10
14
|
|
|
11
15
|
/**
|
|
12
16
|
* Default path to Claude credentials file
|
|
@@ -127,6 +131,101 @@ function formatCurrentTime() {
|
|
|
127
131
|
return `${month} ${day}, ${hour12}:${minutes.toString().padStart(2, '0')}${ampm} UTC`;
|
|
128
132
|
}
|
|
129
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Format bytes into human-readable size
|
|
136
|
+
* @param {number} bytes - Size in bytes
|
|
137
|
+
* @returns {string} Formatted size (e.g., "19.3 GB")
|
|
138
|
+
*/
|
|
139
|
+
function formatBytes(bytes) {
|
|
140
|
+
if (bytes === 0) return '0 B';
|
|
141
|
+
const k = 1024;
|
|
142
|
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
143
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
144
|
+
const value = bytes / Math.pow(k, i);
|
|
145
|
+
// Use 1 decimal place for GB and above, none for smaller units
|
|
146
|
+
const decimals = i >= 3 ? 1 : 0;
|
|
147
|
+
return `${value.toFixed(decimals)} ${sizes[i]}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get disk space information for the current filesystem
|
|
152
|
+
* Returns total, used, available space and usage percentage
|
|
153
|
+
*
|
|
154
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
155
|
+
* @returns {Object} Object with success boolean, and either disk space data or error message
|
|
156
|
+
*/
|
|
157
|
+
export async function getDiskSpaceInfo(verbose = false) {
|
|
158
|
+
try {
|
|
159
|
+
let totalMB, usedMB, availableMB, usedPercentage;
|
|
160
|
+
|
|
161
|
+
if (process.platform === 'darwin') {
|
|
162
|
+
// macOS: use df with 1024-byte blocks and parse
|
|
163
|
+
const { stdout } = await execAsync("df -k . 2>/dev/null | tail -1 | awk '{print $2, $3, $4}'");
|
|
164
|
+
const [totalKB, usedKB, availableKB] = stdout.trim().split(/\s+/).map(Number);
|
|
165
|
+
totalMB = Math.round(totalKB / 1024);
|
|
166
|
+
usedMB = Math.round(usedKB / 1024);
|
|
167
|
+
availableMB = Math.round(availableKB / 1024);
|
|
168
|
+
} else if (process.platform === 'win32') {
|
|
169
|
+
// Windows: use PowerShell to get drive info
|
|
170
|
+
const { stdout } = await execAsync('powershell -Command "$drive = (Get-Location).Drive; $info = Get-PSDrive -Name $drive.Name; Write-Output \\"$($info.Used) $($info.Free)\\""');
|
|
171
|
+
const [usedBytes, freeBytes] = stdout.trim().split(/\s+/).map(Number);
|
|
172
|
+
const totalBytes = usedBytes + freeBytes;
|
|
173
|
+
totalMB = Math.round(totalBytes / (1024 * 1024));
|
|
174
|
+
usedMB = Math.round(usedBytes / (1024 * 1024));
|
|
175
|
+
availableMB = Math.round(freeBytes / (1024 * 1024));
|
|
176
|
+
} else {
|
|
177
|
+
// Linux: use df with megabyte blocks
|
|
178
|
+
const { stdout } = await execAsync("df -BM . 2>/dev/null | tail -1 | awk '{print $2, $3, $4}'");
|
|
179
|
+
const parts = stdout
|
|
180
|
+
.trim()
|
|
181
|
+
.split(/\s+/)
|
|
182
|
+
.map(s => parseInt(s.replace('M', '')));
|
|
183
|
+
[totalMB, usedMB, availableMB] = parts;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (isNaN(totalMB) || isNaN(usedMB) || isNaN(availableMB)) {
|
|
187
|
+
return {
|
|
188
|
+
success: false,
|
|
189
|
+
error: 'Failed to parse disk space information',
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Calculate used percentage (rounded to nearest integer)
|
|
194
|
+
usedPercentage = Math.round((usedMB / totalMB) * 100);
|
|
195
|
+
// Free percentage is the inverse
|
|
196
|
+
const freePercentage = 100 - usedPercentage;
|
|
197
|
+
|
|
198
|
+
if (verbose) {
|
|
199
|
+
console.log(`[VERBOSE] /limits disk space: ${availableMB}MB free of ${totalMB}MB total (${freePercentage}% free)`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
success: true,
|
|
204
|
+
diskSpace: {
|
|
205
|
+
totalMB,
|
|
206
|
+
usedMB,
|
|
207
|
+
availableMB,
|
|
208
|
+
totalBytes: totalMB * 1024 * 1024,
|
|
209
|
+
usedBytes: usedMB * 1024 * 1024,
|
|
210
|
+
availableBytes: availableMB * 1024 * 1024,
|
|
211
|
+
usedPercentage,
|
|
212
|
+
freePercentage,
|
|
213
|
+
totalFormatted: formatBytes(totalMB * 1024 * 1024),
|
|
214
|
+
usedFormatted: formatBytes(usedMB * 1024 * 1024),
|
|
215
|
+
availableFormatted: formatBytes(availableMB * 1024 * 1024),
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (verbose) {
|
|
220
|
+
console.error('[VERBOSE] /limits disk space error:', error);
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
success: false,
|
|
224
|
+
error: `Failed to get disk space info: ${error.message}`,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
130
229
|
/**
|
|
131
230
|
* Get Claude usage limits by calling the Anthropic OAuth usage API
|
|
132
231
|
* This approach is more reliable than trying to parse CLI output
|
|
@@ -285,15 +384,25 @@ export function calculateTimePassedPercentage(resetsAt, periodHours) {
|
|
|
285
384
|
/**
|
|
286
385
|
* Format Claude usage data into a Telegram-friendly message
|
|
287
386
|
* @param {Object} usage - The usage object from getClaudeUsageLimits
|
|
387
|
+
* @param {Object} diskSpace - Optional disk space info from getDiskSpaceInfo
|
|
288
388
|
* @returns {string} Formatted message
|
|
289
389
|
*/
|
|
290
|
-
export function formatUsageMessage(usage) {
|
|
390
|
+
export function formatUsageMessage(usage, diskSpace = null) {
|
|
291
391
|
// Use code block for monospace font to align progress bars properly
|
|
292
392
|
let message = '```\n';
|
|
293
393
|
|
|
294
394
|
// Show current time
|
|
295
395
|
message += `Current time: ${formatCurrentTime()}\n\n`;
|
|
296
396
|
|
|
397
|
+
// Disk space section (if provided)
|
|
398
|
+
if (diskSpace) {
|
|
399
|
+
message += 'Disk space\n';
|
|
400
|
+
// Show free percentage with progress bar (inverted - showing free space)
|
|
401
|
+
const freeBar = getProgressBar(diskSpace.freePercentage);
|
|
402
|
+
message += `${freeBar} ${diskSpace.freePercentage}% free\n`;
|
|
403
|
+
message += `${diskSpace.availableFormatted} free of ${diskSpace.totalFormatted}\n\n`;
|
|
404
|
+
}
|
|
405
|
+
|
|
297
406
|
// Current session (five_hour)
|
|
298
407
|
message += 'Current session\n';
|
|
299
408
|
if (usage.currentSession.percentage !== null) {
|
|
@@ -383,6 +492,7 @@ export function formatUsageMessage(usage) {
|
|
|
383
492
|
|
|
384
493
|
export default {
|
|
385
494
|
getClaudeUsageLimits,
|
|
495
|
+
getDiskSpaceInfo,
|
|
386
496
|
getProgressBar,
|
|
387
497
|
calculateTimePassedPercentage,
|
|
388
498
|
formatUsageMessage,
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -45,7 +45,7 @@ const { parseGitHubUrl } = await import('./github.lib.mjs');
|
|
|
45
45
|
const { validateModelName } = await import('./model-validation.lib.mjs');
|
|
46
46
|
|
|
47
47
|
// Import Claude limits library for /limits command
|
|
48
|
-
const { getClaudeUsageLimits, formatUsageMessage } = await import('./claude-limits.lib.mjs');
|
|
48
|
+
const { getClaudeUsageLimits, getDiskSpaceInfo, formatUsageMessage } = await import('./claude-limits.lib.mjs');
|
|
49
49
|
|
|
50
50
|
// Import version info library for /version command
|
|
51
51
|
const { getVersionInfo, formatVersionMessage } = await import('./version-info.lib.mjs');
|
|
@@ -844,12 +844,12 @@ bot.command('limits', async ctx => {
|
|
|
844
844
|
}
|
|
845
845
|
|
|
846
846
|
// Send "fetching" message to indicate work is in progress
|
|
847
|
-
const fetchingMessage = await ctx.reply('🔄 Fetching
|
|
847
|
+
const fetchingMessage = await ctx.reply('🔄 Fetching usage limits...', {
|
|
848
848
|
reply_to_message_id: ctx.message.message_id,
|
|
849
849
|
});
|
|
850
850
|
|
|
851
|
-
// Get the usage limits
|
|
852
|
-
const result = await getClaudeUsageLimits(VERBOSE);
|
|
851
|
+
// Get the usage limits and disk space info in parallel
|
|
852
|
+
const [result, diskSpaceResult] = await Promise.all([getClaudeUsageLimits(VERBOSE), getDiskSpaceInfo(VERBOSE)]);
|
|
853
853
|
|
|
854
854
|
if (!result.success) {
|
|
855
855
|
// Edit the fetching message to show the error
|
|
@@ -860,7 +860,9 @@ bot.command('limits', async ctx => {
|
|
|
860
860
|
}
|
|
861
861
|
|
|
862
862
|
// Format and edit the fetching message with the results
|
|
863
|
-
|
|
863
|
+
// Pass disk space info if available (non-critical if it fails)
|
|
864
|
+
const diskSpace = diskSpaceResult.success ? diskSpaceResult.diskSpace : null;
|
|
865
|
+
const message = '📊 *Usage Limits*\n\n' + formatUsageMessage(result.usage, diskSpace);
|
|
864
866
|
await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, message, {
|
|
865
867
|
parse_mode: 'Markdown',
|
|
866
868
|
});
|