aboutsummaryrefslogtreecommitdiff
path: root/src/detect/agents/claude_code.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/detect/agents/claude_code.rs')
-rw-r--r--src/detect/agents/claude_code.rs42
1 files changed, 42 insertions, 0 deletions
diff --git a/src/detect/agents/claude_code.rs b/src/detect/agents/claude_code.rs
index ace946da..77db856c 100644
--- a/src/detect/agents/claude_code.rs
+++ b/src/detect/agents/claude_code.rs
@@ -63,6 +63,20 @@ pub(super) fn has_working_chrome(content: &str) -> bool {
|| has_spinner_activity(above)
}
+pub(super) fn is_transcript_viewer(content: &str) -> bool {
+ let bottom_lines = bottom_non_empty_lines(content, 3);
+ let Some(last_line) = bottom_lines.last() else {
+ return false;
+ };
+ let bottom_text = normalize_lines(&bottom_lines);
+
+ bottom_text.contains("showing detailed transcript")
+ && bottom_text.contains("ctrl+o to toggle")
+ && (bottom_text.contains("ctrl+e to show all")
+ || bottom_text.contains("ctrl+e to collapse"))
+ && transcript_control_tail(last_line)
+}
+
pub(super) fn has_prompt_box(content: &str) -> bool {
let lines: Vec<&str> = content.lines().collect();
let Some(top_border_index) = claude_prompt_box_top_border_index(&lines) else {
@@ -232,3 +246,31 @@ fn is_horizontal_rule(line: &str) -> bool {
let trimmed = line.trim();
!trimmed.is_empty() && trimmed.chars().all(|c| c == '─')
}
+
+fn bottom_non_empty_lines(content: &str, max_lines: usize) -> Vec<&str> {
+ let mut lines: Vec<&str> = content
+ .lines()
+ .rev()
+ .filter(|line| !line.trim().is_empty())
+ .take(max_lines)
+ .collect();
+ lines.reverse();
+ lines
+}
+
+fn normalize_lines(lines: &[&str]) -> String {
+ lines
+ .iter()
+ .flat_map(|line| line.split_whitespace())
+ .collect::<Vec<_>>()
+ .join(" ")
+ .to_lowercase()
+}
+
+fn transcript_control_tail(line: &str) -> bool {
+ let lower = line.to_lowercase();
+ lower.contains("ctrl+e")
+ || lower.contains("show all")
+ || lower.contains("collapse")
+ || lower.contains("verbose")
+}