commit
This commit is contained in:
17
README.md
17
README.md
@@ -25,9 +25,9 @@ The full screen clears are 35x larger than incremental line clears - these are t
|
|||||||
claude-chill sits between your terminal and Claude Code:
|
claude-chill sits between your terminal and Claude Code:
|
||||||
|
|
||||||
1. **Intercepts sync blocks** - Catches those massive atomic updates
|
1. **Intercepts sync blocks** - Catches those massive atomic updates
|
||||||
2. **Truncates output** - Sends only the last N lines (default: 100) to your terminal
|
2. **Truncates full screen clears** - Only the 45% that are full redraws (94.5 KB avg) get truncated to the last N lines (default: 100). The 55% incremental updates (2.7 KB avg) pass through unchanged.
|
||||||
3. **Preserves history** - Stores the full content in a buffer
|
3. **Preserves history** - Accumulates content in a buffer. Clears on full screen clear, accumulates otherwise.
|
||||||
4. **Enables lookback** - Press a key to dump the buffer, then scroll up
|
4. **Enables lookback** - Press a key to pause Claude and view the full history buffer
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -43,9 +43,16 @@ claude-chill -- claude --verbose # Use -- for command flags
|
|||||||
claude-chill -l 50 -- claude # Set max lines to 50
|
claude-chill -l 50 -- claude # Set max lines to 50
|
||||||
```
|
```
|
||||||
|
|
||||||
## Lookback
|
## Lookback Mode
|
||||||
|
|
||||||
Press `Ctrl+Shift+J` to dump the history buffer to terminal. Scroll up to see it. The next update from Claude will resume normal display.
|
Press `Ctrl+Shift+J` (or your configured key) to enter lookback mode:
|
||||||
|
|
||||||
|
1. **Claude pauses** - Output from Claude is cached, input is blocked
|
||||||
|
2. **History dumps** - The full history buffer is written to your terminal
|
||||||
|
3. **Scroll freely** - Use your terminal's scrollback to review everything
|
||||||
|
4. **Exit** - Press the lookback key again or `Ctrl+C` to resume
|
||||||
|
|
||||||
|
When you exit lookback mode, any cached output is processed and the current state is displayed.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ pub struct Proxy {
|
|||||||
history: LineBuffer,
|
history: LineBuffer,
|
||||||
sync_buffer: Vec<u8>,
|
sync_buffer: Vec<u8>,
|
||||||
in_sync_block: bool,
|
in_sync_block: bool,
|
||||||
|
in_lookback_mode: bool,
|
||||||
|
lookback_cache: Vec<u8>,
|
||||||
input_buffer: Vec<u8>,
|
input_buffer: Vec<u8>,
|
||||||
output_buffer: Vec<u8>,
|
output_buffer: Vec<u8>,
|
||||||
sync_start_finder: memmem::Finder<'static>,
|
sync_start_finder: memmem::Finder<'static>,
|
||||||
@@ -114,6 +116,8 @@ impl Proxy {
|
|||||||
original_termios,
|
original_termios,
|
||||||
sync_buffer: Vec::with_capacity(SYNC_BUFFER_CAPACITY),
|
sync_buffer: Vec::with_capacity(SYNC_BUFFER_CAPACITY),
|
||||||
in_sync_block: false,
|
in_sync_block: false,
|
||||||
|
in_lookback_mode: false,
|
||||||
|
lookback_cache: Vec::new(),
|
||||||
input_buffer: Vec::with_capacity(INPUT_BUFFER_CAPACITY),
|
input_buffer: Vec::with_capacity(INPUT_BUFFER_CAPACITY),
|
||||||
output_buffer: Vec::with_capacity(OUTPUT_BUFFER_CAPACITY),
|
output_buffer: Vec::with_capacity(OUTPUT_BUFFER_CAPACITY),
|
||||||
sync_start_finder: memmem::Finder::new(SYNC_START),
|
sync_start_finder: memmem::Finder::new(SYNC_START),
|
||||||
@@ -197,6 +201,11 @@ impl Proxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn process_output(&mut self, data: &[u8], stdout_fd: i32) -> Result<()> {
|
fn process_output(&mut self, data: &[u8], stdout_fd: i32) -> Result<()> {
|
||||||
|
if self.in_lookback_mode {
|
||||||
|
self.lookback_cache.extend_from_slice(data);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let mut pos = 0;
|
let mut pos = 0;
|
||||||
|
|
||||||
while pos < data.len() {
|
while pos < data.len() {
|
||||||
@@ -261,36 +270,81 @@ impl Proxy {
|
|||||||
|
|
||||||
fn process_input(&mut self, data: &[u8], stdout_fd: i32) -> Result<()> {
|
fn process_input(&mut self, data: &[u8], stdout_fd: i32) -> Result<()> {
|
||||||
let master_fd = self.pty_master.as_raw_fd();
|
let master_fd = self.pty_master.as_raw_fd();
|
||||||
|
|
||||||
for &byte in data {
|
for &byte in data {
|
||||||
|
if self.in_lookback_mode && byte == 0x03 {
|
||||||
|
self.input_buffer.clear();
|
||||||
|
self.exit_lookback_mode(stdout_fd)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
self.input_buffer.push(byte);
|
self.input_buffer.push(byte);
|
||||||
|
|
||||||
if self.input_buffer.len() > self.config.lookback_sequence.len() {
|
if self.input_buffer.len() > self.config.lookback_sequence.len() {
|
||||||
let excess = self.input_buffer.len() - self.config.lookback_sequence.len();
|
let excess = self.input_buffer.len() - self.config.lookback_sequence.len();
|
||||||
|
if !self.in_lookback_mode {
|
||||||
write_all_raw(master_fd, &self.input_buffer[..excess])?;
|
write_all_raw(master_fd, &self.input_buffer[..excess])?;
|
||||||
|
}
|
||||||
self.input_buffer.drain(..excess);
|
self.input_buffer.drain(..excess);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.input_buffer == self.config.lookback_sequence {
|
if self.input_buffer == self.config.lookback_sequence {
|
||||||
self.enter_lookback_mode(stdout_fd)?;
|
|
||||||
self.input_buffer.clear();
|
self.input_buffer.clear();
|
||||||
|
if self.in_lookback_mode {
|
||||||
|
self.exit_lookback_mode(stdout_fd)?;
|
||||||
|
} else {
|
||||||
|
self.enter_lookback_mode()?;
|
||||||
|
}
|
||||||
} else if !self
|
} else if !self
|
||||||
.config
|
.config
|
||||||
.lookback_sequence
|
.lookback_sequence
|
||||||
.starts_with(&self.input_buffer)
|
.starts_with(&self.input_buffer)
|
||||||
{
|
{
|
||||||
|
if !self.in_lookback_mode {
|
||||||
write_all_raw(master_fd, &self.input_buffer)?;
|
write_all_raw(master_fd, &self.input_buffer)?;
|
||||||
|
}
|
||||||
self.input_buffer.clear();
|
self.input_buffer.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enter_lookback_mode(&mut self, stdout_fd: i32) -> Result<()> {
|
fn enter_lookback_mode(&mut self) -> Result<()> {
|
||||||
let header = b"\x1b[7m--- LOOKBACK: scroll up to see history ---\x1b[0m\r\n";
|
self.in_lookback_mode = true;
|
||||||
write_all_raw(stdout_fd, header)?;
|
self.lookback_cache.clear();
|
||||||
|
|
||||||
self.output_buffer.clear();
|
self.output_buffer.clear();
|
||||||
self.history.append_all(&mut self.output_buffer);
|
self.history.append_all(&mut self.output_buffer);
|
||||||
|
|
||||||
|
let stdout_fd = io::stdout().as_raw_fd();
|
||||||
|
write_all_raw(stdout_fd, CLEAR_SCREEN)?;
|
||||||
|
write_all_raw(stdout_fd, CURSOR_HOME)?;
|
||||||
write_all_raw(stdout_fd, &self.output_buffer)?;
|
write_all_raw(stdout_fd, &self.output_buffer)?;
|
||||||
|
|
||||||
|
let exit_msg = format!(
|
||||||
|
"\r\n\x1b[7m--- LOOKBACK MODE: press {} or Ctrl+C to exit ---\x1b[0m\r\n",
|
||||||
|
self.config.lookback_key
|
||||||
|
);
|
||||||
|
write_all_raw(stdout_fd, exit_msg.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_lookback_mode(&mut self, stdout_fd: i32) -> Result<()> {
|
||||||
|
self.in_lookback_mode = false;
|
||||||
|
|
||||||
|
let cached = std::mem::take(&mut self.lookback_cache);
|
||||||
|
if !cached.is_empty() {
|
||||||
|
self.process_output(&cached, stdout_fd)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.output_buffer.clear();
|
||||||
|
self.history.append_all(&mut self.output_buffer);
|
||||||
|
|
||||||
|
write_all_raw(stdout_fd, CLEAR_SCREEN)?;
|
||||||
|
write_all_raw(stdout_fd, CURSOR_HOME)?;
|
||||||
|
write_all_raw(stdout_fd, &self.output_buffer)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user