From 1d56cd0e18fc69a85a0889ca6f9a9f3069a4be3a Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Mon, 1 Jun 2026 03:50:45 +0300 Subject: [PATCH] feat: detect kilo code cli refs #270 --- docs/next/CHANGELOG.md | 1 + docs/next/README.md | 1 + docs/next/website/src/content/docs/agents.mdx | 1 + .../src/content/docs/configuration.mdx | 2 +- src/config/model.rs | 3 +- src/config/sound.rs | 3 ++ src/detect/agents/kilo.rs | 9 ++++ src/detect/agents/mod.rs | 2 + src/detect/mod.rs | 50 +++++++++++++++++++ src/main.rs | 2 +- 10 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 src/detect/agents/kilo.rs diff --git a/docs/next/CHANGELOG.md b/docs/next/CHANGELOG.md index 13612ccf..724604b6 100644 --- a/docs/next/CHANGELOG.md +++ b/docs/next/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added - Added `ui.right_click_passthrough_modifier` so a configured modifier such as `ctrl` can forward right-click hold and drag gestures to mouse-reporting pane apps while normal right-click still opens Herdr's pane menu. (#148) +- Added Kilo Code CLI automatic detection for idle, working, and blocked terminal states. (#270) ## [0.6.6] - 2026-05-31 diff --git a/docs/next/README.md b/docs/next/README.md index 537377a8..34010388 100644 --- a/docs/next/README.md +++ b/docs/next/README.md @@ -154,6 +154,7 @@ automatic detection works out of the box. process name matching plus terminal ou | [opencode](https://github.com/anomalyco/opencode) | ✓ | ✓ | ✓ | | [grok cli](https://x.ai/grok) | ✓ | ✓ | ✓ | | [hermes agent](https://github.com/NousResearch/hermes-agent) | ✓ | ✓ | ✓ | +| [kilo code cli](https://kilo.ai/) | ✓ | ✓ | ✓ | | cursor agent | ✓ | ✓ | ✓ | | antigravity cli | ✓ | ✓ | ✓ | | kimi code cli | ✓ | ✓ | ✓ | diff --git a/docs/next/website/src/content/docs/agents.mdx b/docs/next/website/src/content/docs/agents.mdx index 57a0848f..bc540198 100644 --- a/docs/next/website/src/content/docs/agents.mdx +++ b/docs/next/website/src/content/docs/agents.mdx @@ -19,6 +19,7 @@ Automatic detection works out of the box for common coding agents. | OpenCode | yes | yes | yes | | Grok CLI | yes | yes | yes | | Hermes Agent | yes | yes | yes | +| Kilo Code CLI | yes | yes | yes | | Cursor Agent | yes | yes | yes | | Antigravity CLI | yes | yes | yes | | Kimi Code CLI | yes | yes | yes | diff --git a/docs/next/website/src/content/docs/configuration.mdx b/docs/next/website/src/content/docs/configuration.mdx index e1a5b8fa..b060b9c5 100644 --- a/docs/next/website/src/content/docs/configuration.mdx +++ b/docs/next/website/src/content/docs/configuration.mdx @@ -401,7 +401,7 @@ cjk_ime_cursor_shape = "steady_block" When enabled, the cursor stays visible at the focused pane's reported position. If the pane reports no cursor position, the anchor falls back to the pane's top-left so a stable IME hint is always available. -`cjk_ime_agents` is an optional allow-list. When empty, the reveal applies to any focused pane. When non-empty, the reveal only applies if the focused pane's detected agent matches one of the listed names — useful to enable the reveal only for AI-agent TUIs that paint their own cursor while leaving plain shells untouched. Accepted names: `pi`, `claude`, `codex`, `gemini`, `cursor`, `agy`, `cline`, `opencode`, `copilot`, `kimi`, `kiro`, `droid`, `amp`, `grok`, `hermes`, `qodercli`, and `qoder`. Unknown names are ignored; if the list contains no valid names, the reveal does not apply. +`cjk_ime_agents` is an optional allow-list. When empty, the reveal applies to any focused pane. When non-empty, the reveal only applies if the focused pane's detected agent matches one of the listed names — useful to enable the reveal only for AI-agent TUIs that paint their own cursor while leaving plain shells untouched. Accepted names: `pi`, `claude`, `codex`, `gemini`, `cursor`, `agy`, `cline`, `opencode`, `copilot`, `kimi`, `kiro`, `droid`, `amp`, `grok`, `hermes`, `kilo`, `qodercli`, and `qoder`. Unknown names are ignored; if the list contains no valid names, the reveal does not apply. `cjk_ime_cursor_shape` controls the DECSCUSR shape rendered for the IME anchor. Accepted values: `block`, `steady_block` (default), `underline`, `steady_underline`, `bar`, `steady_bar`. diff --git a/src/config/model.rs b/src/config/model.rs index 0776e09b..c312acfa 100644 --- a/src/config/model.rs +++ b/src/config/model.rs @@ -414,7 +414,8 @@ pub struct ExperimentalConfig { /// list means apply to any focused pane. Unknown agent names are ignored; /// if the list contains no valid names, the reveal does not apply. /// Accepted names: pi, claude, codex, gemini, cursor, cline, opencode, - /// copilot, kimi, kiro, droid, amp, grok, hermes. Default: empty. + /// copilot, kimi, kiro, droid, amp, grok, hermes, kilo, qodercli, qoder. + /// Default: empty. pub cjk_ime_agents: Vec, /// Cursor shape rendered for the IME anchor when /// `reveal_hidden_cursor_for_cjk_ime` is enabled. Default: "steady_block". diff --git a/src/config/sound.rs b/src/config/sound.rs index e4f15108..50b2355b 100644 --- a/src/config/sound.rs +++ b/src/config/sound.rs @@ -40,6 +40,7 @@ pub struct AgentSoundOverrides { pub amp: AgentSoundSetting, pub grok: AgentSoundSetting, pub hermes: AgentSoundSetting, + pub kilo: AgentSoundSetting, pub qodercli: AgentSoundSetting, } @@ -131,6 +132,7 @@ impl AgentSoundOverrides { Some(Agent::Amp) => self.amp, Some(Agent::Grok) => self.grok, Some(Agent::Hermes) => self.hermes, + Some(Agent::Kilo) => self.kilo, Some(Agent::Qodercli) => self.qodercli, None => AgentSoundSetting::Default, } @@ -167,6 +169,7 @@ impl Default for AgentSoundOverrides { amp: AgentSoundSetting::Default, grok: AgentSoundSetting::Default, hermes: AgentSoundSetting::Default, + kilo: AgentSoundSetting::Default, qodercli: AgentSoundSetting::Default, } } diff --git a/src/detect/agents/kilo.rs b/src/detect/agents/kilo.rs new file mode 100644 index 00000000..1ab3651a --- /dev/null +++ b/src/detect/agents/kilo.rs @@ -0,0 +1,9 @@ +use super::super::AgentState; + +pub(super) fn detect(content: &str) -> AgentState { + if content.to_lowercase().contains("esc interrupt") { + return AgentState::Working; + } + + super::opencode::detect(content) +} diff --git a/src/detect/agents/mod.rs b/src/detect/agents/mod.rs index 87b4043f..4597c122 100644 --- a/src/detect/agents/mod.rs +++ b/src/detect/agents/mod.rs @@ -9,6 +9,7 @@ pub(super) mod gemini; pub(super) mod github_copilot; pub(super) mod grok; pub(super) mod hermes; +pub(super) mod kilo; pub(super) mod kimi; pub(super) mod kiro; pub(super) mod opencode; @@ -34,6 +35,7 @@ pub(super) fn detect(agent: Agent, screen_content: &str) -> AgentDetection { Agent::Amp => amp::detect(screen_content), Agent::Grok => grok::detect(screen_content), Agent::Hermes => hermes::detect(screen_content), + Agent::Kilo => kilo::detect(screen_content), Agent::Qodercli => qodercli::detect(screen_content), }; diff --git a/src/detect/mod.rs b/src/detect/mod.rs index cf600f51..b9806b19 100644 --- a/src/detect/mod.rs +++ b/src/detect/mod.rs @@ -54,6 +54,7 @@ pub enum Agent { Amp, Grok, Hermes, + Kilo, Qodercli, } @@ -74,6 +75,7 @@ pub fn agent_label(agent: Agent) -> &'static str { Agent::Amp => "amp", Agent::Grok => "grok", Agent::Hermes => "hermes", + Agent::Kilo => "kilo", Agent::Qodercli => "qodercli", } } @@ -96,6 +98,7 @@ pub fn parse_agent_label(agent: &str) -> Option { "amp" | "amp-local" => Some(Agent::Amp), "grok" | "grok-build" => Some(Agent::Grok), "hermes" | "hermes-agent" => Some(Agent::Hermes), + "kilo" | "kilo-code" | "kilo code" => Some(Agent::Kilo), "qodercli" | "qoderclicn" | "qoder" | "qodercn" => Some(Agent::Qodercli), _ => None, } @@ -122,6 +125,7 @@ pub fn identify_agent(process_name: &str) -> Option { "amp" | "amp-local" => Some(Agent::Amp), "grok" | "grok-build" => Some(Agent::Grok), "hermes" | "hermes-agent" => Some(Agent::Hermes), + "kilo" | "kilo-code" | "kilo code" => Some(Agent::Kilo), "qodercli" | "qoderclicn" | "qoder" | "qodercn" => Some(Agent::Qodercli), _ => None, } @@ -255,6 +259,11 @@ fn detect_qodercli(content: &str) -> AgentState { detect_state(Some(Agent::Qodercli), content) } +#[cfg(test)] +fn detect_kilo(content: &str) -> AgentState { + detect_state(Some(Agent::Kilo), content) +} + // --------------------------------------------------------------------------- // Shared helpers // --------------------------------------------------------------------------- @@ -567,6 +576,11 @@ mod tests { AgentState::Working, ), (Agent::Hermes, "msg=interrupt", AgentState::Working), + ( + Agent::Kilo, + "Ask · DeepSeek V4 Pro\nesc interrupt", + AgentState::Working, + ), (Agent::Qodercli, "\u{280B} Thinking...", AgentState::Working), ]; @@ -604,6 +618,8 @@ mod tests { assert_eq!(identify_agent("grok-build"), Some(Agent::Grok)); assert_eq!(identify_agent("hermes"), Some(Agent::Hermes)); assert_eq!(identify_agent("hermes-agent"), Some(Agent::Hermes)); + assert_eq!(identify_agent("kilo"), Some(Agent::Kilo)); + assert_eq!(identify_agent("kilo-code"), Some(Agent::Kilo)); } #[test] @@ -623,6 +639,7 @@ mod tests { assert_eq!(parse_agent_label("kiro-cli"), Some(Agent::Kiro)); assert_eq!(parse_agent_label("grok-build"), Some(Agent::Grok)); assert_eq!(parse_agent_label("hermes-agent"), Some(Agent::Hermes)); + assert_eq!(parse_agent_label("kilo-code"), Some(Agent::Kilo)); } #[test] @@ -634,6 +651,7 @@ mod tests { assert_eq!(agent_label(Agent::Kiro), "kiro"); assert_eq!(agent_label(Agent::Grok), "grok"); assert_eq!(agent_label(Agent::Hermes), "hermes"); + assert_eq!(agent_label(Agent::Kilo), "kilo"); } #[test] @@ -1581,6 +1599,38 @@ mod tests { assert_eq!(detect_opencode("> "), AgentState::Idle); } + // ---- Kilo ---- + + #[test] + fn kilo_waiting_question_prompt() { + assert_eq!( + detect_kilo( + "Write the following joke to ~/joke.md?\n\ + 1. Yes, write it\n\ + 2. No, thanks\n\ + 3. Type your own answer\n\ + ↑↓ select enter submit esc dismiss", + ), + AgentState::Blocked + ); + } + + #[test] + fn kilo_working() { + assert_eq!( + detect_kilo("Ask · DeepSeek V4 Pro\nesc interrupt\n12.8K (1%) · $0.01"), + AgentState::Working + ); + } + + #[test] + fn kilo_idle() { + assert_eq!( + detect_kilo("Ask anything... \"Fix broken tests\"\nCode · DeepSeek V4 Pro"), + AgentState::Idle + ); + } + // ---- GitHub Copilot ---- #[test] diff --git a/src/main.rs b/src/main.rs index b317c2da..b7b11b16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -265,7 +265,7 @@ pane_history = false # matches one of these names. Empty means apply to any focused pane. # If the list contains no valid names, the reveal does not apply. # Accepted: pi, claude, codex, gemini, cursor, cline, opencode, copilot, -# kimi, kiro, droid, amp, grok, hermes, qodercli, qoder. +# kimi, kiro, droid, amp, grok, hermes, kilo, qodercli, qoder. # cjk_ime_agents = [] # Cursor shape rendered when reveal_hidden_cursor_for_cjk_ime is true. # Values: block, steady_block (default), underline, steady_underline, bar, steady_bar.