Fix auto-lookback trigger and add version tracking (#20)
* Fix auto-lookback trigger and add version tracking - Trigger on stdin inactivity instead of render time - Only dump when new output exists since last dump - Default timeout 5s -> 15s - Embed git hash in version output - Document macOS config path
This commit is contained in:
@@ -2,6 +2,17 @@
|
||||
|
||||
Contributions welcome! Feel free to open issues or pull requests.
|
||||
|
||||
## Bug Reports
|
||||
|
||||
When filing a bug report, please include the output of `claude-chill --version`:
|
||||
|
||||
```
|
||||
$ claude-chill --version
|
||||
claude-chill 0.1.0 (35ecc80)
|
||||
```
|
||||
|
||||
This includes the version number and git commit hash, which helps identify exactly which code you're running.
|
||||
|
||||
## Branch naming
|
||||
|
||||
- `feature/description`
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -133,7 +133,7 @@ checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||
|
||||
[[package]]
|
||||
name = "claude-chill"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
|
||||
@@ -5,6 +5,6 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2024"
|
||||
authors = ["David Beesley"]
|
||||
|
||||
12
README.md
12
README.md
@@ -55,7 +55,7 @@ Options:
|
||||
-k, --lookback-key <LOOKBACK_KEY>
|
||||
Key to toggle lookback mode, quote to prevent glob expansion (default: "[ctrl][6]")
|
||||
-a, --auto-lookback-timeout <AUTO_LOOKBACK_TIMEOUT>
|
||||
Auto-lookback timeout in ms, 0 to disable (default: 5000)
|
||||
Auto-lookback timeout in ms, 0 to disable (default: 15000)
|
||||
-h, --help
|
||||
Print help
|
||||
-V, --version
|
||||
@@ -97,19 +97,21 @@ When you exit lookback mode, any cached output is processed and the current stat
|
||||
|
||||
## Auto-Lookback
|
||||
|
||||
After 5 seconds of idle (no new renders), the full history is automatically dumped to your terminal so you can scroll back without pressing any keys. This is useful for reviewing Claude's output after it finishes working.
|
||||
After `auto_lookback_timeout_ms` (default 15 seconds) of idle (no user input), the full history is automatically dumped to your terminal so you can scroll back without pressing any keys. This continues to re-dump every `auto_lookback_timeout_ms` while idle. This is useful for reviewing Claude's output after it finishes working.
|
||||
|
||||
**Note:** The auto-lookback causes a brief screen flicker during the transition as it clears the screen and writes the history buffer. Disable with `-a 0` or adjust the timeout with `-a 10000` (10 seconds).
|
||||
**Note:** The auto-lookback causes a brief screen flicker during the transition as it clears the screen and writes the history buffer. Disable with `-a 0` or adjust the timeout with `-a 30000` (30 seconds).
|
||||
|
||||
## Configuration
|
||||
|
||||
Create `~/.config/claude-chill.toml`:
|
||||
Config file location:
|
||||
- **Linux**: `~/.config/claude-chill.toml`
|
||||
- **macOS**: `~/Library/Application Support/claude-chill.toml`
|
||||
|
||||
```toml
|
||||
history_lines = 100000 # Max lines stored for lookback
|
||||
lookback_key = "[ctrl][6]" # Key to toggle lookback mode
|
||||
refresh_rate = 20 # Rendering FPS
|
||||
auto_lookback_timeout_ms = 5000 # Auto-lookback after 5s idle (0 to disable)
|
||||
auto_lookback_timeout_ms = 15000 # Auto-lookback after 15s idle (0 to disable)
|
||||
```
|
||||
|
||||
Note: History is cleared on full screen redraws, so lookback shows output since Claude's last full render.
|
||||
|
||||
38
crates/claude-chill/build.rs
Normal file
38
crates/claude-chill/build.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=.git/HEAD");
|
||||
println!("cargo:rerun-if-changed=.git/index");
|
||||
|
||||
let git_hash = get_git_hash();
|
||||
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
|
||||
}
|
||||
|
||||
fn get_git_hash() -> String {
|
||||
let hash = match Command::new("git")
|
||||
.args(["rev-parse", "--short", "HEAD"])
|
||||
.output()
|
||||
{
|
||||
Ok(output) if output.status.success() => String::from_utf8(output.stdout)
|
||||
.ok()
|
||||
.map(|s| s.trim().to_string()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let Some(hash) = hash else {
|
||||
return "git not found during build, version unavailable".to_string();
|
||||
};
|
||||
|
||||
let is_dirty = Command::new("git")
|
||||
.args(["status", "--porcelain"])
|
||||
.output()
|
||||
.ok()
|
||||
.map(|output| !output.stdout.is_empty())
|
||||
.unwrap_or(false);
|
||||
|
||||
if is_dirty {
|
||||
format!("{}-dirty", hash)
|
||||
} else {
|
||||
hash
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
use clap::Parser;
|
||||
|
||||
const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", env!("GIT_HASH"), ")");
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(
|
||||
name = "claude-chill",
|
||||
version,
|
||||
version = VERSION,
|
||||
about = "A PTY proxy that tames Claude Code's massive terminal updates"
|
||||
)]
|
||||
pub struct Cli {
|
||||
@@ -22,7 +24,7 @@ pub struct Cli {
|
||||
#[arg(short = 'k', long = "lookback-key")]
|
||||
pub lookback_key: Option<String>,
|
||||
|
||||
/// Auto-lookback timeout in ms, 0 to disable (default: 5000)
|
||||
/// Auto-lookback timeout in ms, 0 to disable (default: 15000)
|
||||
#[arg(short = 'a', long = "auto-lookback-timeout")]
|
||||
pub auto_lookback_timeout: Option<u64>,
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::path::PathBuf;
|
||||
|
||||
const DEFAULT_LOOKBACK_KEY: &str = "[ctrl][6]";
|
||||
const DEFAULT_REFRESH_RATE: u64 = 20;
|
||||
const DEFAULT_AUTO_LOOKBACK_TIMEOUT_MS: u64 = 5000;
|
||||
const DEFAULT_AUTO_LOOKBACK_TIMEOUT_MS: u64 = 15000;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(default)]
|
||||
@@ -100,7 +100,7 @@ mod tests {
|
||||
assert_eq!(config.lookback_key, "[ctrl][6]");
|
||||
assert_eq!(config.refresh_rate, 20);
|
||||
assert_eq!(config.redraw_throttle_ms(), 50);
|
||||
assert_eq!(config.auto_lookback_timeout_ms, 5000);
|
||||
assert_eq!(config.auto_lookback_timeout_ms, 15000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -58,7 +58,7 @@ impl Default for ProxyConfig {
|
||||
max_history_lines: 100_000,
|
||||
lookback_key: "[ctrl][6]".to_string(),
|
||||
lookback_sequence: vec![0x1E],
|
||||
auto_lookback_timeout_ms: 5000,
|
||||
auto_lookback_timeout_ms: 15000,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,6 +100,8 @@ pub struct Proxy {
|
||||
vt_prev_screen: Option<vt100::Screen>,
|
||||
last_output_time: Option<Instant>,
|
||||
last_render_time: Option<Instant>,
|
||||
last_stdin_time: Option<Instant>,
|
||||
last_auto_lookback_time: Option<Instant>,
|
||||
auto_lookback_timeout: Duration,
|
||||
sync_buffer: Vec<u8>,
|
||||
in_sync_block: bool,
|
||||
@@ -182,6 +184,8 @@ impl Proxy {
|
||||
vt_prev_screen: None,
|
||||
last_output_time: None,
|
||||
last_render_time: None,
|
||||
last_stdin_time: None,
|
||||
last_auto_lookback_time: None,
|
||||
auto_lookback_timeout,
|
||||
sync_buffer: Vec::with_capacity(SYNC_BUFFER_CAPACITY),
|
||||
in_sync_block: false,
|
||||
@@ -581,14 +585,38 @@ impl Proxy {
|
||||
if self.in_lookback_mode || self.in_alternate_screen {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Check if enough time has passed since last stdin activity
|
||||
let Some(stdin_time) = self.last_stdin_time else {
|
||||
return Ok(());
|
||||
};
|
||||
if stdin_time.elapsed() < self.auto_lookback_timeout {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Check if there's been new output since last auto-lookback
|
||||
// AND enough time has passed since we last dumped
|
||||
let Some(render_time) = self.last_render_time else {
|
||||
return Ok(());
|
||||
};
|
||||
if render_time.elapsed() < self.auto_lookback_timeout {
|
||||
if let Some(last_auto) = self.last_auto_lookback_time {
|
||||
let no_new_output = render_time <= last_auto;
|
||||
let too_soon = last_auto.elapsed() < self.auto_lookback_timeout;
|
||||
if no_new_output || too_soon {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
debug!(
|
||||
"auto_lookback triggered: stdin_idle={}ms render_age={}ms last_auto_age={}ms",
|
||||
stdin_time.elapsed().as_millis(),
|
||||
render_time.elapsed().as_millis(),
|
||||
self.last_auto_lookback_time
|
||||
.map(|t| t.elapsed().as_millis())
|
||||
.unwrap_or(0)
|
||||
);
|
||||
self.dump_history(stdout_fd)?;
|
||||
self.last_render_time = None;
|
||||
self.last_auto_lookback_time = Some(Instant::now());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -611,6 +639,8 @@ impl Proxy {
|
||||
}
|
||||
|
||||
fn process_input<F: AsFd>(&mut self, data: &[u8], stdout_fd: &F) -> Result<()> {
|
||||
self.last_stdin_time = Some(Instant::now());
|
||||
|
||||
if self.in_alternate_screen {
|
||||
return write_all(&self.pty_master, data);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user