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
47 changes: 47 additions & 0 deletions src/dippy/cli/rtk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""rtk (Rust Token Killer) handler for Dippy.

rtk is a token-optimized CLI proxy that is typically prepended to bash
commands via a Claude Code PreToolUse hook. When both rtk and Dippy are
installed, Dippy sees commands like ``rtk git log`` and would otherwise
miss the ``git`` handler. This handler treats rtk as a transparent
wrapper so the inner command is analyzed directly.

Meta subcommands that do not wrap another command:
- ``rtk gain [--history]``: print token savings analytics (read-only)
- ``rtk discover``: analyze Claude Code history (read-only)
- ``rtk proxy <cmd>``: run the raw command without rtk's filtering; we
still delegate to the inner command for safety analysis
"""

from __future__ import annotations

from dippy.cli import Classification, HandlerContext
from dippy.core.bash import bash_join

COMMANDS = ["rtk"]

READ_ONLY_SUBCOMMANDS = frozenset({"gain", "discover"})


def classify(ctx: HandlerContext) -> Classification:
"""Classify an rtk command."""
tokens = ctx.tokens
if len(tokens) == 1:
return Classification("ask", description="rtk")

sub = tokens[1]

if sub in READ_ONLY_SUBCOMMANDS:
return Classification("allow", description=f"rtk {sub}")

if sub == "proxy":
if len(tokens) == 2:
return Classification("ask", description="rtk proxy")
inner_cmd = bash_join(tokens[2:])
return Classification("delegate", inner_command=inner_cmd)

if sub.startswith("-"):
return Classification("ask", description=f"rtk {sub}")

inner_cmd = bash_join(tokens[1:])
return Classification("delegate", inner_command=inner_cmd)
52 changes: 52 additions & 0 deletions tests/cli/test_rtk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Test cases for rtk (Rust Token Killer) transparent wrapper."""

import pytest
from conftest import is_approved, needs_confirmation

TESTS = [
# Bare rtk - ask
("rtk", False),
# Version / help - handled by global version/help check
("rtk --version", True),
("rtk --help", True),
("rtk -h", True),
("rtk help", True),
("rtk version", True),
# Read-only meta subcommands
("rtk gain", True),
("rtk gain --history", True),
("rtk discover", True),
# Transparent wrapper - safe inner
("rtk ls", True),
("rtk ls -la", True),
("rtk cat README.md", True),
("rtk git status", True),
("rtk git log", True),
("rtk git log --oneline -5", True),
# Transparent wrapper - unsafe inner
("rtk rm -rf /", False),
("rtk git push --force origin main", False),
("rtk make", False),
# proxy escape hatch - delegates to inner
("rtk proxy ls", True),
("rtk proxy git status", True),
("rtk proxy rm -rf /", False),
# proxy with no inner command - ask
("rtk proxy", False),
# Chained with && still inspects each side
("rtk git status && rtk git log --oneline -5", True),
("rtk ls && rtk rm foo", False),
# Piped through another rtk-wrapped command
("rtk git log --oneline | rtk head -5", True),
# Unknown rtk flag - ask
("rtk --nonexistent-flag", False),
]


@pytest.mark.parametrize("command,expected", TESTS)
def test_command(check, command: str, expected: bool):
result = check(command)
if expected:
assert is_approved(result), f"Expected approve: {command}"
else:
assert needs_confirmation(result), f"Expected confirm: {command}"