Merge pull request #306 from Waaiez/fix/linux-claude-usage

fix: add Linux support for Claude usage service
This commit is contained in:
Web Dev Cody
2025-12-30 10:13:50 -05:00
committed by GitHub
2 changed files with 79 additions and 3 deletions

View File

@@ -179,8 +179,13 @@ export class ClaudeUsageService {
if (!settled) {
settled = true;
ptyProcess.kill();
// Don't fail if we have data - return it instead
if (output.includes('Current session')) {
resolve(output);
} else {
reject(new Error('Command timed out'));
}
}
}, this.timeout);
ptyProcess.onData((data) => {
@@ -193,6 +198,13 @@ export class ClaudeUsageService {
setTimeout(() => {
if (!settled) {
ptyProcess.write('\x1b'); // Send escape key
// Fallback: if ESC doesn't exit (Linux), use SIGTERM after 2s
setTimeout(() => {
if (!settled) {
ptyProcess.kill('SIGTERM');
}
}, 2000);
}
}, 2000);
}

View File

@@ -485,7 +485,7 @@ Resets in 2h
await expect(promise).rejects.toThrow('Authentication required');
});
it('should handle timeout', async () => {
it('should handle timeout with no data', async () => {
vi.useFakeTimers();
mockSpawnProcess.stdout = {
@@ -619,7 +619,7 @@ Resets in 2h
await expect(promise).rejects.toThrow('Authentication required');
});
it('should handle timeout on Windows', async () => {
it('should handle timeout with no data on Windows', async () => {
vi.useFakeTimers();
const windowsService = new ClaudeUsageService();
@@ -640,5 +640,69 @@ Resets in 2h
vi.useRealTimers();
});
it('should return data on timeout if data was captured', async () => {
vi.useFakeTimers();
const windowsService = new ClaudeUsageService();
let dataCallback: Function | undefined;
const mockPty = {
onData: vi.fn((callback: Function) => {
dataCallback = callback;
}),
onExit: vi.fn(),
write: vi.fn(),
kill: vi.fn(),
};
vi.mocked(pty.spawn).mockReturnValue(mockPty as any);
const promise = windowsService.fetchUsageData();
// Simulate receiving usage data
dataCallback!('Current session\n65% left\nResets in 2h');
// Advance time past timeout (30 seconds)
vi.advanceTimersByTime(31000);
// Should resolve with data instead of rejecting
const result = await promise;
expect(result.sessionPercentage).toBe(35); // 100 - 65
expect(mockPty.kill).toHaveBeenCalled();
vi.useRealTimers();
});
it('should send SIGTERM after ESC if process does not exit', async () => {
vi.useFakeTimers();
const windowsService = new ClaudeUsageService();
let dataCallback: Function | undefined;
const mockPty = {
onData: vi.fn((callback: Function) => {
dataCallback = callback;
}),
onExit: vi.fn(),
write: vi.fn(),
kill: vi.fn(),
};
vi.mocked(pty.spawn).mockReturnValue(mockPty as any);
windowsService.fetchUsageData();
// Simulate seeing usage data
dataCallback!('Current session\n65% left');
// Advance 2s to trigger ESC
vi.advanceTimersByTime(2100);
expect(mockPty.write).toHaveBeenCalledWith('\x1b');
// Advance another 2s to trigger SIGTERM fallback
vi.advanceTimersByTime(2100);
expect(mockPty.kill).toHaveBeenCalledWith('SIGTERM');
vi.useRealTimers();
});
});
});