1aad1e0313
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>
57 lines
2.4 KiB
JavaScript
57 lines
2.4 KiB
JavaScript
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');
|
|
});
|
|
});
|