From e9f2b0368486ca1a4909b80acc83c13221fcd893 Mon Sep 17 00:00:00 2001 From: Dave Beesley Date: Fri, 16 Jan 2026 23:14:05 -0700 Subject: [PATCH] commit --- README.md | 17 +++++--- crates/claude-chill/src/proxy.rs | 66 +++++++++++++++++++++++++++++--- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 4db32dc..ba9cf74 100644 --- a/README.md +++ b/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: 1. **Intercepts sync blocks** - Catches those massive atomic updates -2. **Truncates output** - Sends only the last N lines (default: 100) to your terminal -3. **Preserves history** - Stores the full content in a buffer -4. **Enables lookback** - Press a key to dump the buffer, then scroll up +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** - Accumulates content in a buffer. Clears on full screen clear, accumulates otherwise. +4. **Enables lookback** - Press a key to pause Claude and view the full history buffer ## Installation @@ -43,9 +43,16 @@ claude-chill -- claude --verbose # Use -- for command flags 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 diff --git a/crates/claude-chill/src/proxy.rs b/crates/claude-chill/src/proxy.rs index 1ee06b4..d59de21 100644 --- a/crates/claude-chill/src/proxy.rs +++ b/crates/claude-chill/src/proxy.rs @@ -57,6 +57,8 @@ pub struct Proxy { history: LineBuffer, sync_buffer: Vec, in_sync_block: bool, + in_lookback_mode: bool, + lookback_cache: Vec, input_buffer: Vec, output_buffer: Vec, sync_start_finder: memmem::Finder<'static>, @@ -114,6 +116,8 @@ impl Proxy { original_termios, sync_buffer: Vec::with_capacity(SYNC_BUFFER_CAPACITY), in_sync_block: false, + in_lookback_mode: false, + lookback_cache: Vec::new(), input_buffer: Vec::with_capacity(INPUT_BUFFER_CAPACITY), output_buffer: Vec::with_capacity(OUTPUT_BUFFER_CAPACITY), 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<()> { + if self.in_lookback_mode { + self.lookback_cache.extend_from_slice(data); + return Ok(()); + } + let mut pos = 0; while pos < data.len() { @@ -261,36 +270,81 @@ impl Proxy { fn process_input(&mut self, data: &[u8], stdout_fd: i32) -> Result<()> { let master_fd = self.pty_master.as_raw_fd(); + 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); if self.input_buffer.len() > self.config.lookback_sequence.len() { let excess = self.input_buffer.len() - self.config.lookback_sequence.len(); - write_all_raw(master_fd, &self.input_buffer[..excess])?; + if !self.in_lookback_mode { + write_all_raw(master_fd, &self.input_buffer[..excess])?; + } self.input_buffer.drain(..excess); } if self.input_buffer == self.config.lookback_sequence { - self.enter_lookback_mode(stdout_fd)?; self.input_buffer.clear(); + if self.in_lookback_mode { + self.exit_lookback_mode(stdout_fd)?; + } else { + self.enter_lookback_mode()?; + } } else if !self .config .lookback_sequence .starts_with(&self.input_buffer) { - write_all_raw(master_fd, &self.input_buffer)?; + if !self.in_lookback_mode { + write_all_raw(master_fd, &self.input_buffer)?; + } self.input_buffer.clear(); } } Ok(()) } - fn enter_lookback_mode(&mut self, stdout_fd: i32) -> Result<()> { - let header = b"\x1b[7m--- LOOKBACK: scroll up to see history ---\x1b[0m\r\n"; - write_all_raw(stdout_fd, header)?; + fn enter_lookback_mode(&mut self) -> Result<()> { + self.in_lookback_mode = true; + self.lookback_cache.clear(); + self.output_buffer.clear(); 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)?; + + 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(()) }