Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.36.0"
".": "0.37.1"
}
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,32 @@ All notable changes to rtk (Rust Token Killer) will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.37.1](https://github.com/rtk-ai/rtk/compare/v0.37.0...v0.37.1) (2026-04-18)


### Bug Fixes

* **docs:** user facing docs ([c8d6878](https://github.com/rtk-ai/rtk/commit/c8d68787fb8b31c52125e9fc7ea62e0aa590485f))

## [0.37.0](https://github.com/rtk-ai/rtk/compare/v0.36.0...v0.37.0) (2026-04-17)


### Features

* **discover:** handle more npm/npx/pnpm/pnpx patterns ([9e96caa](https://github.com/rtk-ai/rtk/commit/9e96caa0a18a95c84da82ba57716a9d3ef86d0c8))
* **refacto-core:** binary hook w/ native cmd exec + streaming ([e7b7f9a](https://github.com/rtk-ai/rtk/commit/e7b7f9ab665a0f7303d41d23ad156d24e5e8964e))


### Bug Fixes

* **docs:** use release please changelog no manual ([7591a14](https://github.com/rtk-ai/rtk/commit/7591a14e4ceb732ab7ca160ac01a852926abe77a))
* isolate cursor hook tests from local settings (determinist) ([d8ddefe](https://github.com/rtk-ai/rtk/commit/d8ddefe78efe25c35bb2a2f9083f2eacb9dd7274))
* P0+P1 fixes from pre-merge review of hook engine ([df8e035](https://github.com/rtk-ai/rtk/commit/df8e03558d4d6cc2f5cbac91c63ab1b3b51d3bcd))
* P0+P1 fixes from pre-merge review of hook engine ([d34389c](https://github.com/rtk-ai/rtk/commit/d34389c3d0936c2b0790e14f450bb50a28a7edf7))
* rename ship.md to ship/SKILL.md to match develop ([5916ecd](https://github.com/rtk-ai/rtk/commit/5916ecd86fb319c2519a0b4fb2891309833a3bb4))
* **runner:** preserve fd separation on command failure ([e92d099](https://github.com/rtk-ai/rtk/commit/e92d0993c93f0b732316dfa932d265aeca7488d6))
* **stream:** missing stderr fields ([a1d46f3](https://github.com/rtk-ai/rtk/commit/a1d46f39c291e3356b9c26a062bde05ba1de591a))

## [0.36.0](https://github.com/rtk-ai/rtk/compare/v0.35.0...v0.36.0) (2026-04-13)


Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rtk"
version = "0.34.3"
version = "0.37.1"
edition = "2021"
authors = ["Patrick Szymkowiak"]
description = "Rust Token Killer - High-performance CLI proxy to minimize LLM token consumption"
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,8 @@ The most effective way to use rtk. The hook transparently intercepts Bash comman

```bash
rtk init -g # Install hook + RTK.md (recommended)
rtk init -g --opencode # OpenCode plugin (instead of Claude Code)
rtk init --opencode # OpenCode plugin in the current project
rtk init -g --opencode # OpenCode plugin in global config
rtk init -g --auto-patch # Non-interactive (CI/CD)
rtk init -g --hook-only # Hook only, no RTK.md
rtk init --show # Verify installation
Expand Down Expand Up @@ -362,7 +363,7 @@ RTK supports 12 AI coding tools. Each integration transparently rewrites shell c
| **Codex** | `rtk init -g --codex` | AGENTS.md + RTK.md instructions |
| **Windsurf** | `rtk init --agent windsurf` | .windsurfrules (project-scoped) |
| **Cline / Roo Code** | `rtk init --agent cline` | .clinerules (project-scoped) |
| **OpenCode** | `rtk init -g --opencode` | Plugin TS (tool.execute.before) |
| **OpenCode** | `rtk init --opencode` | Plugin TS (tool.execute.before) |
| **OpenClaw** | `openclaw plugins install ./openclaw` | Plugin TS (before_tool_call) |
| **Mistral Vibe** | Planned ([#800](https://github.com/rtk-ai/rtk/issues/800)) | Blocked on upstream |
| **Kilo Code** | `rtk init --agent kilocode` | .kilocode/rules/rtk-rules.md (project-scoped) |
Expand Down
4 changes: 2 additions & 2 deletions docs/guide/getting-started/supported-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ rtk init --global --gemini
### OpenCode

```bash
rtk init --global --opencode
rtk init --opencode
```

Creates `~/.config/opencode/plugins/rtk.ts`. Uses the `tool.execute.before` hook.
Creates `.opencode/plugins/rtk.ts` in the current project. Add `--global` to install `~/.config/opencode/plugins/rtk.ts` instead. Uses the `tool.execute.before` hook.

### OpenClaw

Expand Down
5 changes: 3 additions & 2 deletions docs/guide/resources/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ rtk gain # should now show token savings stats
```bash
rtk init --global # Claude Code
rtk init --global --cursor # Cursor
rtk init --global --opencode # OpenCode
rtk init --opencode # OpenCode (project)
rtk init --global --opencode # OpenCode (global)
```

3. Restart your AI assistant.
Expand Down Expand Up @@ -149,7 +150,7 @@ Minimum required Rust version: 1.70+.
## OpenCode not using RTK

```bash
rtk init --global --opencode
rtk init --opencode
# restart OpenCode
rtk init --show # should show "OpenCode: plugin installed"
```
Expand Down
1 change: 1 addition & 0 deletions hooks/opencode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
- Intercepts `tool.execute.before` events, calls `rtk rewrite` as a subprocess
- Uses `.quiet().nothrow()` to silently ignore failures
- Mutates `args.command` in-place if rewrite differs from original
- Installed to `.opencode/plugins/rtk.ts` by `rtk init --opencode`
- Installed to `~/.config/opencode/plugins/rtk.ts` by `rtk init -g --opencode`
101 changes: 72 additions & 29 deletions src/hooks/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,6 @@ pub fn run(
}

// Validation: Global-only features
if install_opencode && !global {
anyhow::bail!("OpenCode plugin is global-only. Use: rtk init -g --opencode");
}

if install_cursor && !global {
anyhow::bail!("Cursor hooks are global-only. Use: rtk init -g --agent cursor");
}
Expand All @@ -274,7 +270,7 @@ pub fn run(

// Mode selection (Claude Code / OpenCode)
match (install_claude, install_opencode, claude_md, hook_only) {
(false, true, _, _) => run_opencode_only_mode(verbose)?,
(false, true, _, _) => run_opencode_only_mode(global, verbose)?,
(true, opencode, true, _) => run_claude_md_mode(global, verbose, opencode)?,
(true, opencode, false, true) => run_hook_only_mode(global, patch_mode, verbose, opencode)?,
(true, opencode, false, false) => run_default_mode(global, patch_mode, verbose, opencode)?,
Expand Down Expand Up @@ -535,7 +531,14 @@ fn remove_hook_from_settings(verbose: u8) -> Result<bool> {
}

/// Full uninstall for Claude, Gemini, Codex, or Cursor artifacts.
pub fn uninstall(global: bool, gemini: bool, codex: bool, cursor: bool, verbose: u8) -> Result<()> {
pub fn uninstall(
global: bool,
gemini: bool,
codex: bool,
cursor: bool,
opencode: bool,
verbose: u8,
) -> Result<()> {
if codex {
return uninstall_codex(global, verbose);
}
Expand All @@ -558,6 +561,20 @@ pub fn uninstall(global: bool, gemini: bool, codex: bool, cursor: bool, verbose:
return Ok(());
}

if !global && opencode {
let removed = remove_opencode_plugin(false, verbose)?;
if removed.is_empty() {
println!("RTK OpenCode plugin was not installed in this project (nothing to remove)");
} else {
println!("RTK uninstalled (OpenCode project plugin):");
for path in &removed {
println!(" - OpenCode plugin: {}", path.display());
}
println!("\nRestart OpenCode to apply changes.");
}
return Ok(());
}

if !global {
anyhow::bail!("Uninstall only works with --global flag. For local projects, manually remove RTK from CLAUDE.md");
}
Expand Down Expand Up @@ -631,7 +648,7 @@ pub fn uninstall(global: bool, gemini: bool, codex: bool, cursor: bool, verbose:
}

// 5. Remove OpenCode plugin
let opencode_removed = remove_opencode_plugin(verbose)?;
let opencode_removed = remove_opencode_plugin(true, verbose)?;
for path in opencode_removed {
removed.push(format!("OpenCode plugin: {}", path.display()));
}
Expand Down Expand Up @@ -910,7 +927,7 @@ fn run_default_mode(
write_if_changed(&rtk_md_path, RTK_SLIM, RTK_MD, verbose)?;

let opencode_plugin_path = if install_opencode {
let path = prepare_opencode_plugin_path()?;
let path = prepare_opencode_plugin_path(true)?;
ensure_opencode_plugin_installed(&path, verbose)?;
Some(path)
} else {
Expand Down Expand Up @@ -1154,7 +1171,7 @@ fn run_hook_only_mode(
migrate_old_hook_script(verbose);

let opencode_plugin_path = if install_opencode {
let path = prepare_opencode_plugin_path()?;
let path = prepare_opencode_plugin_path(true)?;
ensure_opencode_plugin_installed(&path, verbose)?;
Some(path)
} else {
Expand Down Expand Up @@ -1266,7 +1283,7 @@ fn run_claude_md_mode(global: bool, verbose: u8, install_opencode: bool) -> Resu

if global {
if install_opencode {
let opencode_plugin_path = prepare_opencode_plugin_path()?;
let opencode_plugin_path = prepare_opencode_plugin_path(true)?;
ensure_opencode_plugin_installed(&opencode_plugin_path, verbose)?;
println!(
"[ok] OpenCode plugin installed: {}",
Expand Down Expand Up @@ -1779,18 +1796,21 @@ fn codex_rtk_md_ref(codex_dir: &Path) -> String {
format!("@{}", codex_dir.join(RTK_MD).display())
}

fn resolve_opencode_dir() -> Result<PathBuf> {
resolve_home_subdir(".config/opencode")
fn resolve_opencode_dir(global: bool) -> Result<PathBuf> {
if global {
return resolve_home_subdir(".config/opencode");
}
Ok(std::env::current_dir()?.join(".opencode"))
}

/// Return OpenCode plugin path: ~/.config/opencode/plugins/rtk.ts
/// Return OpenCode plugin path.
fn opencode_plugin_path(opencode_dir: &Path) -> PathBuf {
opencode_dir.join("plugins").join("rtk.ts")
}

/// Prepare OpenCode plugin directory and return install path
fn prepare_opencode_plugin_path() -> Result<PathBuf> {
let opencode_dir = resolve_opencode_dir()?;
fn prepare_opencode_plugin_path(global: bool) -> Result<PathBuf> {
let opencode_dir = resolve_opencode_dir(global)?;
let path = opencode_plugin_path(&opencode_dir);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).with_context(|| {
Expand All @@ -1809,8 +1829,8 @@ fn ensure_opencode_plugin_installed(path: &Path, verbose: u8) -> Result<bool> {
}

/// Remove OpenCode plugin file
fn remove_opencode_plugin(verbose: u8) -> Result<Vec<PathBuf>> {
let opencode_dir = resolve_opencode_dir()?;
fn remove_opencode_plugin(global: bool, verbose: u8) -> Result<Vec<PathBuf>> {
let opencode_dir = resolve_opencode_dir(global)?;
let path = opencode_plugin_path(&opencode_dir);
let mut removed = Vec::new();

Expand Down Expand Up @@ -2251,15 +2271,34 @@ fn show_claude_config() -> Result<()> {
}

// Check OpenCode plugin
if let Ok(opencode_dir) = resolve_opencode_dir() {
let plugin = opencode_plugin_path(&opencode_dir);
if plugin.exists() {
println!("[ok] OpenCode: plugin installed ({})", plugin.display());
} else {
println!("[--] OpenCode: plugin not found");
let global_opencode = resolve_opencode_dir(true)
.ok()
.map(|dir| opencode_plugin_path(&dir));
let local_opencode = resolve_opencode_dir(false)
.ok()
.map(|dir| opencode_plugin_path(&dir));
match (global_opencode, local_opencode) {
(Some(global_plugin), Some(local_plugin)) => {
let mut found = false;
if global_plugin.exists() {
println!(
"[ok] OpenCode (global): plugin installed ({})",
global_plugin.display()
);
found = true;
}
if local_plugin.exists() {
println!(
"[ok] OpenCode (local): plugin installed ({})",
local_plugin.display()
);
found = true;
}
if !found {
println!("[--] OpenCode: plugin not found");
}
}
} else {
println!("[--] OpenCode: config dir not found");
_ => println!("[--] OpenCode: plugin path unavailable"),
}

// Check Cursor hooks
Expand Down Expand Up @@ -2324,7 +2363,8 @@ fn show_claude_config() -> Result<()> {
println!(" rtk init -g --hook-only # Hook only, no RTK.md");
println!(" rtk init --codex # Configure local AGENTS.md + RTK.md");
println!(" rtk init -g --codex # Configure $CODEX_HOME/AGENTS.md + $CODEX_HOME/RTK.md (or ~/.codex/)");
println!(" rtk init -g --opencode # OpenCode plugin only");
println!(" rtk init --opencode # OpenCode plugin (project) only");
println!(" rtk init -g --opencode # OpenCode plugin (global) only");
println!(" rtk init -g --agent cursor # Install Cursor Agent hooks");

Ok(())
Expand Down Expand Up @@ -2386,10 +2426,13 @@ fn show_codex_config() -> Result<()> {
Ok(())
}

fn run_opencode_only_mode(verbose: u8) -> Result<()> {
let opencode_plugin_path = prepare_opencode_plugin_path()?;
fn run_opencode_only_mode(global: bool, verbose: u8) -> Result<()> {
let opencode_plugin_path = prepare_opencode_plugin_path(global)?;
ensure_opencode_plugin_installed(&opencode_plugin_path, verbose)?;
println!("\nOpenCode plugin installed (global).\n");
println!(
"\nOpenCode plugin installed ({}).\n",
if global { "global" } else { "project" }
);
println!(" OpenCode: {}", opencode_plugin_path.display());
println!(" Restart OpenCode. Test with: git status\n");
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1721,7 +1721,7 @@ fn run_cli() -> Result<i32> {
hooks::init::show_config(codex)?;
} else if uninstall {
let cursor = agent == Some(AgentTarget::Cursor);
hooks::init::uninstall(global, gemini, codex, cursor, cli.verbose)?;
hooks::init::uninstall(global, gemini, codex, cursor, opencode, cli.verbose)?;
} else if gemini {
let patch_mode = if auto_patch {
hooks::init::PatchMode::Auto
Expand Down