diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 93c546c8d..18ec56b0d 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.36.0" + ".": "0.37.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 08cae60ae..081d4f11e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/Cargo.lock b/Cargo.lock index 7ad9dc981..f24979261 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -892,7 +892,7 @@ dependencies = [ [[package]] name = "rtk" -version = "0.36.0" +version = "0.37.1" dependencies = [ "anyhow", "automod", diff --git a/Cargo.toml b/Cargo.toml index 81cc9c1df..8da192c91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index ff848a690..5dd820f84 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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) | diff --git a/docs/guide/getting-started/supported-agents.md b/docs/guide/getting-started/supported-agents.md index 4623353d5..7e0ac7f62 100644 --- a/docs/guide/getting-started/supported-agents.md +++ b/docs/guide/getting-started/supported-agents.md @@ -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 diff --git a/docs/guide/resources/troubleshooting.md b/docs/guide/resources/troubleshooting.md index 51a6fa3be..ec639a836 100644 --- a/docs/guide/resources/troubleshooting.md +++ b/docs/guide/resources/troubleshooting.md @@ -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. @@ -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" ``` diff --git a/hooks/opencode/README.md b/hooks/opencode/README.md index 8edc93cc4..6fdc84bed 100644 --- a/hooks/opencode/README.md +++ b/hooks/opencode/README.md @@ -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` diff --git a/src/hooks/init.rs b/src/hooks/init.rs index 65517fc7b..05d6dd59c 100644 --- a/src/hooks/init.rs +++ b/src/hooks/init.rs @@ -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"); } @@ -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)?, @@ -535,7 +531,14 @@ fn remove_hook_from_settings(verbose: u8) -> Result { } /// 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); } @@ -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"); } @@ -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())); } @@ -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 { @@ -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 { @@ -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: {}", @@ -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 { - resolve_home_subdir(".config/opencode") +fn resolve_opencode_dir(global: bool) -> Result { + 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 { - let opencode_dir = resolve_opencode_dir()?; +fn prepare_opencode_plugin_path(global: bool) -> Result { + 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(|| { @@ -1809,8 +1829,8 @@ fn ensure_opencode_plugin_installed(path: &Path, verbose: u8) -> Result { } /// Remove OpenCode plugin file -fn remove_opencode_plugin(verbose: u8) -> Result> { - let opencode_dir = resolve_opencode_dir()?; +fn remove_opencode_plugin(global: bool, verbose: u8) -> Result> { + let opencode_dir = resolve_opencode_dir(global)?; let path = opencode_plugin_path(&opencode_dir); let mut removed = Vec::new(); @@ -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 @@ -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(()) @@ -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(()) diff --git a/src/main.rs b/src/main.rs index e8a19c2be..e8d48a613 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1721,7 +1721,7 @@ fn run_cli() -> Result { 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