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
28 changes: 28 additions & 0 deletions skills/raw-command-output/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
name: raw-command-output
description: Use when shell command output appears compressed, summarized, or mangled, or when you need exact raw output (JSON/XML/CSV) for data analysis. Teaches RTK_DISABLED=1 prefix to bypass rtk output filtering.
allowed-tools: Bash
---

# Raw Command Output

## When to Use

- Shell command output appears compressed, summarized, or truncated
- You need to parse raw JSON/XML/CSV data from a command
- A command succeeded but the output is unusable for analysis

## How to Use

Prefix your shell command with `RTK_DISABLED=1` to bypass output filtering:

RTK_DISABLED=1 aws ec2 describe-spot-price-history --output json
RTK_DISABLED=1 kubectl get pods -o json
RTK_DISABLED=1 curl -s https://api.example.com/data
RTK_DISABLED=1 terraform show -json

This is a standard shell environment variable prefix. It is recognised by
the rtk binary and all agent hooks (OpenCode, Claude Code, Cursor, etc.).

The system also automatically detects retries and disables filtering,
but use this prefix proactively when you know you need raw data.
5 changes: 5 additions & 0 deletions src/hooks/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ pub const OPENCODE_SUBDIR: &str = "opencode";
pub const PLUGIN_SUBDIR: &str = "plugins";
pub const OPENCODE_PLUGIN_FILE: &str = "rtk.ts";

pub const AGENTS_DIR: &str = ".agents";
pub const SKILLS_SUBDIR: &str = "skills";
pub const RTK_SKILL_DIR: &str = "raw-command-output";
pub const RTK_SKILL_FILE: &str = "SKILL.md";

pub const CURSOR_DIR: &str = ".cursor";
pub const CODEX_DIR: &str = ".codex";
pub const GEMINI_DIR: &str = ".gemini";
Expand Down
101 changes: 98 additions & 3 deletions src/hooks/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use std::path::{Path, PathBuf};
use tempfile::NamedTempFile;

use crate::hooks::constants::{
CONFIG_DIR, CURSOR_DIR, GEMINI_DIR, OPENCODE_PLUGIN_FILE, OPENCODE_SUBDIR, PLUGIN_SUBDIR,
AGENTS_DIR, CONFIG_DIR, CURSOR_DIR, GEMINI_DIR, OPENCODE_PLUGIN_FILE, OPENCODE_SUBDIR,
PLUGIN_SUBDIR, RTK_SKILL_DIR, RTK_SKILL_FILE, SKILLS_SUBDIR,
};

