# rtk-registry

Command rewriting engine from the [RTK (Rust Token Killer)](https://github.com/TokenFleet-AI/rtk) project.

Rewrite shell commands to their RTK-optimized equivalents for token-efficient LLM filtering. Supports 200+ commands across git, cargo, npm/pnpm, go, python, docker, and more ecosystems.

## Quick Start

Add to your `Cargo.toml`:

```toml
[dependencies]
rtk-registry = "1.0"
```

## Usage

### Rewrite a Command

```rust
use rtk_registry::rewrite_command;

let rewritten = rewrite_command("git status", &[], &[]);
assert_eq!(rewritten, Some("rtk git status".to_string()));

// Unsupported command
let result = rewrite_command("htop", &[], &[]);
assert_eq!(result, None);

// Already has rtk prefix
let result = rewrite_command("rtk git log", &[], &[]);
assert_eq!(result, Some("rtk git log".to_string()));
```

### Compound Commands

```rust
use rtk_registry::rewrite_command;

// Chain commands are rewritten segment by segment
let result = rewrite_command("git status && cargo test", &[], &[]);
assert_eq!(result, Some("rtk git status && rtk cargo test".to_string()));

// Pipes work too
let result = rewrite_command("git diff | head -20", &[], &[]);
assert_eq!(result, Some("rtk git diff | head -20".to_string()));
```

### Check if RTK is Installed

```rust
use rtk_registry::{is_rtk_installed, RtkInstallStatus};

match is_rtk_installed() {
    RtkInstallStatus::Installed { path } => {
        println!("rtk is installed at: {}", path.display());
    }
    RtkInstallStatus::NotInstalled { install_hints } => {
        eprintln!("rtk is not installed.");
        eprintln!("Install with one of:");
        for cmd in &install_hints.install_commands {
            eprintln!("  {}", cmd);
        }
        eprintln!("Repository: {}", install_hints.repository);
        eprintln!("Docs: {}", install_hints.docs_url);
    }
}
```

### Require RTK (Fail if Not Installed)

```rust
use rtk_registry::require_rtk_installed;

let rtk_path = require_rtk_installed().map_err(|hints| {
    format!(
        "rtk not found. Install via: {}",
        hints.install_commands.join(" or ")
    )
})?;

println!("Using rtk at: {}", rtk_path.display());
```

### Exclude Commands and Transparent Prefixes

```rust
use rtk_registry::rewrite_command;

// Exclude specific commands from rewriting
let excluded = vec!["git push".to_string()];
let result = rewrite_command("git push origin main", &excluded, &[]);
assert_eq!(result, None);

// Transparent prefixes (wrapper commands)
let prefixes = vec!["docker exec mycontainer".to_string()];
let result = rewrite_command("docker exec mycontainer git status", &[], &prefixes);
assert_eq!(result, Some("docker exec mycontainer rtk git status".to_string()));
```

### Classify a Command

```rust
use rtk_registry::{classify_command, Classification};

match classify_command("git status") {
    Classification::Supported { rtk_equivalent, category, estimated_savings_pct, .. } => {
        println!("RTK equivalent: {}", rtk_equivalent);
        println!("Category: {}", category);
        println!("Estimated savings: {:.0}%", estimated_savings_pct);
    }
    Classification::Unsupported { base_command } => {
        println!("No RTK support for: {}", base_command);
    }
    Classification::Ignored => {
        println!("Command should be passed through unchanged");
    }
}
```

### Access the Rule Table

```rust
use rtk_registry::RULES;

println!("RTK supports {} command patterns", RULES.len());

for rule in RULES.iter().take(5) {
    println!("{} → {} ({}% savings)",
        rule.pattern,
        rule.rtk_cmd,
        rule.savings_pct
    );
}
```

### Security: Detect Untrusted Shell Constructs

```rust
use rtk_registry::contains_unattestable_construct;

// Dangerous: command substitution inside a rewritable command
assert!(contains_unattestable_construct("git status `whoami`"));
assert!(contains_unattestable_construct("git log --pretty=$(rm -rf ~)"));

// Safe: no substitution, simple variable expansion is fine
assert!(!contains_unattestable_construct("git status"));
assert!(!contains_unattestable_construct("echo $HOME"));

// Safe: fd-dup redirects (2>&1) and /dev/null are exempt
assert!(!contains_unattestable_construct("cargo test 2>&1"));
assert!(!contains_unattestable_construct("cmd > /dev/null"));
```

### Split Command for Permission Checking

```rust
use rtk_registry::split_for_permissions;

// Splits on operators, pipes, newlines, background &, and subshells
let segments = split_for_permissions("git status && cargo build");
assert_eq!(segments, vec!["git status", "cargo build"]);

// Truncates at redirects
let segments = split_for_permissions("git log > /tmp/out");
assert_eq!(segments, vec!["git log"]);
```

## Public API

| Function / Type | Description |
|-----------------|-------------|
| `rewrite_command(cmd, excluded, transparent_prefixes) -> Option<String>` | Rewrite a command to its RTK equivalent |
| `rewrite_command_with_config(cmd, excluded, transparent_prefixes, config) -> Option<String>` | Rewrite with custom warning callback |
| `classify_command(cmd) -> Classification` | Classify whether RTK handles this command |
| `contains_unattestable_construct(cmd) -> bool` | Detect shell constructs unsafe for auto-rewrite (backticks, `$()`, `<()`) |
| `split_for_permissions(cmd) -> Vec<&str>` | Split command for per-segment permission validation |
| `is_rtk_installed() -> RtkInstallStatus` | Check if rtk binary is in PATH |
| `require_rtk_installed() -> Result<PathBuf, InstallHints>` | Require rtk, return install hints on failure |
| `has_heredoc(cmd) -> bool` | Detect heredoc in command (won't rewrite) |
| `split_command_chain(cmd) -> Vec<&str>` | Split compound command on operators |
| `category_avg_tokens(category, subcmd) -> usize` | Average token count for a category/subcmd |
| `cmd_has_rtk_disabled_prefix(cmd) -> bool` | Check if command has `RTK_DISABLED=` env prefix |
| `prefix_contains_rtk_disabled(prefix) -> bool` | Check if a prefix part contains `RTK_DISABLED=` |
| `strip_disabled_prefix(cmd) -> (&str, &str)` | Strip `RTK_DISABLED=` prefix, return (prefix, rest) |
| `RewriteConfig` | Config struct with optional `on_warning: Option<fn(&str)>` callback |
| `Classification` | Enum: `Supported`, `Unsupported`, `Ignored` |
| `RtkRule` | Rule struct with `pattern`, `rtk_cmd`, `category`, `savings_pct` fields |
| `RtkStatus` | Enum: `Existing`, `Passthrough`, `NotSupported` |
| `IGNORED_EXACT: &[&str]` | Exact commands that RTK should not rewrite |
| `IGNORED_PREFIXES: &[&str]` | Command prefixes that RTK should not rewrite |
| `RULES: &[RtkRule]` | Static table of all rewrite rules (74 patterns) |

## Supported Ecosystems

| Ecosystem | Commands | Estimated Savings |
|-----------|----------|-------------------|
| Git | git, gh, gt, diff | 80%+ |
| Rust | cargo, cargo test | 90%+ |
| JS/TS | npm, pnpm, vitest, jest, next, playwright, prisma | 70%+ |
| Python | ruff, pytest, mypy, pip | 75%+ |
| Go | go, golangci-lint | 65%+ |
| Containers | docker, kubectl | 60%+ |
| System | ls, tree, find, grep, env | 70%+ |
| Ruby | rake, rspec, rubocop | 80%+ |
| .NET | dotnet, binlog, trx | 60%+ |

## License

MIT
