aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/detect/manifests/cline.toml41
-rw-r--r--src/detect/mod.rs86
2 files changed, 123 insertions, 4 deletions
diff --git a/src/detect/manifests/cline.toml b/src/detect/manifests/cline.toml
index b30f4417..2d314087 100644
--- a/src/detect/manifests/cline.toml
+++ b/src/detect/manifests/cline.toml
@@ -1,7 +1,7 @@
id = "cline"
-version = "2026.06.10.1"
+version = "2026.09.11.1"
min_engine_version = 1
-updated_at = "2026-06-10T00:00:00Z"
+updated_at = "2026-09-11T00:00:00Z"
[[rules]]
id = "tool_permission"
@@ -18,6 +18,43 @@ any = [
]
[[rules]]
+id = "inline_tool_permission"
+state = "blocked"
+priority = 300
+region = "bottom_non_empty_lines(16)"
+visible_blocker = true
+contains = ["Cline needs permission", "Approve tool call?", "[y] Approve", "[n] Deny"]
+
+[[rules]]
+id = "inline_question"
+state = "blocked"
+priority = 300
+region = "bottom_non_empty_lines(24)"
+visible_blocker = true
+line_regex = ['^\s*Cline is asking a question\s*$', '^\s*>\s+\S']
+contains = ["(Tab)", "Shift+Tab"]
+
+[[rules]]
+id = "active_turn"
+state = "working"
+priority = 200
+region = "bottom_non_empty_lines(20)"
+visible_working = true
+any = [
+ { line_regex = ['^\s*[\x{2801}-\x{28FF}]\s+\S'] },
+ { contains = ["Thinking... (esc to cancel)"] },
+]
+
+[[rules]]
+id = "composer_idle"
+state = "idle"
+priority = 100
+region = "bottom_non_empty_lines(12)"
+visible_idle = true
+line_regex = ['^\s*❯(?:\s.*)?$']
+contains = ["─", "(Tab)", "Shift+Tab"]
+
+[[rules]]
id = "default_cline_working"
state = "working"
priority = -10
diff --git a/src/detect/mod.rs b/src/detect/mod.rs
index c244dd89..0248d5ce 100644
--- a/src/detect/mod.rs
+++ b/src/detect/mod.rs
@@ -200,7 +200,7 @@ fn lookup_agent(name: &str) -> Option<Agent> {
"cursor" | "cursor-agent" => Some(Agent::Cursor),
"devin" | "devin-cli" | "devin cli" => Some(Agent::Devin),
"agy" | "antigravity" | "antigravity-cli" => Some(Agent::Antigravity),
- "cline" => Some(Agent::Cline),
+ "cline" | ".cline" => Some(Agent::Cline),
"omp" => Some(Agent::Omp),
"mastracode" | "mastra-code" | "mastra code" => Some(Agent::Mastracode),
"opencode" | "opencode2" | "open-code" => Some(Agent::OpenCode),
@@ -378,7 +378,10 @@ fn normalized_process_name(process: &crate::platform::ForegroundProcess) -> Stri
if let Some(wrapped_agent) =
wrapped_agent_name_from_runtime_argv(runtime, process.argv.as_deref())
{
- if identify_agent(&wrapped_agent) == Some(Agent::Qwen) {
+ if matches!(
+ identify_agent(&wrapped_agent),
+ Some(Agent::Qwen | Agent::Cline)
+ ) {
return wrapped_agent;
}
}
@@ -984,6 +987,85 @@ mod tests {
}
#[test]
+ fn identify_agent_in_job_detects_cline_native_binaries() {
+ for (name, executable) in [
+ (
+ ".cline",
+ "/home/user/.npm/lib/node_modules/cline/bin/.cline",
+ ),
+ (
+ "cline",
+ "/usr/local/lib/node_modules/@cline/cli-darwin-arm64/bin/cline",
+ ),
+ (
+ "cline.exe",
+ r"C:\Users\user\AppData\Roaming\npm\node_modules\@cline\cli-windows-x64\bin\cline.exe",
+ ),
+ ] {
+ let job = crate::platform::ForegroundJob {
+ process_group_id: 123,
+ processes: vec![foreground_process(123, name, &[executable, "--tui"])],
+ };
+
+ assert_eq!(
+ identify_agent_in_job(&job),
+ Some((Agent::Cline, name.to_string()))
+ );
+ }
+ }
+
+ #[test]
+ fn identify_agent_in_job_detects_cline_node_wrapper() {
+ for (name, argv) in [
+ (
+ "MainThread",
+ vec!["node", "/home/user/.fnm/bin/cline", "--tui"],
+ ),
+ (
+ "node",
+ vec!["node", "/usr/local/lib/node_modules/cline/bin/cline"],
+ ),
+ (
+ "node.exe",
+ vec![
+ r"C:\Program Files\nodejs\node.exe",
+ r"C:\Users\user\AppData\Roaming\npm\node_modules\cline\bin\cline",
+ ],
+ ),
+ ] {
+ let job = crate::platform::ForegroundJob {
+ process_group_id: 123,
+ processes: vec![foreground_process(123, name, &argv)],
+ };
+
+ assert_eq!(
+ identify_agent_in_job(&job),
+ Some((Agent::Cline, "cline".to_string()))
+ );
+ }
+ }
+
+ #[test]
+ fn identify_agent_in_job_rejects_unrelated_cline_mentions() {
+ for argv in [
+ vec!["node"],
+ vec!["node", "/path/to/other.js", "cline"],
+ vec!["node", "-e", "cline"],
+ vec!["node", "/path/to/cline-helper"],
+ vec!["/path/to/.cline-helper"],
+ vec!["/path/to/other", "/path/to/cline"],
+ ] {
+ let job = crate::platform::ForegroundJob {
+ process_group_id: 123,
+ processes: vec![foreground_process(123, "MainThread", &argv)],
+ };
+
+ assert_eq!(identify_agent_in_job(&job), None);
+ }
+ assert_eq!(identify_agent("MainThread"), None);
+ }
+
+ #[test]
fn identify_agent_in_job_detects_windows_cursor_install() {
let job = crate::platform::ForegroundJob {
process_group_id: 123,