use super::constants::{
Expand All @@ -22,6 +23,9 @@ use super::integrity;
// Embedded OpenCode plugin (auto-rewrite)
const OPENCODE_PLUGIN: &str = include_str!("../../hooks/opencode/rtk.ts");

// Embedded OpenCode skill (RTK_DISABLED=1 opt-out)
const OPENCODE_SKILL: &str = include_str!("../../skills/raw-command-output/SKILL.md");

// Embedded slim RTK awareness instructions
const RTK_SLIM: &str = include_str!("../../hooks/claude/rtk-awareness.md");
const RTK_SLIM_CODEX: &str = include_str!("../../hooks/codex/rtk-awareness.md");
Expand Down Expand Up @@ -1122,6 +1126,7 @@ fn run_default_mode(
let opencode_plugin_path = if install_opencode {
let path = prepare_opencode_plugin_path()?;
ensure_opencode_plugin_installed(&path, ctx)?;
ensure_rtk_skill_installed(ctx)?;
Some(path)
} else {
None
Expand Down Expand Up @@ -1413,6 +1418,7 @@ fn run_hook_only_mode(
let opencode_plugin_path = if install_opencode {
let path = prepare_opencode_plugin_path()?;
ensure_opencode_plugin_installed(&path, ctx)?;
ensure_rtk_skill_installed(ctx)?;
Some(path)
} else {
None
Expand Down Expand Up @@ -1554,6 +1560,7 @@ fn run_claude_md_mode(global: bool, install_opencode: bool, ctx: InitContext) ->
if install_opencode {
let opencode_plugin_path = prepare_opencode_plugin_path()?;
ensure_opencode_plugin_installed(&opencode_plugin_path, ctx)?;
ensure_rtk_skill_installed(ctx)?;
if !dry_run {
println!(
"[ok] OpenCode plugin installed: {}",
Expand Down Expand Up @@ -2727,6 +2734,24 @@ fn opencode_plugin_path(opencode_dir: &Path) -> PathBuf {
opencode_dir.join(PLUGIN_SUBDIR).join(OPENCODE_PLUGIN_FILE)
}

/// Return RTK skill path: ~/.agents/skills/raw-command-output/SKILL.md
fn rtk_skill_path() -> Result<PathBuf> {
let home = resolve_home_subdir(AGENTS_DIR)?;
Ok(home
.join(SKILLS_SUBDIR)
.join(RTK_SKILL_DIR)
.join(RTK_SKILL_FILE))
}

/// Return Claude Code skill path: ~/.claude/skills/raw-command-output/SKILL.md
fn claude_skill_path() -> Result<PathBuf> {
let home = resolve_home_subdir(CLAUDE_DIR)?;
Ok(home
.join(SKILLS_SUBDIR)
.join(RTK_SKILL_DIR)
.join(RTK_SKILL_FILE))
}

/// Prepare OpenCode plugin directory and return install path
fn prepare_opencode_plugin_path() -> Result<PathBuf> {
let opencode_dir = resolve_opencode_dir()?;
Expand All @@ -2752,7 +2777,35 @@ fn ensure_opencode_plugin_installed(path: &Path, ctx: InitContext) -> Result<boo
write_if_changed(path, OPENCODE_PLUGIN, "OpenCode plugin", ctx)
}

/// Remove OpenCode plugin file
/// Write RTK skill file to ~/.agents/skills/ and ~/.claude/skills/
fn ensure_rtk_skill_installed(ctx: InitContext) -> Result<bool> {
let InitContext { dry_run, .. } = ctx;
let mut changed = false;

// Install to each supported skill directory
let paths = [rtk_skill_path(), claude_skill_path()];
for path_result in &paths {
if let Ok(path) = path_result {
if !dry_run {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).with_context(|| {
format!(
"Failed to create skills directory: {}",
parent.display()
)
})?;
}
}
if write_if_changed(path, OPENCODE_SKILL, "RTK skill", ctx)? {
changed = true;
}
}
}

Ok(changed)
}

/// Remove OpenCode plugin and RTK skill files
fn remove_opencode_plugin(ctx: InitContext) -> Result<Vec<PathBuf>> {
let InitContext { verbose, dry_run } = ctx;
let opencode_dir = resolve_opencode_dir()?;
Expand All @@ -2772,6 +2825,24 @@ fn remove_opencode_plugin(ctx: InitContext) -> Result<Vec<PathBuf>> {
removed.push(path);
}

// Also remove the RTK skill from all locations
for skill_path_result in &[rtk_skill_path(), claude_skill_path()] {
if let Ok(skill_path) = skill_path_result {
if skill_path.exists() {
if dry_run {
println!("[dry-run] would remove RTK skill: {}", skill_path.display());
} else {
fs::remove_file(skill_path)
.with_context(|| format!("Failed to remove RTK skill: {}", skill_path.display()))?;
if verbose > 0 {
eprintln!("Removed RTK skill: {}", skill_path.display());
}
}
removed.push(skill_path.clone());
}
}
}

Ok(removed)
}

Expand Down Expand Up @@ -3259,6 +3330,23 @@ fn show_claude_config() -> Result<()> {
println!("[--] OpenCode: config dir not found");
}

// Check RTK skill (all install locations)
let mut skill_found = false;
for (label, path_result) in [
("~/.agents", rtk_skill_path()),
("~/.claude", claude_skill_path()),
] {
if let Ok(skill_path) = path_result {
if skill_path.exists() {
println!("[ok] Skill: RTK_DISABLED opt-out [{}] ({})", label, skill_path.display());
skill_found = true;
}
}
}
if !skill_found {
println!("[--] Skill: not found");
}

// Check Cursor hooks
if let Ok(cursor_dir) = resolve_cursor_dir() {
let cursor_hook = cursor_dir.join(HOOKS_SUBDIR).join(REWRITE_HOOK_FILE);
Expand Down Expand Up @@ -3387,9 +3475,16 @@ fn run_opencode_only_mode(ctx: InitContext) -> Result<()> {
let InitContext { dry_run, .. } = ctx;
let opencode_plugin_path = prepare_opencode_plugin_path()?;
ensure_opencode_plugin_installed(&opencode_plugin_path, ctx)?;
ensure_rtk_skill_installed(ctx)?;
if !dry_run {
println!("\nOpenCode plugin installed (global).\n");
println!(" OpenCode: {}", opencode_plugin_path.display());
println!(" Plugin: {}", opencode_plugin_path.display());
if let Ok(p) = rtk_skill_path() {
println!(" Skill: {}", p.display());
}
if let Ok(p) = claude_skill_path() {
println!(" Skill: {}", p.display());
}
println!(" Restart OpenCode. Test with: git status\n");
}
Ok(())
Expand Down