Skip to content

Commit d921cc4

Browse files
authored
feat: add Cline/Roo Code support via rtk init --agent cline (#701) (#702)
Install RTK rules in .clinerules (project-scoped) so Cline prefixes shell commands with rtk for token savings. Same rules-based approach as Windsurf and Codex. Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
1 parent 86d5069 commit d921cc4

3 files changed

Lines changed: 84 additions & 13 deletions

File tree

‎hooks/cline-rtk-rules.md‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# RTK - Rust Token Killer (Cline)
2+
3+
**Usage**: Token-optimized CLI proxy for shell commands.
4+
5+
## Rule
6+
7+
Always prefix shell commands with `rtk` to minimize token consumption.
8+
9+
Examples:
10+
11+
```bash
12+
rtk git status
13+
rtk cargo test
14+
rtk ls src/
15+
rtk grep "pattern" src/
16+
rtk find "*.rs" .
17+
rtk docker ps
18+
rtk gh pr list
19+
```
20+
21+
## Meta Commands
22+
23+
```bash
24+
rtk gain # Show token savings
25+
rtk gain --history # Command history with savings
26+
rtk discover # Find missed RTK opportunities
27+
rtk proxy <cmd> # Run raw (no filtering, for debugging)
28+
```
29+
30+
## Why
31+
32+
RTK filters and compresses command output before it reaches the LLM context, saving 60-90% tokens on common operations. Always use `rtk <cmd>` instead of raw commands.

‎src/init.rs‎

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -204,12 +204,14 @@ Overall average: **60-90% token reduction** on common development operations.
204204
"##;
205205

206206
/// Main entry point for `rtk init`
207+
#[allow(clippy::too_many_arguments)]
207208
pub fn run(
208209
global: bool,
209210
install_claude: bool,
210211
install_opencode: bool,
211212
install_cursor: bool,
212213
install_windsurf: bool,
214+
install_cline: bool,
213215
claude_md: bool,
214216
hook_only: bool,
215217
codex: bool,
@@ -254,6 +256,11 @@ pub fn run(
254256
return run_windsurf_mode(verbose);
255257
}
256258

259+
// Cline-only mode
260+
if install_cline {
261+
return run_cline_mode(verbose);
262+
}
263+
257264
// Mode selection (Claude Code / OpenCode)
258265
match (install_claude, install_opencode, claude_md, hook_only) {
259266
(false, true, _, _) => run_opencode_only_mode(verbose)?,
@@ -1156,17 +1163,43 @@ fn run_claude_md_mode(global: bool, verbose: u8, install_opencode: bool) -> Resu
11561163
Ok(())
11571164
}
11581165

1159-
/// Codex mode: slim RTK.md + @RTK.md reference in AGENTS.md
11601166
// ─── Windsurf support ─────────────────────────────────────────
11611167

11621168
/// Embedded Windsurf RTK rules
11631169
const WINDSURF_RULES: &str = include_str!("../hooks/windsurf-rtk-rules.md");
11641170

1165-
/// Resolve Windsurf user config directory (~/.codeium/windsurf)
1166-
fn resolve_windsurf_dir() -> Result<PathBuf> {
1167-
dirs::home_dir()
1168-
.map(|h| h.join(".codeium").join("windsurf"))
1169-
.context("Cannot determine home directory")
1171+
/// Embedded Cline RTK rules
1172+
const CLINE_RULES: &str = include_str!("../hooks/cline-rtk-rules.md");
1173+
1174+
// ─── Cline / Roo Code support ─────────────────────────────────
1175+
1176+
fn run_cline_mode(verbose: u8) -> Result<()> {
1177+
// Cline reads .clinerules from the project root (workspace-scoped)
1178+
let rules_path = PathBuf::from(".clinerules");
1179+
1180+
let existing = fs::read_to_string(&rules_path).unwrap_or_default();
1181+
if existing.contains("RTK") || existing.contains("rtk") {
1182+
println!("\nRTK already configured for Cline in this project.\n");
1183+
println!(" Rules: .clinerules (already present)");
1184+
} else {
1185+
let new_content = if existing.trim().is_empty() {
1186+
CLINE_RULES.to_string()
1187+
} else {
1188+
format!("{}\n\n{}", existing.trim(), CLINE_RULES)
1189+
};
1190+
fs::write(&rules_path, &new_content).context("Failed to write .clinerules")?;
1191+
1192+
if verbose > 0 {
1193+
eprintln!("Wrote .clinerules");
1194+
}
1195+
1196+
println!("\nRTK configured for Cline.\n");
1197+
println!(" Rules: .clinerules (installed)");
1198+
}
1199+
println!(" Cline will now use rtk commands for token savings.");
1200+
println!(" Test with: git status\n");
1201+
1202+
Ok(())
11701203
}
11711204

11721205
fn run_windsurf_mode(verbose: u8) -> Result<()> {
@@ -1655,7 +1688,7 @@ fn cursor_hook_already_present(root: &serde_json::Value) -> bool {
16551688
entry
16561689
.get("command")
16571690
.and_then(|c| c.as_str())
1658-
.map_or(false, |cmd| cmd.contains("rtk-rewrite.sh"))
1691+
.is_some_and(|cmd| cmd.contains("rtk-rewrite.sh"))
16591692
})
16601693
}
16611694

@@ -1750,7 +1783,7 @@ fn remove_cursor_hook_from_json(root: &mut serde_json::Value) -> bool {
17501783
!entry
17511784
.get("command")
17521785
.and_then(|c| c.as_str())
1753-
.map_or(false, |cmd| cmd.contains("rtk-rewrite.sh"))
1786+
.is_some_and(|cmd| cmd.contains("rtk-rewrite.sh"))
17541787
});
17551788

17561789
pre_tool_use.len() < original_len
@@ -2139,7 +2172,7 @@ fn patch_gemini_settings(
21392172
if arr.iter().any(|h| {
21402173
h.pointer("/hooks/0/command")
21412174
.and_then(|v| v.as_str())
2142-
.map_or(false, |c| c.contains("rtk"))
2175+
.is_some_and(|c| c.contains("rtk"))
21432176
}) {
21442177
if verbose > 0 {
21452178
eprintln!("Gemini settings.json already has RTK hook");
@@ -2248,7 +2281,7 @@ fn uninstall_gemini(verbose: u8) -> Result<Vec<String>> {
22482281
arr.retain(|h| {
22492282
!h.pointer("/hooks/0/command")
22502283
.and_then(|v| v.as_str())
2251-
.map_or(false, |c| c.contains("rtk"))
2284+
.is_some_and(|c| c.contains("rtk"))
22522285
});
22532286
if arr.len() < before {
22542287
let new_content = serde_json::to_string_pretty(&settings)?;
@@ -2494,6 +2527,7 @@ More notes
24942527
false,
24952528
false,
24962529
false,
2530+
false,
24972531
true,
24982532
PatchMode::Auto,
24992533
0,
@@ -2515,6 +2549,7 @@ More notes
25152549
false,
25162550
false,
25172551
false,
2552+
false,
25182553
true,
25192554
PatchMode::Skip,
25202555
0,

‎src/main.rs‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ pub enum AgentTarget {
8080
Cursor,
8181
/// Windsurf IDE (Cascade)
8282
Windsurf,
83+
/// Cline / Roo Code (VS Code)
84+
Cline,
8385
}
8486

8587
#[derive(Parser)]
@@ -1659,7 +1661,7 @@ fn main() -> Result<()> {
16591661
if show {
16601662
init::show_config(codex)?;
16611663
} else if uninstall {
1662-
let cursor = agent.map_or(false, |a| a == AgentTarget::Cursor);
1664+
let cursor = agent == Some(AgentTarget::Cursor);
16631665
init::uninstall(global, gemini, codex, cursor, cli.verbose)?;
16641666
} else if gemini {
16651667
let patch_mode = if auto_patch {
@@ -1673,8 +1675,9 @@ fn main() -> Result<()> {
16731675
} else {
16741676
let install_opencode = opencode;
16751677
let install_claude = !opencode;
1676-
let install_cursor = agent.map_or(false, |a| a == AgentTarget::Cursor);
1677-
let install_windsurf = agent.map_or(false, |a| a == AgentTarget::Windsurf);
1678+
let install_cursor = agent == Some(AgentTarget::Cursor);
1679+
let install_windsurf = agent == Some(AgentTarget::Windsurf);
1680+
let install_cline = agent == Some(AgentTarget::Cline);
16781681

16791682
let patch_mode = if auto_patch {
16801683
init::PatchMode::Auto
@@ -1689,6 +1692,7 @@ fn main() -> Result<()> {
16891692
install_opencode,
16901693
install_cursor,
16911694
install_windsurf,
1695+
install_cline,
16921696
claude_md,
16931697
hook_only,
16941698
codex,

0 commit comments

Comments
 (0)