feat: claude-auto-retry v0.1.0
Auto-retry Claude Code sessions when hitting Anthropic subscription rate limits. Uses tmux monitoring + send-keys to detect rate limit messages, wait for reset, and send "continue" automatically. Zero dependencies, zero workflow change. - Shell wrapper intercepts `claude` command transparently - Background monitor polls tmux pane for rate limit patterns - Timezone-aware reset time parsing with DST safety - Safe send-keys with foreground process verification - --print mode: buffers output, retries cleanly for pipes - Config validation prevents bad values from causing crashes - 59 tests, 0 dependencies Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { describe, it, afterEach } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { writeFile, readFile, unlink } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { injectWrapper, removeWrapper, MARKER_START, MARKER_END } from '../bin/cli.js';
|
||||
|
||||
describe('injectWrapper', () => {
|
||||
const testFile = join(tmpdir(), `car-rc-test-${Date.now()}`);
|
||||
afterEach(async () => { try { await unlink(testFile); } catch {} });
|
||||
|
||||
it('adds wrapper to empty file', async () => {
|
||||
await writeFile(testFile, '');
|
||||
await injectWrapper(testFile, '/path/to/launcher.js');
|
||||
const content = await readFile(testFile, 'utf-8');
|
||||
assert.ok(content.includes(MARKER_START));
|
||||
assert.ok(content.includes(MARKER_END));
|
||||
assert.ok(content.includes('/path/to/launcher.js'));
|
||||
});
|
||||
it('adds wrapper to file with existing content', async () => {
|
||||
await writeFile(testFile, 'export PATH=$HOME/bin:$PATH\n');
|
||||
await injectWrapper(testFile, '/path/to/launcher.js');
|
||||
const content = await readFile(testFile, 'utf-8');
|
||||
assert.ok(content.includes('export PATH'));
|
||||
assert.ok(content.includes(MARKER_START));
|
||||
});
|
||||
it('replaces existing wrapper', async () => {
|
||||
await writeFile(testFile, `before\n${MARKER_START}\nold stuff\n${MARKER_END}\nafter\n`);
|
||||
await injectWrapper(testFile, '/new/path/launcher.js');
|
||||
const content = await readFile(testFile, 'utf-8');
|
||||
assert.ok(content.includes('/new/path'));
|
||||
assert.ok(!content.includes('old stuff'));
|
||||
assert.ok(content.includes('before'));
|
||||
assert.ok(content.includes('after'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeWrapper', () => {
|
||||
const testFile = join(tmpdir(), `car-rm-test-${Date.now()}`);
|
||||
afterEach(async () => { try { await unlink(testFile); } catch {} });
|
||||
|
||||
it('removes wrapper and preserves surrounding content', async () => {
|
||||
await writeFile(testFile, `before\n${MARKER_START}\nwrapper stuff\n${MARKER_END}\nafter\n`);
|
||||
await removeWrapper(testFile);
|
||||
const content = await readFile(testFile, 'utf-8');
|
||||
assert.ok(!content.includes(MARKER_START));
|
||||
assert.ok(content.includes('before'));
|
||||
assert.ok(content.includes('after'));
|
||||
});
|
||||
it('does nothing when no wrapper present', async () => {
|
||||
await writeFile(testFile, 'just normal content\n');
|
||||
await removeWrapper(testFile);
|
||||
const content = await readFile(testFile, 'utf-8');
|
||||
assert.equal(content, 'just normal content\n');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user